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