• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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