All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowBluetoothAdapter Maven / Gradle / Ivy

The newest version!
package org.robolectric.shadows;

import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
import static android.os.Build.VERSION_CODES.S_V2;
import static android.os.Build.VERSION_CODES.TIRAMISU;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter.LeScanCallback;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.IBluetoothGatt;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.bluetooth.le.BluetoothLeScanner;
import android.content.Context;
import android.os.Binder;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.IBinder;
import android.os.IInterface;
import android.os.ParcelUuid;
import android.provider.Settings;
import android.util.Log;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nullable;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;
import org.robolectric.versioning.AndroidVersions.V;

@SuppressWarnings({"UnusedDeclaration"})
@Implements(value = BluetoothAdapter.class)
public class ShadowBluetoothAdapter {
  @RealObject private BluetoothAdapter realAdapter;

  private static final int ADDRESS_LENGTH = 17;
  private static final int LE_MAXIMUM_ADVERTISING_DATA_LENGTH = 31;
  private static final int LE_MAXIMUM_ADVERTISING_DATA_LENGTH_EXTENDED = 1650;

  /**
   * Equivalent value to internal SystemApi {@link
   * BluetoothStatusCodes#RFCOMM_LISTENER_START_FAILED_UUID_IN_USE}.
   */
  public static final int RFCOMM_LISTENER_START_FAILED_UUID_IN_USE = 2000;

  /**
   * Equivalent value to internal SystemApi {@link
   * BluetoothStatusCodes#RFCOMM_LISTENER_OPERATION_FAILED_NO_MATCHING_SERVICE_RECORD}.
   */
  public static final int RFCOMM_LISTENER_OPERATION_FAILED_NO_MATCHING_SERVICE_RECORD = 2001;

  /**
   * Equivalent value to internal SystemApi {@link
   * BluetoothStatusCodes#RFCOMM_LISTENER_FAILED_TO_CLOSE_SERVER_SOCKET}.
   */
  public static final int RFCOMM_LISTENER_FAILED_TO_CLOSE_SERVER_SOCKET = 2004;

  private static boolean isBluetoothSupported = true;

  private static final Map deviceCache = new HashMap<>();
  private Set bondedDevices = new HashSet();
  private List mostRecentlyConnectedDevices = new ArrayList<>();
  private Set leScanCallbacks = new HashSet();
  private boolean isDiscovering;
  private String address;
  private int state;
  private String name = "DefaultBluetoothDeviceName";
  private int scanMode = BluetoothAdapter.SCAN_MODE_NONE;
  private Duration discoverableTimeout;
  private boolean isBleScanAlwaysAvailable = true;
  private boolean isMultipleAdvertisementSupported = true;
  private int isLeAudioSupported = BluetoothStatusCodes.FEATURE_NOT_SUPPORTED;
  private int isDistanceMeasurementSupported = BluetoothStatusCodes.FEATURE_NOT_SUPPORTED;
  private boolean isLeExtendedAdvertisingSupported = true;
  private boolean isLeCodedPhySupported = true;
  private boolean isLe2MPhySupported = true;
  private boolean isOverridingProxyBehavior;
  private final Map profileConnectionStateData = new HashMap<>();
  private final Map profileProxies = new HashMap<>();
  private final ConcurrentMap backgroundRfcommServers =
      new ConcurrentHashMap<>();
  private final Map>
      bluetoothProfileServiceListeners = new HashMap<>();
  private IBluetoothGatt ibluetoothGatt;

  @Resetter
  public static void reset() {
    setIsBluetoothSupported(true);
    BluetoothAdapterReflector bluetoothReflector = reflector(BluetoothAdapterReflector.class);
    int apiLevel = RuntimeEnvironment.getApiLevel();
    if (apiLevel <= VERSION_CODES.R) {
      bluetoothReflector.setSBluetoothLeAdvertiser(null);
      bluetoothReflector.setSBluetoothLeScanner(null);
    }
    bluetoothReflector.setAdapter(null);
    deviceCache.clear();
  }

  @Implementation
  protected static BluetoothAdapter getDefaultAdapter() {
    if (!isBluetoothSupported) {
      return null;
    }
    return reflector(BluetoothAdapterReflector.class).getDefaultAdapter();
  }

  /** Sets whether the Le Audio is supported or not. Minimum sdk version required is TIRAMISU. */
  public void setLeAudioSupported(int supported) {
    isLeAudioSupported = supported;
  }

