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