1 package org.robolectric.shadows; 2 3 import static android.bluetooth.BluetoothAdapter.STATE_ON; 4 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; 5 import static android.os.Build.VERSION_CODES.LOLLIPOP; 6 import static android.os.Build.VERSION_CODES.M; 7 import static android.os.Build.VERSION_CODES.O; 8 import static android.os.Build.VERSION_CODES.Q; 9 import static android.os.Build.VERSION_CODES.R; 10 import static android.os.Build.VERSION_CODES.S_V2; 11 import static android.os.Build.VERSION_CODES.TIRAMISU; 12 import static org.robolectric.Shadows.shadowOf; 13 import static org.robolectric.util.reflector.Reflector.reflector; 14 15 import android.app.PendingIntent; 16 import android.app.PendingIntent.CanceledException; 17 import android.bluetooth.BluetoothAdapter; 18 import android.bluetooth.BluetoothAdapter.LeScanCallback; 19 import android.bluetooth.BluetoothDevice; 20 import android.bluetooth.BluetoothProfile; 21 import android.bluetooth.BluetoothServerSocket; 22 import android.bluetooth.BluetoothSocket; 23 import android.bluetooth.BluetoothStatusCodes; 24 import android.bluetooth.IBluetoothManager; 25 import android.bluetooth.le.BluetoothLeAdvertiser; 26 import android.bluetooth.le.BluetoothLeScanner; 27 import android.content.AttributionSource; 28 import android.content.Context; 29 import android.os.Build; 30 import android.os.Build.VERSION_CODES; 31 import android.os.ParcelUuid; 32 import android.provider.Settings; 33 import com.google.common.collect.ImmutableList; 34 import java.io.IOException; 35 import java.time.Duration; 36 import java.util.ArrayList; 37 import java.util.Collections; 38 import java.util.HashMap; 39 import java.util.HashSet; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Set; 43 import java.util.UUID; 44 import java.util.concurrent.ConcurrentHashMap; 45 import java.util.concurrent.ConcurrentMap; 46 import javax.annotation.Nullable; 47 import org.robolectric.RuntimeEnvironment; 48 import org.robolectric.annotation.Implementation; 49 import org.robolectric.annotation.Implements; 50 import org.robolectric.annotation.RealObject; 51 import org.robolectric.annotation.Resetter; 52 import org.robolectric.util.ReflectionHelpers; 53 import org.robolectric.util.ReflectionHelpers.ClassParameter; 54 import org.robolectric.util.reflector.Accessor; 55 import org.robolectric.util.reflector.Direct; 56 import org.robolectric.util.reflector.ForType; 57 import org.robolectric.util.reflector.Static; 58 59 @SuppressWarnings({"UnusedDeclaration"}) 60 @Implements(value = BluetoothAdapter.class, looseSignatures = true) 61 public class ShadowBluetoothAdapter { 62 @RealObject private BluetoothAdapter realAdapter; 63 64 private static final int ADDRESS_LENGTH = 17; 65 private static final int LE_MAXIMUM_ADVERTISING_DATA_LENGTH = 31; 66 private static final int LE_MAXIMUM_ADVERTISING_DATA_LENGTH_EXTENDED = 1650; 67 68 /** 69 * Equivalent value to internal SystemApi {@link 70 * BluetoothStatusCodes#RFCOMM_LISTENER_START_FAILED_UUID_IN_USE}. 71 */ 72 public static final int RFCOMM_LISTENER_START_FAILED_UUID_IN_USE = 2000; 73 74 /** 75 * Equivalent value to internal SystemApi {@link 76 * BluetoothStatusCodes#RFCOMM_LISTENER_OPERATION_FAILED_NO_MATCHING_SERVICE_RECORD}. 77 */ 78 public static final int RFCOMM_LISTENER_OPERATION_FAILED_NO_MATCHING_SERVICE_RECORD = 2001; 79 80 /** 81 * Equivalent value to internal SystemApi {@link 82 * BluetoothStatusCodes#RFCOMM_LISTENER_FAILED_TO_CLOSE_SERVER_SOCKET}. 83 */ 84 public static final int RFCOMM_LISTENER_FAILED_TO_CLOSE_SERVER_SOCKET = 2004; 85 86 private static boolean isBluetoothSupported = true; 87 88 private static final Map<String, BluetoothDevice> deviceCache = new HashMap<>(); 89 private Set<BluetoothDevice> bondedDevices = new HashSet<BluetoothDevice>(); 90 private Set<LeScanCallback> leScanCallbacks = new HashSet<LeScanCallback>(); 91 private boolean isDiscovering; 92 private String address; 93 private int state; 94 private String name = "DefaultBluetoothDeviceName"; 95 private int scanMode = BluetoothAdapter.SCAN_MODE_NONE; 96 private Duration discoverableTimeout; 97 private boolean isBleScanAlwaysAvailable = true; 98 private boolean isMultipleAdvertisementSupported = true; 99 private boolean isLeExtendedAdvertisingSupported = true; 100 private boolean isOverridingProxyBehavior; 101 private final Map<Integer, Integer> profileConnectionStateData = new HashMap<>(); 102 private final Map<Integer, BluetoothProfile> profileProxies = new HashMap<>(); 103 private final ConcurrentMap<UUID, BackgroundRfcommServerEntry> backgroundRfcommServers = 104 new ConcurrentHashMap<>(); 105 private final Map<Integer, List<BluetoothProfile.ServiceListener>> 106 bluetoothProfileServiceListeners = new HashMap<>(); 107 108 @Resetter reset()109 public static void reset() { 110 setIsBluetoothSupported(true); 111 BluetoothAdapterReflector bluetoothReflector = reflector(BluetoothAdapterReflector.class); 112 int apiLevel = RuntimeEnvironment.getApiLevel(); 113 if (apiLevel >= VERSION_CODES.LOLLIPOP && apiLevel <= VERSION_CODES.R) { 114 bluetoothReflector.setSBluetoothLeAdvertiser(null); 115 bluetoothReflector.setSBluetoothLeScanner(null); 116 } 117 bluetoothReflector.setAdapter(null); 118 deviceCache.clear(); 119 } 120 121 @Implementation getDefaultAdapter()122 protected static BluetoothAdapter getDefaultAdapter() { 123 if (!isBluetoothSupported) { 124 return null; 125 } 126 return reflector(BluetoothAdapterReflector.class).getDefaultAdapter(); 127 } 128 129 /** Requires LooseSignatures because of {@link AttributionSource} parameter */ 130 @Implementation(minSdk = VERSION_CODES.TIRAMISU) createAdapter(Object attributionSource)131 protected static Object createAdapter(Object attributionSource) { 132 IBluetoothManager service = ReflectionHelpers.createNullProxy(IBluetoothManager.class); 133 return ReflectionHelpers.callConstructor( 134 BluetoothAdapter.class, 135 ClassParameter.from(IBluetoothManager.class, service), 136 ClassParameter.from(AttributionSource.class, attributionSource)); 137 } 138 139 /** Determines if getDefaultAdapter() returns the default local adapter (true) or null (false). */ setIsBluetoothSupported(boolean supported)140 public static void setIsBluetoothSupported(boolean supported) { 141 isBluetoothSupported = supported; 142 } 143 144 /** @deprecated use real BluetoothLeAdvertiser instead */ 145 @Deprecated setBluetoothLeAdvertiser(BluetoothLeAdvertiser advertiser)146 public void setBluetoothLeAdvertiser(BluetoothLeAdvertiser advertiser) { 147 if (RuntimeEnvironment.getApiLevel() <= VERSION_CODES.LOLLIPOP_MR1) { 148 reflector(BluetoothAdapterReflector.class, realAdapter).setSBluetoothLeAdvertiser(advertiser); 149 } else { 150 reflector(BluetoothAdapterReflector.class, realAdapter).setBluetoothLeAdvertiser(advertiser); 151 } 152 } 153 154 @Implementation getRemoteDevice(String address)155 protected synchronized BluetoothDevice getRemoteDevice(String address) { 156 if (!deviceCache.containsKey(address)) { 157 deviceCache.put( 158 address, 159 reflector(BluetoothAdapterReflector.class, realAdapter).getRemoteDevice(address)); 160 } 161 return deviceCache.get(address); 162 } 163 164 @Implementation getBondedDevices()165 protected Set<BluetoothDevice> getBondedDevices() { 166 return Collections.unmodifiableSet(bondedDevices); 167 } 168 setBondedDevices(Set<BluetoothDevice> bluetoothDevices)169 public void setBondedDevices(Set<BluetoothDevice> bluetoothDevices) { 170 bondedDevices = bluetoothDevices; 171 } 172 173 @Implementation listenUsingInsecureRfcommWithServiceRecord( String serviceName, UUID uuid)174 protected BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord( 175 String serviceName, UUID uuid) { 176 return ShadowBluetoothServerSocket.newInstance( 177 BluetoothSocket.TYPE_RFCOMM, /*auth=*/ false, /*encrypt=*/ false, new ParcelUuid(uuid)); 178 } 179 180 @Implementation listenUsingRfcommWithServiceRecord(String serviceName, UUID uuid)181 protected BluetoothServerSocket listenUsingRfcommWithServiceRecord(String serviceName, UUID uuid) 182 throws IOException { 183 return ShadowBluetoothServerSocket.newInstance( 184 BluetoothSocket.TYPE_RFCOMM, /*auth=*/ false, /*encrypt=*/ true, new ParcelUuid(uuid)); 185 } 186 187 @Implementation(minSdk = Q) listenUsingInsecureL2capChannel()188 protected BluetoothServerSocket listenUsingInsecureL2capChannel() throws IOException { 189 return ShadowBluetoothServerSocket.newInstance( 190 BluetoothSocket.TYPE_L2CAP, /*auth=*/ false, /*encrypt=*/ true, /*uuid=*/ null); 191 } 192 193 @Implementation(minSdk = Q) listenUsingL2capChannel()194 protected BluetoothServerSocket listenUsingL2capChannel() throws IOException { 195 return ShadowBluetoothServerSocket.newInstance( 196 BluetoothSocket.TYPE_L2CAP, /*auth=*/ false, /*encrypt=*/ true, /*uuid=*/ null); 197 } 198 199 @Implementation startDiscovery()200 protected boolean startDiscovery() { 201 isDiscovering = true; 202 return true; 203 } 204 205 @Implementation cancelDiscovery()206 protected boolean cancelDiscovery() { 207 isDiscovering = false; 208 return true; 209 } 210 211 /** When true, overrides the value of {@link #getLeState}. By default, this is false. */ 212 @Implementation(minSdk = M) isBleScanAlwaysAvailable()213 protected boolean isBleScanAlwaysAvailable() { 214 return isBleScanAlwaysAvailable; 215 } 216 217 /** 218 * Decides the correct LE state. When off, BLE calls will fail or return null. 219 * 220 * <p>LE is enabled if either Bluetooth or BLE scans are enabled. LE is always off if Airplane 221 * Mode is enabled. 222 */ 223 @Implementation(minSdk = M) getLeState()224 public int getLeState() { 225 if (isAirplaneMode()) { 226 return BluetoothAdapter.STATE_OFF; 227 } 228 229 if (isEnabled()) { 230 return BluetoothAdapter.STATE_ON; 231 } 232 233 if (isBleScanAlwaysAvailable()) { 234 return BluetoothAdapter.STATE_BLE_ON; 235 } 236 237 return BluetoothAdapter.STATE_OFF; 238 } 239 240 /** 241 * True if either Bluetooth is enabled or BLE scanning is available. Always false if Airplane Mode 242 * is enabled. When false, BLE scans will fail. @Implementation(minSdk = M) protected boolean 243 * isLeEnabled() { if (isAirplaneMode()) { return false; } return isEnabled() || 244 * isBleScanAlwaysAvailable(); } 245 */ isAirplaneMode()246 private static boolean isAirplaneMode() { 247 Context context = RuntimeEnvironment.getApplication(); 248 return Settings.Global.getInt(context.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) 249 != 0; 250 } 251 252 @Implementation(minSdk = JELLY_BEAN_MR2) startLeScan(LeScanCallback callback)253 protected boolean startLeScan(LeScanCallback callback) { 254 return startLeScan(null, callback); 255 } 256 257 @Implementation(minSdk = JELLY_BEAN_MR2) startLeScan(UUID[] serviceUuids, LeScanCallback callback)258 protected boolean startLeScan(UUID[] serviceUuids, LeScanCallback callback) { 259 if (Build.VERSION.SDK_INT >= M && !realAdapter.isLeEnabled()) { 260 return false; 261 } 262 263 // Ignoring the serviceUuids param for now. 264 leScanCallbacks.add(callback); 265 return true; 266 } 267 268 @Implementation(minSdk = JELLY_BEAN_MR2) stopLeScan(LeScanCallback callback)269 protected void stopLeScan(LeScanCallback callback) { 270 leScanCallbacks.remove(callback); 271 } 272 getLeScanCallbacks()273 public Set<LeScanCallback> getLeScanCallbacks() { 274 return Collections.unmodifiableSet(leScanCallbacks); 275 } 276 getSingleLeScanCallback()277 public LeScanCallback getSingleLeScanCallback() { 278 if (leScanCallbacks.size() != 1) { 279 throw new IllegalStateException("There are " + leScanCallbacks.size() + " callbacks"); 280 } 281 return leScanCallbacks.iterator().next(); 282 } 283 284 @Implementation isDiscovering()285 protected boolean isDiscovering() { 286 return isDiscovering; 287 } 288 289 @Implementation isEnabled()290 protected boolean isEnabled() { 291 return state == BluetoothAdapter.STATE_ON; 292 } 293 294 @Implementation enable()295 protected boolean enable() { 296 setState(BluetoothAdapter.STATE_ON); 297 return true; 298 } 299 300 @Implementation disable()301 protected boolean disable() { 302 setState(BluetoothAdapter.STATE_OFF); 303 return true; 304 } 305 306 @Implementation getAddress()307 protected String getAddress() { 308 return this.address; 309 } 310 311 @Implementation getState()312 protected int getState() { 313 return state; 314 } 315 316 @Implementation getName()317 protected String getName() { 318 return name; 319 } 320 321 @Implementation setName(String name)322 protected boolean setName(String name) { 323 this.name = name; 324 return true; 325 } 326 327 /** 328 * Needs looseSignatures because in Android T the return value of this method was changed from 329 * bool to int. 330 */ 331 @Implementation setScanMode(int scanMode)332 protected Object setScanMode(int scanMode) { 333 boolean result = true; 334 if (scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE 335 && scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE 336 && scanMode != BluetoothAdapter.SCAN_MODE_NONE) { 337 result = false; 338 } 339 340 this.scanMode = scanMode; 341 if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.TIRAMISU) { 342 return result ? BluetoothStatusCodes.SUCCESS : BluetoothStatusCodes.ERROR_UNKNOWN; 343 } else { 344 return result; 345 } 346 } 347 348 @Implementation(maxSdk = Q) setScanMode(int scanMode, int discoverableTimeout)349 protected boolean setScanMode(int scanMode, int discoverableTimeout) { 350 setDiscoverableTimeout(discoverableTimeout); 351 return (boolean) setScanMode(scanMode); 352 } 353 354 @Implementation(minSdk = R, maxSdk = S_V2) setScanMode(int scanMode, long durationMillis)355 protected boolean setScanMode(int scanMode, long durationMillis) { 356 int durationSeconds = Math.toIntExact(durationMillis / 1000); 357 setDiscoverableTimeout(durationSeconds); 358 return (boolean) setScanMode(scanMode); 359 } 360 361 @Implementation getScanMode()362 protected int getScanMode() { 363 return scanMode; 364 } 365 366 /** 367 * Needs looseSignatures because the return value changed from {@code int} to {@link Duration} 368 * starting in T. 369 */ 370 @Implementation getDiscoverableTimeout()371 protected Object getDiscoverableTimeout() { 372 if (RuntimeEnvironment.getApiLevel() <= S_V2) { 373 return (int) discoverableTimeout.toSeconds(); 374 } else { 375 return discoverableTimeout; 376 } 377 } 378 379 @Implementation(maxSdk = S_V2) setDiscoverableTimeout(int timeout)380 protected void setDiscoverableTimeout(int timeout) { 381 discoverableTimeout = Duration.ofSeconds(timeout); 382 } 383 384 @Implementation(minSdk = 33) setDiscoverableTimeout(Duration timeout)385 protected int setDiscoverableTimeout(Duration timeout) { 386 if (getState() != STATE_ON) { 387 return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; 388 } 389 if (timeout.toSeconds() > Integer.MAX_VALUE) { 390 throw new IllegalArgumentException( 391 "Timeout in seconds must be less or equal to " + Integer.MAX_VALUE); 392 } 393 this.discoverableTimeout = timeout; 394 return BluetoothStatusCodes.SUCCESS; 395 } 396 397 @Implementation(minSdk = LOLLIPOP) isMultipleAdvertisementSupported()398 protected boolean isMultipleAdvertisementSupported() { 399 return isMultipleAdvertisementSupported; 400 } 401 402 /** 403 * Validate a Bluetooth address, such as "00:43:A8:23:10:F0" Alphabetic characters must be 404 * uppercase to be valid. 405 * 406 * @param address Bluetooth address as string 407 * @return true if the address is valid, false otherwise 408 */ 409 @Implementation checkBluetoothAddress(String address)410 protected static boolean checkBluetoothAddress(String address) { 411 if (address == null || address.length() != ADDRESS_LENGTH) { 412 return false; 413 } 414 for (int i = 0; i < ADDRESS_LENGTH; i++) { 415 char c = address.charAt(i); 416 switch (i % 3) { 417 case 0: 418 case 1: 419 if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) { 420 // hex character, OK 421 break; 422 } 423 return false; 424 case 2: 425 if (c == ':') { 426 break; // OK 427 } 428 return false; 429 } 430 } 431 return true; 432 } 433 434 /** 435 * Returns the connection state for the given Bluetooth {@code profile}, defaulting to {@link 436 * BluetoothProfile.STATE_DISCONNECTED} if the profile's connection state was never set. 437 * 438 * <p>Set a Bluetooth profile's connection state via {@link #setProfileConnectionState(int, int)}. 439 */ 440 @Implementation getProfileConnectionState(int profile)441 protected int getProfileConnectionState(int profile) { 442 Integer state = profileConnectionStateData.get(profile); 443 if (state == null) { 444 return BluetoothProfile.STATE_DISCONNECTED; 445 } 446 return state; 447 } 448 setAddress(String address)449 public void setAddress(String address) { 450 this.address = address; 451 } 452 setState(int state)453 public void setState(int state) { 454 this.state = state; 455 } 456 457 /** @deprecated Use {@link BluetoothAdapter#enable()} or {@link BluetoothAdapter#disable()}. */ 458 @Deprecated setEnabled(boolean enabled)459 public void setEnabled(boolean enabled) { 460 if (enabled) { 461 enable(); 462 } else { 463 disable(); 464 } 465 } 466 467 /** 468 * Sets the value for {@link isBleScanAlwaysAvailable}. If true, {@link getLeState} will always 469 * return true. 470 */ setBleScanAlwaysAvailable(boolean alwaysAvailable)471 public void setBleScanAlwaysAvailable(boolean alwaysAvailable) { 472 isBleScanAlwaysAvailable = alwaysAvailable; 473 } 474 475 /** Sets the value for {@link isMultipleAdvertisementSupported}. */ setIsMultipleAdvertisementSupported(boolean supported)476 public void setIsMultipleAdvertisementSupported(boolean supported) { 477 isMultipleAdvertisementSupported = supported; 478 } 479 480 /** Sets the connection state {@code state} for the given BluetoothProfile {@code profile} */ setProfileConnectionState(int profile, int state)481 public void setProfileConnectionState(int profile, int state) { 482 profileConnectionStateData.put(profile, state); 483 } 484 485 /** 486 * Sets the active BluetoothProfile {@code proxy} for the given {@code profile}. Will always 487 * affect behavior of {@link BluetoothAdapter#getProfileProxy} and {@link 488 * BluetoothAdapter#closeProfileProxy}. Call to {@link BluetoothAdapter#closeProfileProxy} can 489 * remove the set active proxy. 490 * 491 * @param proxy can be 'null' to simulate the situation where {@link 492 * BluetoothAdapter#getProfileProxy} would return 'false'. This can happen on older Android 493 * versions for Bluetooth profiles introduced in later Android versions. 494 */ setProfileProxy(int profile, @Nullable BluetoothProfile proxy)495 public void setProfileProxy(int profile, @Nullable BluetoothProfile proxy) { 496 isOverridingProxyBehavior = true; 497 if (proxy != null) { 498 profileProxies.put(profile, proxy); 499 } 500 } 501 502 /** 503 * @return 'true' if active (non-null) proxy has been set by {@link 504 * ShadowBluetoothAdapter#setProfileProxy} for the given {@code profile} AND it has not been 505 * "deactivated" by a call to {@link BluetoothAdapter#closeProfileProxy}. Only meaningful if 506 * {@link ShadowBluetoothAdapter#setProfileProxy} has been previously called. 507 */ hasActiveProfileProxy(int profile)508 public boolean hasActiveProfileProxy(int profile) { 509 return profileProxies.get(profile) != null; 510 } 511 512 /** 513 * Overrides behavior of {@link getProfileProxy} if {@link ShadowBluetoothAdapter#setProfileProxy} 514 * has been previously called. 515 * 516 * <p>If active (non-null) proxy has been set by {@link setProfileProxy} for the given {@code 517 * profile}, {@link getProfileProxy} will immediately call {@code onServiceConnected} of the given 518 * BluetoothProfile.ServiceListener {@code listener}. 519 * 520 * @return 'true' if a proxy object has been set by {@link setProfileProxy} for the given 521 * BluetoothProfile {@code profile} 522 */ 523 @Implementation getProfileProxy( Context context, BluetoothProfile.ServiceListener listener, int profile)524 protected boolean getProfileProxy( 525 Context context, BluetoothProfile.ServiceListener listener, int profile) { 526 if (!isOverridingProxyBehavior) { 527 return reflector(BluetoothAdapterReflector.class, realAdapter) 528 .getProfileProxy(context, listener, profile); 529 } 530 531 BluetoothProfile proxy = profileProxies.get(profile); 532 if (proxy == null) { 533 return false; 534 } else { 535 listener.onServiceConnected(profile, proxy); 536 List<BluetoothProfile.ServiceListener> profileListeners = 537 bluetoothProfileServiceListeners.get(profile); 538 if (profileListeners != null) { 539 profileListeners.add(listener); 540 } else { 541 bluetoothProfileServiceListeners.put(profile, new ArrayList<>(ImmutableList.of(listener))); 542 } 543 return true; 544 } 545 } 546 547 /** 548 * Overrides behavior of {@link closeProfileProxy} if {@link 549 * ShadowBluetoothAdapter#setProfileProxy} has been previously called. 550 * 551 * If the given non-null BluetoothProfile {@code proxy} was previously set for the given {@code 552 * profile} by {@link ShadowBluetoothAdapter#setProfileProxy}, this proxy will be "deactivated". 553 */ 554 @Implementation closeProfileProxy(int profile, BluetoothProfile proxy)555 protected void closeProfileProxy(int profile, BluetoothProfile proxy) { 556 if (!isOverridingProxyBehavior) { 557 reflector(BluetoothAdapterReflector.class, realAdapter).closeProfileProxy(profile, proxy); 558 return; 559 } 560 561 if (proxy != null && proxy.equals(profileProxies.get(profile))) { 562 profileProxies.remove(profile); 563 List<BluetoothProfile.ServiceListener> profileListeners = 564 bluetoothProfileServiceListeners.remove(profile); 565 if (profileListeners != null) { 566 for (BluetoothProfile.ServiceListener listener : profileListeners) { 567 listener.onServiceDisconnected(profile); 568 } 569 } 570 } 571 } 572 573 /** Returns the last value of {@link #setIsLeExtendedAdvertisingSupported}, defaulting to true. */ 574 @Implementation(minSdk = O) isLeExtendedAdvertisingSupported()575 protected boolean isLeExtendedAdvertisingSupported() { 576 return isLeExtendedAdvertisingSupported; 577 } 578 579 /** 580 * Sets the isLeExtendedAdvertisingSupported to enable/disable LE extended advertisements feature 581 */ setIsLeExtendedAdvertisingSupported(boolean supported)582 public void setIsLeExtendedAdvertisingSupported(boolean supported) { 583 isLeExtendedAdvertisingSupported = supported; 584 } 585 586 @Implementation(minSdk = O) getLeMaximumAdvertisingDataLength()587 protected int getLeMaximumAdvertisingDataLength() { 588 return isLeExtendedAdvertisingSupported 589 ? LE_MAXIMUM_ADVERTISING_DATA_LENGTH_EXTENDED 590 : LE_MAXIMUM_ADVERTISING_DATA_LENGTH; 591 } 592 593 @Implementation(minSdk = TIRAMISU) startRfcommServer(String name, UUID uuid, PendingIntent pendingIntent)594 protected int startRfcommServer(String name, UUID uuid, PendingIntent pendingIntent) { 595 // PendingIntent#isImmutable throws an NPE if the component does not exist, so verify directly 596 // against the flags for now. 597 if ((shadowOf(pendingIntent).getFlags() & PendingIntent.FLAG_IMMUTABLE) == 0) { 598 throw new IllegalArgumentException("RFCOMM server PendingIntent must be marked immutable"); 599 } 600 601 boolean[] isNewServerSocket = {false}; 602 backgroundRfcommServers.computeIfAbsent( 603 uuid, 604 unused -> { 605 isNewServerSocket[0] = true; 606 return new BackgroundRfcommServerEntry(uuid, pendingIntent); 607 }); 608 return isNewServerSocket[0] 609 ? BluetoothStatusCodes.SUCCESS 610 : RFCOMM_LISTENER_START_FAILED_UUID_IN_USE; 611 } 612 613 @Implementation(minSdk = TIRAMISU) stopRfcommServer(UUID uuid)614 protected int stopRfcommServer(UUID uuid) { 615 BackgroundRfcommServerEntry entry = backgroundRfcommServers.remove(uuid); 616 617 if (entry == null) { 618 return RFCOMM_LISTENER_OPERATION_FAILED_NO_MATCHING_SERVICE_RECORD; 619 } 620 621 try { 622 entry.serverSocket.close(); 623 return BluetoothStatusCodes.SUCCESS; 624 } catch (IOException e) { 625 return RFCOMM_LISTENER_FAILED_TO_CLOSE_SERVER_SOCKET; 626 } 627 } 628 629 @Implementation(minSdk = TIRAMISU) 630 @Nullable retrieveConnectedRfcommSocket(UUID uuid)631 protected BluetoothSocket retrieveConnectedRfcommSocket(UUID uuid) { 632 BackgroundRfcommServerEntry serverEntry = backgroundRfcommServers.get(uuid); 633 634 try { 635 return serverEntry == null ? null : serverEntry.serverSocket.accept(/* timeout= */ 0); 636 } catch (IOException e) { 637 // This means there is no pending socket, so the contract indicates we should return null. 638 return null; 639 } 640 } 641 642 /** 643 * Creates an incoming socket connection from the given {@link BluetoothDevice} to a background 644 * Bluetooth server created with {@link BluetoothAdapter#startRfcommServer(String, UUID, 645 * PendingIntent)} on the given uuid. 646 * 647 * <p>Creating this socket connection will invoke the {@link PendingIntent} provided in {@link 648 * BluetoothAdapter#startRfcommServer(String, UUID, PendingIntent)} when the server socket was 649 * created for the given UUID. The component provided in the intent can then call {@link 650 * BluetoothAdapter#retrieveConnectedRfcommSocket(UUID)} to obtain the server side socket. 651 * 652 * <p>A {@link ShadowBluetoothSocket} obtained from the returned {@link BluetoothSocket} can be 653 * used to send data to and receive data from the server side socket. This returned {@link 654 * BluetoothSocket} is the same socket as returned by {@link 655 * BluetoothAdapter#retrieveConnectedRfcommSocket(UUID)} and should generally not be used directly 656 * outside of obtaining the shadow, as this socket is normally not exposed outside of the 657 * component started by the pending intent. {@link ShadowBluetoothSocket#getInputStreamFeeder()} 658 * and {@link ShadowBluetoothSocket#getOutputStreamSink()} can be used to send data to and from 659 * the socket as if it was a remote connection. 660 * 661 * <p><b>Warning:</b> The socket returned by this method and the corresponding server side socket 662 * retrieved from {@link BluetoothAdapter#retrieveConnectedRfcommSocket(UUID)} do not support 663 * reads and writes from different threads. Once reading or writing is started for a given socket 664 * on a given thread, that type of operation on that socket must only be done on that thread. 665 * 666 * @return a server side BluetoothSocket or {@code null} if the {@link UUID} is not registered. 667 * This value should generally not be used directly, and is mainly used to obtain a shadow 668 * with which a RFCOMM client can be simulated. 669 * @throws IllegalArgumentException if a server is not started for the given {@link UUID}. 670 * @throws CanceledException if the pending intent for the server socket was cancelled. 671 */ addIncomingRfcommConnection(BluetoothDevice remoteDevice, UUID uuid)672 public BluetoothSocket addIncomingRfcommConnection(BluetoothDevice remoteDevice, UUID uuid) 673 throws CanceledException { 674 BackgroundRfcommServerEntry entry = backgroundRfcommServers.get(uuid); 675 if (entry == null) { 676 throw new IllegalArgumentException("No RFCOMM server open for UUID: " + uuid); 677 } 678 679 BluetoothSocket socket = shadowOf(entry.serverSocket).deviceConnected(remoteDevice); 680 entry.pendingIntent.send(); 681 682 return socket; 683 } 684 685 /** 686 * Returns an immutable set of {@link UUID}s representing the currently registered RFCOMM servers. 687 */ 688 @SuppressWarnings("JdkImmutableCollections") getRegisteredRfcommServerUuids()689 public Set<UUID> getRegisteredRfcommServerUuids() { 690 return Set.of(backgroundRfcommServers.keySet().toArray(new UUID[0])); 691 } 692 693 private static final class BackgroundRfcommServerEntry { 694 final BluetoothServerSocket serverSocket; 695 final PendingIntent pendingIntent; 696 BackgroundRfcommServerEntry(UUID uuid, PendingIntent pendingIntent)697 BackgroundRfcommServerEntry(UUID uuid, PendingIntent pendingIntent) { 698 this.serverSocket = 699 ShadowBluetoothServerSocket.newInstance( 700 /* type= */ BluetoothSocket.TYPE_RFCOMM, 701 /* auth= */ true, 702 /* encrypt= */ true, 703 new ParcelUuid(uuid)); 704 this.pendingIntent = pendingIntent; 705 } 706 } 707 708 @ForType(BluetoothAdapter.class) 709 interface BluetoothAdapterReflector { 710 711 @Static 712 @Direct getDefaultAdapter()713 BluetoothAdapter getDefaultAdapter(); 714 715 @Direct getProfileProxy( Context context, BluetoothProfile.ServiceListener listener, int profile)716 boolean getProfileProxy( 717 Context context, BluetoothProfile.ServiceListener listener, int profile); 718 719 @Direct closeProfileProxy(int profile, BluetoothProfile proxy)720 void closeProfileProxy(int profile, BluetoothProfile proxy); 721 722 @Direct getRemoteDevice(String address)723 BluetoothDevice getRemoteDevice(String address); 724 725 @Accessor("sAdapter") 726 @Static setAdapter(BluetoothAdapter adapter)727 void setAdapter(BluetoothAdapter adapter); 728 729 @Accessor("mBluetoothLeAdvertiser") 730 @Deprecated setBluetoothLeAdvertiser(BluetoothLeAdvertiser advertiser)731 void setBluetoothLeAdvertiser(BluetoothLeAdvertiser advertiser); 732 733 @Accessor("sBluetoothLeAdvertiser") 734 @Static setSBluetoothLeAdvertiser(BluetoothLeAdvertiser advertiser)735 void setSBluetoothLeAdvertiser(BluetoothLeAdvertiser advertiser); 736 737 @Accessor("sBluetoothLeScanner") 738 @Static setSBluetoothLeScanner(BluetoothLeScanner scanner)739 void setSBluetoothLeScanner(BluetoothLeScanner scanner); 740 } 741 } 742