  @Implementation(minSdk = VERSION_CODES.TIRAMISU)
  protected int isLeAudioSupported() {
    return isLeAudioSupported;
  }

  /** Determines if getDefaultAdapter() returns the default local adapter (true) or null (false). */
  public static void setIsBluetoothSupported(boolean supported) {
    isBluetoothSupported = supported;
  }

  /**
   * Sets whether the distance measurement is supported or not. Minimum sdk version required is
   * UPSIDE_DOWN_CAKE.
   */
  public void setDistanceMeasurementSupported(int supported) {
    isDistanceMeasurementSupported = supported;
  }

  @Implementation(minSdk = UPSIDE_DOWN_CAKE)
  protected int isDistanceMeasurementSupported() {
    return isDistanceMeasurementSupported;
  }

  /**
   * @deprecated use real BluetoothLeAdvertiser instead
   */
  @Deprecated
  public void setBluetoothLeAdvertiser(BluetoothLeAdvertiser advertiser) {
    if (RuntimeEnvironment.getApiLevel() <= VERSION_CODES.LOLLIPOP_MR1) {
      reflector(BluetoothAdapterReflector.class, realAdapter).setSBluetoothLeAdvertiser(advertiser);
    } else {
      reflector(BluetoothAdapterReflector.class, realAdapter).setBluetoothLeAdvertiser(advertiser);
    }
  }

  @Implementation
  protected synchronized BluetoothDevice getRemoteDevice(String address) {
    if (!deviceCache.containsKey(address)) {
      deviceCache.put(
          address,
          reflector(BluetoothAdapterReflector.class, realAdapter).getRemoteDevice(address));
    }
    return deviceCache.get(address);
  }

  public void setMostRecentlyConnectedDevices(List devices) {
    mostRecentlyConnectedDevices = devices;
  }

  @Implementation(minSdk = TIRAMISU)
  protected List getMostRecentlyConnectedDevices() {
    return mostRecentlyConnectedDevices;
  }

  @Implementation
  @Nullable
  protected Set getBondedDevices() {
    // real android will return null in error conditions
    if (bondedDevices == null) {
      return null;
    }
    return Collections.unmodifiableSet(bondedDevices);
  }

  public void setBondedDevices(@Nullable Set bluetoothDevices) {
    bondedDevices = bluetoothDevices;
  }

  @Implementation
  protected BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(
      String serviceName, UUID uuid) {
    return ShadowBluetoothServerSocket.newInstance(
        BluetoothSocket.TYPE_RFCOMM, /* auth= */ false, /* encrypt= */ false, new ParcelUuid(uuid));
  }

  @Implementation
  protected BluetoothServerSocket listenUsingRfcommWithServiceRecord(String serviceName, UUID uuid)
      throws IOException {
    return ShadowBluetoothServerSocket.newInstance(
        BluetoothSocket.TYPE_RFCOMM, /* auth= */ false, /* encrypt= */ true, new ParcelUuid(uuid));
  }

  @Implementation(minSdk = Q)
  protected BluetoothServerSocket listenUsingInsecureL2capChannel() throws IOException {
    return ShadowBluetoothServerSocket.newInstance(
        BluetoothSocket.TYPE_L2CAP, /* auth= */ false, /* encrypt= */ true, /* uuid= */ null);
  }

  @Implementation(minSdk = Q)
  protected BluetoothServerSocket listenUsingL2capChannel() throws IOException {
    return ShadowBluetoothServerSocket.newInstance(
        BluetoothSocket.TYPE_L2CAP, /* auth= */ false, /* encrypt= */ true, /* uuid= */ null);
  }

  @Implementation
  protected boolean startDiscovery() {
    isDiscovering = true;
    return true;
  }

  @Implementation
  protected boolean cancelDiscovery() {
    isDiscovering = false;
    return true;
  }

  /** When true, overrides the value of {@link #getLeState}. By default, this is false. */
  @Implementation(minSdk = M)
  protected boolean isBleScanAlwaysAvailable() {
    return isBleScanAlwaysAvailable;
  }

  /**
   * Decides the correct LE state. When off, BLE calls will fail or return null.
   *
   * 

LE is enabled if either Bluetooth or BLE scans are enabled. LE is always off if Airplane * Mode is enabled. */ @Implementation(minSdk = M) public int getLeState() { if (isAirplaneMode()) { return BluetoothAdapter.STATE_OFF; } if (isEnabled()) { return BluetoothAdapter.STATE_ON; } if (isBleScanAlwaysAvailable()) { return BluetoothAdapter.STATE_BLE_ON; } return BluetoothAdapter.STATE_OFF; } /** * True if either Bluetooth is enabled or BLE scanning is available. Always false if Airplane Mode * is enabled. When false, BLE scans will fail. @Implementation(minSdk = M) protected boolean * isLeEnabled() { if (isAirplaneMode()) { return false; } return isEnabled() || * isBleScanAlwaysAvailable(); } */ private static boolean isAirplaneMode() { Context context = RuntimeEnvironment.getApplication(); return Settings.Global.getInt(context.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) != 0; } @Implementation protected boolean startLeScan(LeScanCallback callback) { return startLeScan(null, callback); } @Implementation protected boolean startLeScan(UUID[] serviceUuids, LeScanCallback callback) { if (Build.VERSION.SDK_INT >= M && !realAdapter.isLeEnabled()) { return false; } // Ignoring the serviceUuids param for now. leScanCallbacks.add(callback); return true; } @Implementation protected void stopLeScan(LeScanCallback callback) { leScanCallbacks.remove(callback); } public Set getLeScanCallbacks() { return Collections.unmodifiableSet(leScanCallbacks); } public LeScanCallback getSingleLeScanCallback() { if (leScanCallbacks.size() != 1) { throw new IllegalStateException("There are " + leScanCallbacks.size() + " callbacks"); } return leScanCallbacks.iterator().next(); } @Implementation protected boolean isDiscovering() { return isDiscovering; } @Implementation protected boolean isEnabled() { return state == BluetoothAdapter.STATE_ON; } @Implementation protected boolean enable() { setState(BluetoothAdapter.STATE_ON); return true; } @Implementation protected boolean disable() { setState(BluetoothAdapter.STATE_OFF); return true; } @Implementation protected boolean disable(boolean persist) { return disable(); } @Implementation protected String getAddress() { return this.address; } @Implementation protected int getState() { return state; } @Implementation protected String getName() { return name; } @Implementation protected boolean setName(String name) { this.name = name; return true; } /** * Needs looseSignatures because in Android T the return value of this method was changed from * bool to int. */ @Implementation(maxSdk = S_V2) protected boolean setScanMode(int scanMode) { boolean result = scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE || scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE || scanMode == BluetoothAdapter.SCAN_MODE_NONE; this.scanMode = scanMode; return result; } @Implementation(minSdk = TIRAMISU, methodName = "setScanMode") protected int setScanModeFromT(int scanMode) { return setScanMode(scanMode) ? BluetoothStatusCodes.SUCCESS : BluetoothStatusCodes.ERROR_UNKNOWN; } @Implementation(maxSdk = Q) protected boolean setScanMode(int scanMode, int discoverableTimeout) { setDiscoverableTimeout(discoverableTimeout); return (boolean) setScanMode(scanMode); } @Implementation(minSdk = R, maxSdk = S_V2) protected boolean setScanMode(int scanMode, long durationMillis) { int durationSeconds = Math.toIntExact(durationMillis / 1000); setDiscoverableTimeout(durationSeconds); return (boolean) setScanMode(scanMode); } @Implementation protected int getScanMode() { return scanMode; } @Implementation(maxSdk = S_V2) protected int getDiscoverableTimeout() { return (int) discoverableTimeout.toSeconds(); } /** Return value changed from {@code int} to {@link Duration} starting in T. */ @Implementation(minSdk = TIRAMISU, methodName = "getDiscoverableTimeout") protected Duration getDiscoverableTimeoutT() { return discoverableTimeout; } @Implementation(maxSdk = S_V2) protected void setDiscoverableTimeout(int timeout) { discoverableTimeout = Duration.ofSeconds(timeout); } @Implementation(minSdk = 33) protected int setDiscoverableTimeout(Duration timeout) { if (getState() != STATE_ON) { return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; } if (timeout.toSeconds() > Integer.MAX_VALUE) { throw new IllegalArgumentException( "Timeout in seconds must be less or equal to " + Integer.MAX_VALUE); } this.discoverableTimeout = timeout; return BluetoothStatusCodes.SUCCESS; } @Implementation protected boolean isMultipleAdvertisementSupported() { return isMultipleAdvertisementSupported; } /** * Validate a Bluetooth address, such as "00:43:A8:23:10:F0" Alphabetic characters must be * uppercase to be valid. * * @param address Bluetooth address as string * @return true if the address is valid, false otherwise */ @Implementation protected static boolean checkBluetoothAddress(String address) { if (address == null || address.length() != ADDRESS_LENGTH) { return false; } for (int i = 0; i < ADDRESS_LENGTH; i++) { char c = address.charAt(i); switch (i % 3) { case 0: case 1: if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) { // hex character, OK break; } return false; case 2: if (c == ':') { break; // OK } return false; } } return true; } /** * Returns the connection state for the given Bluetooth {@code profile}, defaulting to {@link * BluetoothProfile.STATE_DISCONNECTED} if the profile's connection state was never set. * *

Set a Bluetooth profile's connection state via {@link #setProfileConnectionState(int, int)}. */ @Implementation protected int getProfileConnectionState(int profile) { Integer state = profileConnectionStateData.get(profile); if (state == null) { return BluetoothProfile.STATE_DISCONNECTED; } return state; } public void setAddress(String address) { this.address = address; } public void setState(int state) { this.state = state; } /** * @deprecated Use {@link BluetoothAdapter#enable()} or {@link BluetoothAdapter#disable()}. */ @Deprecated public void setEnabled(boolean enabled) { if (enabled) { enable(); } else { disable(); } } /** * Sets the value for {@link isBleScanAlwaysAvailable}. If true, {@link getLeState} will always * return true. */ public void setBleScanAlwaysAvailable(boolean alwaysAvailable) { isBleScanAlwaysAvailable = alwaysAvailable; } /** Sets the value for {@link isMultipleAdvertisementSupported}. */ public void setIsMultipleAdvertisementSupported(boolean supported) { isMultipleAdvertisementSupported = supported; } /** Sets the connection state {@code state} for the given BluetoothProfile {@code profile} */ public void setProfileConnectionState(int profile, int state) { profileConnectionStateData.put(profile, state); } /** * Sets the active BluetoothProfile {@code proxy} for the given {@code profile}. Will always * affect behavior of {@link BluetoothAdapter#getProfileProxy} and {@link * BluetoothAdapter#closeProfileProxy}. Call to {@link BluetoothAdapter#closeProfileProxy} can * remove the set active proxy. * * @param proxy can be 'null' to simulate the situation where {@link * BluetoothAdapter#getProfileProxy} would return 'false'. This can happen on older Android * versions for Bluetooth profiles introduced in later Android versions. */ public void setProfileProxy(int profile, @Nullable BluetoothProfile proxy) { isOverridingProxyBehavior = true; if (proxy != null) { profileProxies.put(profile, proxy); } } /** * @return 'true' if active (non-null) proxy has been set by {@link * ShadowBluetoothAdapter#setProfileProxy} for the given {@code profile} AND it has not been * "deactivated" by a call to {@link BluetoothAdapter#closeProfileProxy}. Only meaningful if * {@link ShadowBluetoothAdapter#setProfileProxy} has been previously called. */ public boolean hasActiveProfileProxy(int profile) { return profileProxies.get(profile) != null; } /** * Overrides behavior of {@link getProfileProxy} if {@link ShadowBluetoothAdapter#setProfileProxy} * has been previously called. * *

If active (non-null) proxy has been set by {@link setProfileProxy} for the given {@code * profile}, {@link getProfileProxy} will immediately call {@code onServiceConnected} of the given * BluetoothProfile.ServiceListener {@code listener}. * * @return 'true' if a proxy object has been set by {@link setProfileProxy} for the given * BluetoothProfile {@code profile} */ @Implementation protected boolean getProfileProxy( Context context, BluetoothProfile.ServiceListener listener, int profile) { if (!isOverridingProxyBehavior) { return reflector(BluetoothAdapterReflector.class, realAdapter) .getProfileProxy(context, listener, profile); } BluetoothProfile proxy = profileProxies.get(profile); if (proxy == null) { return false; } else { listener.onServiceConnected(profile, proxy); List profileListeners = bluetoothProfileServiceListeners.get(profile); if (profileListeners != null) { profileListeners.add(listener); } else { bluetoothProfileServiceListeners.put(profile, new ArrayList<>(ImmutableList.of(listener))); } return true; } } /** * Overrides behavior of {@link closeProfileProxy} if {@link * ShadowBluetoothAdapter#setProfileProxy} has been previously called. * *

If the given non-null BluetoothProfile {@code proxy} was previously set for the given {@code * profile} by {@link ShadowBluetoothAdapter#setProfileProxy}, this proxy will be "deactivated". */ @Implementation protected void closeProfileProxy(int profile, BluetoothProfile proxy) { if (!isOverridingProxyBehavior) { reflector(BluetoothAdapterReflector.class, realAdapter).closeProfileProxy(profile, proxy); return; } if (proxy != null && proxy.equals(profileProxies.get(profile))) { profileProxies.remove(profile); List profileListeners = bluetoothProfileServiceListeners.remove(profile); if (profileListeners != null) { for (BluetoothProfile.ServiceListener listener : profileListeners) { listener.onServiceDisconnected(profile); } } } } @Implementation(minSdk = V.SDK_INT) protected IBinder getProfile(int profile) { if (isEnabled()) { IInterface localProxy = createBinderProfileProxy(profile); if (localProxy != null) { Binder binder = new Binder(); binder.attachInterface(localProxy, "profile"); return binder; } } return null; } private static IInterface createBinderProfileProxy(int profile) { switch (profile) { case BluetoothProfile.HEADSET: return ReflectionHelpers.createNullProxy(android.bluetooth.IBluetoothHeadset.class); case BluetoothProfile.A2DP: return ReflectionHelpers.createNullProxy(android.bluetooth.IBluetoothA2dp.class); } Log.w("ShadowBluetoothAdapter", "getProfile called with unsupported profile " + profile); return null; } /** Returns the last value of {@link #setIsLeExtendedAdvertisingSupported}, defaulting to true. */ @Implementation(minSdk = O) protected boolean isLeExtendedAdvertisingSupported() { return isLeExtendedAdvertisingSupported; } /** * Sets the isLeExtendedAdvertisingSupported to enable/disable LE extended advertisements feature */ public void setIsLeExtendedAdvertisingSupported(boolean supported) { isLeExtendedAdvertisingSupported = supported; } /** Returns the last value of {@link #setIsLeCodedPhySupported}, defaulting to true. */ @Implementation(minSdk = UPSIDE_DOWN_CAKE) protected boolean isLeCodedPhySupported() { return isLeCodedPhySupported; } /** Sets the {@link #isLeCodedPhySupported} to enable/disable LE coded phy supported featured. */ public void setIsLeCodedPhySupported(boolean supported) { isLeCodedPhySupported = supported; } /** Returns the last value of {@link #setIsLe2MPhySupported}, defaulting to true. */ @Implementation(minSdk = UPSIDE_DOWN_CAKE) protected boolean isLe2MPhySupported() { return isLe2MPhySupported; } /** Sets the {@link #isLe2MPhySupported} to enable/disable LE 2M phy supported featured. */ public void setIsLe2MPhySupported(boolean supported) { isLe2MPhySupported = supported; } @Implementation(minSdk = O) protected int getLeMaximumAdvertisingDataLength() { return isLeExtendedAdvertisingSupported ? LE_MAXIMUM_ADVERTISING_DATA_LENGTH_EXTENDED : LE_MAXIMUM_ADVERTISING_DATA_LENGTH; } @Implementation(minSdk = TIRAMISU) protected int startRfcommServer(String name, UUID uuid, PendingIntent pendingIntent) { // PendingIntent#isImmutable throws an NPE if the component does not exist, so verify directly // against the flags for now. if ((shadowOf(pendingIntent).getFlags() & PendingIntent.FLAG_IMMUTABLE) == 0) { throw new IllegalArgumentException("RFCOMM servers PendingIntent must be marked immutable"); } boolean[] isNewServerSocket = {false}; backgroundRfcommServers.computeIfAbsent( uuid, unused -> { isNewServerSocket[0] = true; return new BackgroundRfcommServerEntry(uuid, pendingIntent); }); return isNewServerSocket[0] ? BluetoothStatusCodes.SUCCESS : RFCOMM_LISTENER_START_FAILED_UUID_IN_USE; } @Implementation(minSdk = TIRAMISU) protected int stopRfcommServer(UUID uuid) { BackgroundRfcommServerEntry entry = backgroundRfcommServers.remove(uuid); if (entry == null) { return RFCOMM_LISTENER_OPERATION_FAILED_NO_MATCHING_SERVICE_RECORD; } try { entry.serverSocket.close(); return BluetoothStatusCodes.SUCCESS; } catch (IOException e) { return RFCOMM_LISTENER_FAILED_TO_CLOSE_SERVER_SOCKET; } } @Implementation(minSdk = TIRAMISU) @Nullable protected BluetoothSocket retrieveConnectedRfcommSocket(UUID uuid) { BackgroundRfcommServerEntry serverEntry = backgroundRfcommServers.get(uuid); try { return serverEntry == null ? null : serverEntry.serverSocket.accept(/* timeout= */ 0); } catch (IOException e) { // This means there is no pending socket, so the contract indicates we should return null. return null; } } /** * Creates an incoming socket connection from the given {@link BluetoothDevice} to a background * Bluetooth server created with {@link BluetoothAdapter#startRfcommServer(String, UUID, * PendingIntent)} on the given uuid. * *

Creating this socket connection will invoke the {@link PendingIntent} provided in {@link * BluetoothAdapter#startRfcommServer(String, UUID, PendingIntent)} when the server socket was * created for the given UUID. The component provided in the intent can then call {@link * BluetoothAdapter#retrieveConnectedRfcommSocket(UUID)} to obtain the server side socket. * *

A {@link ShadowBluetoothSocket} obtained from the returned {@link BluetoothSocket} can be * used to send data to and receive data from the server side socket. This returned {@link * BluetoothSocket} is the same socket as returned by {@link * BluetoothAdapter#retrieveConnectedRfcommSocket(UUID)} and should generally not be used directly * outside of obtaining the shadow, as this socket is normally not exposed outside of the * component started by the pending intent. {@link ShadowBluetoothSocket#getInputStreamFeeder()} * and {@link ShadowBluetoothSocket#getOutputStreamSink()} can be used to send data to and from * the socket as if it was a remote connection. * *

Warning: The socket returned by this method and the corresponding server side socket * retrieved from {@link BluetoothAdapter#retrieveConnectedRfcommSocket(UUID)} do not support * reads and writes from different threads. Once reading or writing is started for a given socket * on a given thread, that type of operation on that socket must only be done on that thread. * * @return a server side BluetoothSocket or {@code null} if the {@link UUID} is not registered. * This value should generally not be used directly, and is mainly used to obtain a shadow * with which a RFCOMM client can be simulated. * @throws IllegalArgumentException if a server is not started for the given {@link UUID}. * @throws CanceledException if the pending intent for the server socket was cancelled. */ public BluetoothSocket addIncomingRfcommConnection(BluetoothDevice remoteDevice, UUID uuid) throws CanceledException { BackgroundRfcommServerEntry entry = backgroundRfcommServers.get(uuid); if (entry == null) { throw new IllegalArgumentException("No RFCOMM server open for UUID: " + uuid); } BluetoothSocket socket = shadowOf(entry.serverSocket).deviceConnected(remoteDevice); entry.pendingIntent.send(); return socket; } /** * Returns an immutable set of {@link UUID}s representing the currently registered RFCOMM servers. */ @SuppressWarnings("JdkImmutableCollections") public Set getRegisteredRfcommServerUuids() { return Set.of(backgroundRfcommServers.keySet().toArray(new UUID[0])); } @Implementation(minSdk = S) protected int getNameLengthForAdvertise() { return name.length(); } // TODO: remove this method as it shouldn't be necessary. // Real android just calls IBluetoothManager.getBluetoothGatt @Implementation(minSdk = V.SDK_INT) protected IBluetoothGatt getBluetoothGatt() { if (ibluetoothGatt == null) { ibluetoothGatt = BluetoothGattProxyDelegate.createBluetoothGattProxy(); } return ibluetoothGatt; } private static final class BackgroundRfcommServerEntry { final BluetoothServerSocket serverSocket; final PendingIntent pendingIntent; BackgroundRfcommServerEntry(UUID uuid, PendingIntent pendingIntent) { this.serverSocket = ShadowBluetoothServerSocket.newInstance( /* type= */ BluetoothSocket.TYPE_RFCOMM, /* auth= */ true, /* encrypt= */ true, new ParcelUuid(uuid)); this.pendingIntent = pendingIntent; } } @ForType(BluetoothAdapter.class) interface BluetoothAdapterReflector { @Static @Direct BluetoothAdapter getDefaultAdapter(); @Direct boolean getProfileProxy( Context context, BluetoothProfile.ServiceListener listener, int profile); @Direct void closeProfileProxy(int profile, BluetoothProfile proxy); @Direct BluetoothDevice getRemoteDevice(String address); @Accessor("sAdapter") @Static void setAdapter(BluetoothAdapter adapter); @Accessor("mBluetoothLeAdvertiser") @Deprecated void setBluetoothLeAdvertiser(BluetoothLeAdvertiser advertiser); @Accessor("sBluetoothLeAdvertiser") @Static void setSBluetoothLeAdvertiser(BluetoothLeAdvertiser advertiser); @Accessor("sBluetoothLeScanner") @Static void setSBluetoothLeScanner(BluetoothLeScanner scanner); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy