1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.bluetooth.cts;
18 
19 import static android.Manifest.permission.BLUETOOTH_CONNECT;
20 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
21 import static android.Manifest.permission.BLUETOOTH_SCAN;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertNotNull;
26 import static org.junit.Assert.assertNotSame;
27 import static org.junit.Assert.assertNull;
28 import static org.junit.Assert.assertThrows;
29 import static org.junit.Assert.assertTrue;
30 import static org.junit.Assume.assumeTrue;
31 import static org.mockito.Mockito.mock;
32 
33 import android.app.UiAutomation;
34 import android.bluetooth.BluetoothActivityEnergyInfo;
35 import android.bluetooth.BluetoothAdapter;
36 import android.bluetooth.BluetoothDevice;
37 import android.bluetooth.BluetoothManager;
38 import android.bluetooth.BluetoothProfile;
39 import android.bluetooth.BluetoothQualityReport;
40 import android.bluetooth.BluetoothServerSocket;
41 import android.bluetooth.BluetoothStatusCodes;
42 import android.bluetooth.test_utils.Permissions;
43 import android.content.BroadcastReceiver;
44 import android.content.Context;
45 import android.content.Intent;
46 import android.content.IntentFilter;
47 import android.content.pm.PackageManager;
48 import android.os.Build;
49 import android.os.Bundle;
50 import android.os.SystemProperties;
51 import android.platform.test.annotations.RequiresFlagsEnabled;
52 import android.platform.test.flag.junit.CheckFlagsRule;
53 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
54 import android.util.Log;
55 
56 import androidx.test.ext.junit.runners.AndroidJUnit4;
57 import androidx.test.filters.MediumTest;
58 import androidx.test.platform.app.InstrumentationRegistry;
59 
60 import com.android.bluetooth.flags.Flags;
61 import com.android.compatibility.common.util.ApiLevelUtil;
62 
63 import org.junit.After;
64 import org.junit.Before;
65 import org.junit.Rule;
66 import org.junit.Test;
67 import org.junit.runner.RunWith;
68 
69 import java.io.IOException;
70 import java.time.Duration;
71 import java.util.List;
72 import java.util.Set;
73 import java.util.UUID;
74 import java.util.concurrent.Executor;
75 import java.util.concurrent.TimeUnit;
76 import java.util.concurrent.locks.Condition;
77 import java.util.concurrent.locks.ReentrantLock;
78 
79 /** Very basic test, just of the static methods of {@link BluetoothAdapter}. */
80 @RunWith(AndroidJUnit4.class)
81 @MediumTest
82 public class BluetoothAdapterTest {
83     private static final String TAG = "BluetoothAdapterTest";
84     private static final int SET_NAME_TIMEOUT = 5000; // ms timeout for setting adapter name
85     private static final String ENABLE_DUAL_MODE_AUDIO =
86             "persist.bluetooth.enable_dual_mode_audio";
87 
88     @Rule
89     public final CheckFlagsRule mCheckFlagsRule =
90             DeviceFlagsValueProvider.createCheckFlagsRule();
91 
92     private Context mContext;
93     private boolean mHasBluetooth;
94     private ReentrantLock mAdapterNameChangedlock;
95     private Condition mConditionAdapterNameChanged;
96     private boolean mIsAdapterNameChanged;
97 
98     private BluetoothAdapter mAdapter;
99     private UiAutomation mUiAutomation;
100 
101     @Before
setUp()102     public void setUp() {
103         mContext = InstrumentationRegistry.getInstrumentation().getContext();
104         mHasBluetooth = mContext.getPackageManager().hasSystemFeature(
105                 PackageManager.FEATURE_BLUETOOTH);
106         if (mHasBluetooth) {
107             mAdapter = mContext.getSystemService(BluetoothManager.class).getAdapter();
108             assertNotNull(mAdapter);
109             mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
110             mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
111         }
112         mAdapterNameChangedlock = new ReentrantLock();
113         mConditionAdapterNameChanged = mAdapterNameChangedlock.newCondition();
114         mIsAdapterNameChanged = false;
115     }
116 
117     @After
tearDown()118     public void tearDown() {
119         if (mHasBluetooth) {
120             mUiAutomation.dropShellPermissionIdentity();
121         }
122     }
123 
124     @Test
getDefaultAdapter()125     public void getDefaultAdapter() {
126         /*
127          * Note: If the target doesn't support Bluetooth at all, then
128          * this method should return null.
129          */
130         if (mHasBluetooth) {
131             assertNotNull(BluetoothAdapter.getDefaultAdapter());
132         } else {
133             assertNull(BluetoothAdapter.getDefaultAdapter());
134         }
135     }
136 
137     @Test
checkBluetoothAddress()138     public void checkBluetoothAddress() {
139         // Can't be null.
140         assertFalse(BluetoothAdapter.checkBluetoothAddress(null));
141 
142         // Must be 17 characters long.
143         assertFalse(BluetoothAdapter.checkBluetoothAddress(""));
144         assertFalse(BluetoothAdapter.checkBluetoothAddress("0"));
145         assertFalse(BluetoothAdapter.checkBluetoothAddress("00"));
146         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:"));
147         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:0"));
148         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00"));
149         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:"));
150         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:0"));
151         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00"));
152         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:"));
153         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:0"));
154         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00"));
155         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:"));
156         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:0"));
157         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00"));
158         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00:"));
159         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00:0"));
160 
161         // Must have colons between octets.
162         assertFalse(BluetoothAdapter.checkBluetoothAddress("00x00:00:00:00:00"));
163         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00.00:00:00:00"));
164         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00-00:00:00"));
165         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00900:00"));
166         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00?00"));
167 
168         // Hex letters must be uppercase.
169         assertFalse(BluetoothAdapter.checkBluetoothAddress("a0:00:00:00:00:00"));
170         assertFalse(BluetoothAdapter.checkBluetoothAddress("0b:00:00:00:00:00"));
171         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:c0:00:00:00:00"));
172         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:0d:00:00:00:00"));
173         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:e0:00:00:00"));
174         assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:0f:00:00:00"));
175 
176         assertTrue(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00:00"));
177         assertTrue(BluetoothAdapter.checkBluetoothAddress("12:34:56:78:9A:BC"));
178         assertTrue(BluetoothAdapter.checkBluetoothAddress("DE:F0:FE:DC:B8:76"));
179     }
180 
181     /** Checks enable(), disable(), getState(), isEnabled() */
182     @Test
enableDisable()183     public void enableDisable() {
184         assumeTrue(mHasBluetooth);
185 
186         for (int i = 0; i < 5; i++) {
187             assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
188             assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
189         }
190     }
191 
192     @Test
getAddress()193     public void getAddress() {
194         assumeTrue(mHasBluetooth);
195 
196         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
197         assertTrue(BluetoothAdapter.checkBluetoothAddress(mAdapter.getAddress()));
198 
199         mUiAutomation.dropShellPermissionIdentity();
200         assertThrows(SecurityException.class, () -> mAdapter.getAddress());
201 
202     }
203 
204     @Test
setName_getName()205     public void setName_getName() {
206         assumeTrue(mHasBluetooth);
207 
208         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
209 
210         IntentFilter filter = new IntentFilter();
211         filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
212         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
213         mContext.registerReceiver(mAdapterNameChangeReceiver, filter);
214 
215         String name = mAdapter.getName();
216         assertNotNull(name);
217 
218         // Check renaming the adapter
219         String genericName = "Generic Device 1";
220         mIsAdapterNameChanged = false;
221         assertTrue(mAdapter.setName(genericName));
222         assertTrue(waitForAdapterNameChange());
223         mIsAdapterNameChanged = false;
224         assertEquals(genericName, mAdapter.getName());
225 
226         // Check setting adapter back to original name
227         assertTrue(mAdapter.setName(name));
228         assertTrue(waitForAdapterNameChange());
229         mIsAdapterNameChanged = false;
230         assertEquals(name, mAdapter.getName());
231 
232         mUiAutomation.dropShellPermissionIdentity();
233         assertThrows(SecurityException.class, () -> mAdapter.setName("The name"));
234         assertThrows(SecurityException.class, () -> mAdapter.getName());
235     }
236 
237     @Test
getBondedDevices()238     public void getBondedDevices() {
239         assumeTrue(mHasBluetooth);
240 
241         assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
242 
243         // empty value is returned when Bluetooth is disabled
244         Set<BluetoothDevice> devices = mAdapter.getBondedDevices();
245         assertNotNull(devices);
246         assertTrue(devices.isEmpty());
247 
248         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
249         devices = mAdapter.getBondedDevices();
250         assertNotNull(devices);
251         for (BluetoothDevice device : devices) {
252             assertTrue(BluetoothAdapter.checkBluetoothAddress(device.getAddress()));
253         }
254 
255         mUiAutomation.dropShellPermissionIdentity();
256         assertThrows(SecurityException.class, () -> mAdapter.getBondedDevices());
257 
258     }
259 
260     @Test
getProfileConnectionState()261     public void getProfileConnectionState() {
262         assumeTrue(mHasBluetooth);
263 
264         mUiAutomation.dropShellPermissionIdentity();
265         // getProfileConnectionState is caching it's return value and cts test doesn't know how to
266         // deal with it
267         // assertThrows(SecurityException.class,
268         //         () -> mAdapter.getProfileConnectionState(BluetoothProfile.A2DP));
269         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
270         assertEquals(mAdapter.getProfileConnectionState(BluetoothProfile.A2DP),
271                 BluetoothAdapter.STATE_DISCONNECTED);
272         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
273         assertEquals(mAdapter.getProfileConnectionState(BluetoothProfile.A2DP),
274                 BluetoothAdapter.STATE_DISCONNECTED);
275     }
276 
277     @Test
getRemoteDevice()278     public void getRemoteDevice() {
279         assumeTrue(mHasBluetooth);
280 
281         // getRemoteDevice() should work even with Bluetooth disabled
282         assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
283         mUiAutomation.dropShellPermissionIdentity();
284 
285         // test bad addresses
286         assertThrows(IllegalArgumentException.class, () -> mAdapter.getRemoteDevice((String) null));
287         assertThrows(IllegalArgumentException.class, () ->
288                 mAdapter.getRemoteDevice("00:00:00:00:00:00:00:00"));
289         assertThrows(IllegalArgumentException.class, () -> mAdapter.getRemoteDevice((byte[]) null));
290         assertThrows(IllegalArgumentException.class, () ->
291                 mAdapter.getRemoteDevice(new byte[] {0x00, 0x00, 0x00, 0x00, 0x00}));
292 
293         // test success
294         BluetoothDevice device = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
295         assertNotNull(device);
296         assertEquals("00:11:22:AA:BB:CC", device.getAddress());
297         device = mAdapter.getRemoteDevice(
298                 new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06});
299         assertNotNull(device);
300         assertEquals("01:02:03:04:05:06", device.getAddress());
301     }
302 
303     @Test
getRemoteLeDevice()304     public void getRemoteLeDevice() {
305         assumeTrue(mHasBluetooth);
306 
307         // getRemoteLeDevice() should work even with Bluetooth disabled
308         assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
309         mUiAutomation.dropShellPermissionIdentity();
310 
311         // test bad addresses
312         assertThrows(IllegalArgumentException.class,
313                 () -> mAdapter.getRemoteLeDevice((String) null,
314                                                  BluetoothDevice.ADDRESS_TYPE_PUBLIC));
315         assertThrows(IllegalArgumentException.class,
316                 () -> mAdapter.getRemoteLeDevice("01:02:03:04:05:06:07:08",
317                                                  BluetoothDevice.ADDRESS_TYPE_PUBLIC));
318         assertThrows(IllegalArgumentException.class,
319                 () -> mAdapter.getRemoteLeDevice("01:02:03:04:05",
320                                                  BluetoothDevice.ADDRESS_TYPE_PUBLIC));
321         assertThrows(IllegalArgumentException.class,
322                 () -> mAdapter.getRemoteLeDevice("00:01:02:03:04:05",
323                                                  BluetoothDevice.ADDRESS_TYPE_RANDOM + 1));
324         assertThrows(IllegalArgumentException.class,
325                 () -> mAdapter.getRemoteLeDevice("00:01:02:03:04:05",
326                                                  BluetoothDevice.ADDRESS_TYPE_PUBLIC - 1));
327 
328         // test success
329         BluetoothDevice device = mAdapter.getRemoteLeDevice("00:11:22:AA:BB:CC",
330                 BluetoothDevice.ADDRESS_TYPE_PUBLIC);
331         assertNotNull(device);
332         assertEquals("00:11:22:AA:BB:CC", device.getAddress());
333         device = mAdapter.getRemoteLeDevice("01:02:03:04:05:06",
334                 BluetoothDevice.ADDRESS_TYPE_RANDOM);
335         assertNotNull(device);
336         assertEquals("01:02:03:04:05:06", device.getAddress());
337     }
338 
339     @Test
isLeAudioSupported()340     public void isLeAudioSupported() throws IOException {
341         assumeTrue(mHasBluetooth);
342 
343         assertNotSame(BluetoothStatusCodes.ERROR_UNKNOWN, mAdapter.isLeAudioSupported());
344     }
345 
346     @Test
isLeAudioBroadcastSourceSupported()347     public void isLeAudioBroadcastSourceSupported() throws IOException {
348         assumeTrue(mHasBluetooth);
349 
350         assertNotSame(BluetoothStatusCodes.ERROR_UNKNOWN,
351                 mAdapter.isLeAudioBroadcastSourceSupported());
352     }
353 
354     @Test
isLeAudioBroadcastAssistantSupported()355     public void isLeAudioBroadcastAssistantSupported() throws IOException {
356         assumeTrue(mHasBluetooth);
357 
358         assertNotSame(BluetoothStatusCodes.ERROR_UNKNOWN,
359                 mAdapter.isLeAudioBroadcastAssistantSupported());
360     }
361 
362     @Test
isDistanceMeasurementSupported()363     public void isDistanceMeasurementSupported() throws IOException {
364         assumeTrue(mHasBluetooth);
365 
366         TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
367         assertNotSame(BluetoothStatusCodes.ERROR_UNKNOWN,
368                 mAdapter.isDistanceMeasurementSupported());
369         TestUtils.dropPermissionAsShellUid();
370     }
371 
372     @Test
getMaxConnectedAudioDevices()373     public void getMaxConnectedAudioDevices() {
374         assumeTrue(mHasBluetooth);
375 
376         // Defined in com.android.bluetooth.btservice.AdapterProperties
377         int maxConnectedAudioDevicesLowerBound = 1;
378         // Defined in com.android.bluetooth.btservice.AdapterProperties
379         int maxConnectedAudioDevicesUpperBound = 5;
380 
381         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
382         assertTrue(mAdapter.getMaxConnectedAudioDevices() >= maxConnectedAudioDevicesLowerBound);
383         assertTrue(mAdapter.getMaxConnectedAudioDevices() <= maxConnectedAudioDevicesUpperBound);
384 
385         mUiAutomation.dropShellPermissionIdentity();
386         assertThrows(SecurityException.class, () -> mAdapter.getMaxConnectedAudioDevices());
387     }
388 
389     @Test
listenUsingRfcommWithServiceRecord()390     public void listenUsingRfcommWithServiceRecord() throws IOException {
391         assumeTrue(mHasBluetooth);
392 
393         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
394         BluetoothServerSocket socket = mAdapter.listenUsingRfcommWithServiceRecord(
395                 "test", UUID.randomUUID());
396         assertNotNull(socket);
397         socket.close();
398 
399         mUiAutomation.dropShellPermissionIdentity();
400         assertThrows(SecurityException.class, () -> mAdapter.listenUsingRfcommWithServiceRecord(
401                     "test", UUID.randomUUID()));
402     }
403 
404     @Test
discoverableTimeout()405     public void discoverableTimeout() {
406         assumeTrue(mHasBluetooth);
407 
408         Duration minutes = Duration.ofMinutes(2);
409 
410         assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
411         assertEquals(null, mAdapter.getDiscoverableTimeout());
412         assertEquals(BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED,
413                 mAdapter.setDiscoverableTimeout(minutes));
414 
415         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
416         TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
417         assertThrows(IllegalArgumentException.class, () -> mAdapter.setDiscoverableTimeout(
418                 Duration.ofDays(25000)));
419         Permissions.enforceEachPermissions(
420                 () -> mAdapter.setDiscoverableTimeout(minutes),
421                 List.of(BLUETOOTH_PRIVILEGED, BLUETOOTH_SCAN));
422         try (var p = Permissions.withPermissions(BLUETOOTH_SCAN, BLUETOOTH_PRIVILEGED)) {
423             assertEquals(BluetoothStatusCodes.SUCCESS,
424                     mAdapter.setDiscoverableTimeout(minutes));
425             assertEquals(minutes, mAdapter.getDiscoverableTimeout());
426         }
427     }
428 
429     @Test
getConnectionState()430     public void getConnectionState() {
431         assumeTrue(mHasBluetooth);
432 
433         // Verify return value if Bluetooth is not enabled
434         assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
435         assertEquals(BluetoothProfile.STATE_DISCONNECTED, mAdapter.getConnectionState());
436     }
437 
438     @Test
getMostRecentlyConnectedDevices()439     public void getMostRecentlyConnectedDevices() {
440         assumeTrue(mHasBluetooth);
441 
442         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
443 
444         // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
445         assertThrows(SecurityException.class, () -> mAdapter.getMostRecentlyConnectedDevices());
446 
447         // Verify return value if Bluetooth is not enabled
448         assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
449         List<BluetoothDevice> devices = mAdapter.getMostRecentlyConnectedDevices();
450         assertTrue(devices.isEmpty());
451     }
452 
453     @Test
getUuids()454     public void getUuids() {
455         assumeTrue(mHasBluetooth);
456 
457         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
458 
459         // Verify return value without permission.BLUETOOTH_CONNECT
460         mUiAutomation.dropShellPermissionIdentity();
461         assertThrows(SecurityException.class, () -> mAdapter.getUuidsList());
462         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
463 
464         assertNotNull(mAdapter.getUuidsList());
465         assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
466 
467         // Verify return value if Bluetooth is not enabled
468         assertEquals(0, mAdapter.getUuidsList().size());
469 
470     }
471 
472     @Test
nameForState()473     public void nameForState() {
474         assertEquals("ON", BluetoothAdapter.nameForState(BluetoothAdapter.STATE_ON));
475         assertEquals("OFF", BluetoothAdapter.nameForState(BluetoothAdapter.STATE_OFF));
476         assertEquals("TURNING_ON",
477                 BluetoothAdapter.nameForState(BluetoothAdapter.STATE_TURNING_ON));
478         assertEquals("TURNING_OFF",
479                 BluetoothAdapter.nameForState(BluetoothAdapter.STATE_TURNING_OFF));
480 
481         assertEquals("BLE_ON", BluetoothAdapter.nameForState(BluetoothAdapter.STATE_BLE_ON));
482 
483         // Check value before state range
484         for (int state = 0; state < BluetoothAdapter.STATE_OFF; state++) {
485             assertEquals("?!?!? (" + state + ")", BluetoothAdapter.nameForState(state));
486         }
487         // Check value after state range (skip TURNING_OFF)
488         for (int state = BluetoothAdapter.STATE_BLE_ON + 2; state < 100; state++) {
489             assertEquals("?!?!? (" + state + ")", BluetoothAdapter.nameForState(state));
490         }
491     }
492 
493     @Test
BluetoothConnectionCallback_disconnectReasonText()494     public void BluetoothConnectionCallback_disconnectReasonText() {
495         assertEquals("Reason unknown", BluetoothAdapter.BluetoothConnectionCallback
496                 .disconnectReasonToString(BluetoothStatusCodes.ERROR_UNKNOWN));
497     }
498 
499     @Test
registerBluetoothConnectionCallback()500     public void registerBluetoothConnectionCallback() {
501         assumeTrue(mHasBluetooth);
502 
503         Executor executor = mContext.getMainExecutor();
504         BluetoothAdapter.BluetoothConnectionCallback callback =
505                 mock(BluetoothAdapter.BluetoothConnectionCallback.class);
506 
507         // placeholder call for coverage
508         callback.onDeviceConnected(null);
509         callback.onDeviceDisconnected(null, BluetoothStatusCodes.ERROR_UNKNOWN);
510 
511         // Verify parameter
512         assertFalse(mAdapter.registerBluetoothConnectionCallback(null, callback));
513         assertFalse(mAdapter.registerBluetoothConnectionCallback(executor, null));
514         assertFalse(mAdapter.unregisterBluetoothConnectionCallback(null));
515 
516         try (var p = Permissions.withPermissions(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED)) {
517             assertTrue(mAdapter.registerBluetoothConnectionCallback(executor, callback));
518             assertTrue(mAdapter.unregisterBluetoothConnectionCallback(callback));
519         }
520     }
521 
522     @Test
requestControllerActivityEnergyInfo()523     public void requestControllerActivityEnergyInfo() {
524         assumeTrue(mHasBluetooth);
525 
526         BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback callback =
527                 new BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback() {
528                     @Override
529                     public void onBluetoothActivityEnergyInfoAvailable(
530                             BluetoothActivityEnergyInfo info) {
531                         assertNotNull(info);
532                     }
533 
534                     @Override
535                     public void onBluetoothActivityEnergyInfoError(int errorCode) {}
536                 };
537 
538         // Verify parameter
539         assertThrows(NullPointerException.class,
540                 () -> mAdapter.requestControllerActivityEnergyInfo(null, callback));
541     }
542 
543     @Test
clearBluetooth()544     public void clearBluetooth() {
545         assumeTrue(mHasBluetooth);
546 
547         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
548 
549         // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
550         assertThrows(SecurityException.class, () -> mAdapter.clearBluetooth());
551         mUiAutomation.dropShellPermissionIdentity();
552         // Verify throws SecurityException without permission.BLUETOOTH_CONNECT
553         assertThrows(SecurityException.class, () -> mAdapter.clearBluetooth());
554 
555         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
556         assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
557         // Verify throws RuntimeException when trying to save sysprop for later (permission denied)
558         assertThrows(RuntimeException.class, () -> mAdapter.clearBluetooth());
559     }
560 
561     @Test
BluetoothProfile_getConnectionStateName()562     public void BluetoothProfile_getConnectionStateName() {
563         assumeTrue(mHasBluetooth);
564 
565         assertEquals("STATE_DISCONNECTED",
566                 BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_DISCONNECTED));
567         assertEquals("STATE_CONNECTED",
568                 BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_CONNECTED));
569         assertEquals("STATE_CONNECTING",
570                 BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_CONNECTING));
571         assertEquals("STATE_CONNECTED",
572                 BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_CONNECTED));
573         assertEquals("STATE_DISCONNECTING",
574                 BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_DISCONNECTING));
575         assertEquals("STATE_UNKNOWN",
576                 BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_DISCONNECTING + 1));
577     }
578 
579     @Test
BluetoothProfile_getProfileName()580     public void BluetoothProfile_getProfileName() {
581         assertEquals("HEADSET",
582                 BluetoothProfile.getProfileName(BluetoothProfile.HEADSET));
583         assertEquals("A2DP",
584                 BluetoothProfile.getProfileName(BluetoothProfile.A2DP));
585         assertEquals("HID_HOST",
586                 BluetoothProfile.getProfileName(BluetoothProfile.HID_HOST));
587         assertEquals("PAN",
588                 BluetoothProfile.getProfileName(BluetoothProfile.PAN));
589         assertEquals("PBAP",
590                 BluetoothProfile.getProfileName(BluetoothProfile.PBAP));
591         assertEquals("GATT",
592                 BluetoothProfile.getProfileName(BluetoothProfile.GATT));
593         assertEquals("GATT_SERVER",
594                 BluetoothProfile.getProfileName(BluetoothProfile.GATT_SERVER));
595         assertEquals("MAP",
596                 BluetoothProfile.getProfileName(BluetoothProfile.MAP));
597         assertEquals("SAP",
598                 BluetoothProfile.getProfileName(BluetoothProfile.SAP));
599         assertEquals("A2DP_SINK",
600                 BluetoothProfile.getProfileName(BluetoothProfile.A2DP_SINK));
601         assertEquals("AVRCP_CONTROLLER",
602                 BluetoothProfile.getProfileName(BluetoothProfile.AVRCP_CONTROLLER));
603         // assertEquals("AVRCP",
604         //         BluetoothProfile.getProfileName(BluetoothProfile.AVRCP));
605         assertEquals("HEADSET_CLIENT",
606                 BluetoothProfile.getProfileName(BluetoothProfile.HEADSET_CLIENT));
607         assertEquals("PBAP_CLIENT",
608                 BluetoothProfile.getProfileName(BluetoothProfile.PBAP_CLIENT));
609         assertEquals("MAP_CLIENT",
610                 BluetoothProfile.getProfileName(BluetoothProfile.MAP_CLIENT));
611         assertEquals("HID_DEVICE",
612                 BluetoothProfile.getProfileName(BluetoothProfile.HID_DEVICE));
613         assertEquals("OPP",
614                 BluetoothProfile.getProfileName(BluetoothProfile.OPP));
615         assertEquals("HEARING_AID",
616                 BluetoothProfile.getProfileName(BluetoothProfile.HEARING_AID));
617         assertEquals("LE_AUDIO",
618                 BluetoothProfile.getProfileName(BluetoothProfile.LE_AUDIO));
619         assertEquals("HAP_CLIENT",
620                 BluetoothProfile.getProfileName(BluetoothProfile.HAP_CLIENT));
621 
622         if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
623             return;
624         }
625 
626         assertEquals("VOLUME_CONTROL",
627                 BluetoothProfile.getProfileName(BluetoothProfile.VOLUME_CONTROL));
628         assertEquals("CSIP_SET_COORDINATOR",
629                 BluetoothProfile.getProfileName(BluetoothProfile.CSIP_SET_COORDINATOR));
630         assertEquals("LE_AUDIO_BROADCAST",
631                 BluetoothProfile.getProfileName(BluetoothProfile.LE_AUDIO_BROADCAST));
632         assertEquals("LE_AUDIO_BROADCAST_ASSISTANT",
633                 BluetoothProfile.getProfileName(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT));
634     }
635 
636     @Test
637     @RequiresFlagsEnabled(Flags.FLAG_AUTO_ON_FEATURE)
autoOnApi()638     public void autoOnApi() {
639         assumeTrue(mHasBluetooth);
640 
641         assertThrows(SecurityException.class, () -> mAdapter.isAutoOnSupported());
642         assertThrows(SecurityException.class, () -> mAdapter.isAutoOnEnabled());
643         assertThrows(SecurityException.class, () -> mAdapter.setAutoOnEnabled(false));
644 
645         TestUtils.adoptPermissionAsShellUid(BLUETOOTH_PRIVILEGED);
646 
647         // Not all devices support the auto on feature
648         assumeTrue(mAdapter.isAutoOnSupported());
649 
650         mAdapter.setAutoOnEnabled(false);
651         assertEquals(false, mAdapter.isAutoOnEnabled());
652 
653         mAdapter.setAutoOnEnabled(true);
654         assertEquals(true, mAdapter.isAutoOnEnabled());
655     }
656 
657     @Test
getSetBluetoothHciSnoopLoggingMode()658     public void getSetBluetoothHciSnoopLoggingMode() {
659         assumeTrue(mHasBluetooth);
660 
661         assertThrows(SecurityException.class, () -> mAdapter
662                 .setBluetoothHciSnoopLoggingMode(BluetoothAdapter.BT_SNOOP_LOG_MODE_FULL));
663         assertThrows(SecurityException.class, () -> mAdapter
664                 .getBluetoothHciSnoopLoggingMode());
665 
666         TestUtils.adoptPermissionAsShellUid(BLUETOOTH_PRIVILEGED);
667 
668         assertThrows(IllegalArgumentException.class, () -> mAdapter
669                 .setBluetoothHciSnoopLoggingMode(-1));
670 
671         assertEquals(BluetoothStatusCodes.SUCCESS, mAdapter
672                 .setBluetoothHciSnoopLoggingMode(BluetoothAdapter.BT_SNOOP_LOG_MODE_FULL));
673         assertEquals(mAdapter.getBluetoothHciSnoopLoggingMode(),
674                 BluetoothAdapter.BT_SNOOP_LOG_MODE_FULL);
675 
676         assertEquals(BluetoothStatusCodes.SUCCESS, mAdapter
677                 .setBluetoothHciSnoopLoggingMode(BluetoothAdapter.BT_SNOOP_LOG_MODE_FILTERED));
678         assertEquals(mAdapter.getBluetoothHciSnoopLoggingMode(),
679                 BluetoothAdapter.BT_SNOOP_LOG_MODE_FILTERED);
680 
681         assertEquals(BluetoothStatusCodes.SUCCESS, mAdapter
682                 .setBluetoothHciSnoopLoggingMode(BluetoothAdapter.BT_SNOOP_LOG_MODE_DISABLED));
683         assertEquals(mAdapter.getBluetoothHciSnoopLoggingMode(),
684                 BluetoothAdapter.BT_SNOOP_LOG_MODE_DISABLED);
685 
686     }
687 
688     @Test
setPreferredAudioProfiles_getPreferredAudioProfiles()689     public void setPreferredAudioProfiles_getPreferredAudioProfiles() {
690         assumeTrue(mHasBluetooth);
691 
692         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
693         String deviceAddress = "00:11:22:AA:BB:CC";
694         BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress);
695 
696         Bundle preferences = new Bundle();
697         preferences.putInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY, BluetoothProfile.HEADSET);
698 
699         // Test invalid input
700         assertThrows(NullPointerException.class, () ->
701                 mAdapter.setPreferredAudioProfiles(device, null));
702         assertThrows(IllegalArgumentException.class,
703                 () -> mAdapter.setPreferredAudioProfiles(device, preferences));
704         assertThrows(NullPointerException.class, () -> mAdapter.getPreferredAudioProfiles(null));
705 
706         preferences.putInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY, BluetoothProfile.HID_HOST);
707         assertThrows(IllegalArgumentException.class,
708                 () -> mAdapter.setPreferredAudioProfiles(device, preferences));
709 
710         preferences.putInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY, BluetoothProfile.LE_AUDIO);
711         preferences.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.A2DP);
712         assertThrows(IllegalArgumentException.class,
713                 () -> mAdapter.setPreferredAudioProfiles(device, preferences));
714 
715         preferences.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.GATT);
716         assertThrows(IllegalArgumentException.class,
717                 () -> mAdapter.setPreferredAudioProfiles(device, preferences));
718 
719         preferences.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.HEADSET);
720 
721         assertThrows(NullPointerException.class, () ->
722                 mAdapter.setPreferredAudioProfiles(null, preferences));
723 
724         // Check what happens when the device is not bonded
725         assertTrue(mAdapter.getPreferredAudioProfiles(device).isEmpty());
726         assertEquals(BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED,
727                 mAdapter.setPreferredAudioProfiles(device, preferences));
728     }
729 
730     @Test
preferredAudioProfileCallbacks()731     public void preferredAudioProfileCallbacks() {
732         assumeTrue(mHasBluetooth);
733 
734         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
735         String deviceAddress = "00:11:22:AA:BB:CC";
736         BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress);
737 
738         Executor executor = mContext.getMainExecutor();
739         BluetoothAdapter.PreferredAudioProfilesChangedCallback callback =
740                 new BluetoothAdapter.PreferredAudioProfilesChangedCallback() {
741             @Override
742             public void onPreferredAudioProfilesChanged(
743                     @androidx.annotation.NonNull BluetoothDevice device,
744                     @androidx.annotation.NonNull Bundle preferredAudioProfiles, int status) {}
745         };
746 
747         callback.onPreferredAudioProfilesChanged(device, Bundle.EMPTY,
748                 BluetoothStatusCodes.SUCCESS);
749 
750         assertThrows(NullPointerException.class, () ->
751                 mAdapter.registerPreferredAudioProfilesChangedCallback(null, callback));
752         assertThrows(NullPointerException.class, () ->
753                 mAdapter.registerPreferredAudioProfilesChangedCallback(executor, null));
754         assertThrows(NullPointerException.class, () ->
755                 mAdapter.unregisterPreferredAudioProfilesChangedCallback(null));
756 
757         // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
758         assertThrows(SecurityException.class, () ->
759                 mAdapter.registerPreferredAudioProfilesChangedCallback(executor, callback));
760         assertThrows(IllegalArgumentException.class, () ->
761                 mAdapter.unregisterPreferredAudioProfilesChangedCallback(callback));
762 
763         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
764 
765         if (isDualModeAudioEnabled()) {
766             assertEquals(BluetoothStatusCodes.SUCCESS,
767                     mAdapter.registerPreferredAudioProfilesChangedCallback(executor, callback));
768             assertEquals(BluetoothStatusCodes.SUCCESS,
769                     mAdapter.unregisterPreferredAudioProfilesChangedCallback(callback));
770         } else {
771             assertEquals(BluetoothStatusCodes.FEATURE_NOT_SUPPORTED,
772                     mAdapter.registerPreferredAudioProfilesChangedCallback(executor, callback));
773             assertThrows(IllegalArgumentException.class, () ->
774                     mAdapter.unregisterPreferredAudioProfilesChangedCallback(callback));
775         }
776     }
777 
778     @Test
bluetoothQualityReportReadyCallbacks()779     public void bluetoothQualityReportReadyCallbacks() {
780         assumeTrue(mHasBluetooth);
781 
782         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
783         String deviceAddress = "00:11:22:AA:BB:CC";
784         BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress);
785 
786         Executor executor = mContext.getMainExecutor();
787         BluetoothAdapter.BluetoothQualityReportReadyCallback callback =
788                 new BluetoothAdapter.BluetoothQualityReportReadyCallback() {
789             @Override
790             public void onBluetoothQualityReportReady(
791                     @androidx.annotation.NonNull BluetoothDevice device,
792                     @androidx.annotation.NonNull BluetoothQualityReport bluetoothQualityReport,
793                     int status) {}
794         };
795 
796         BluetoothQualityReport bqr =
797                 BluetoothQualityReportTest.getBqr(BluetoothQualityReport.QUALITY_REPORT_ID_MONITOR);
798 
799         callback.onBluetoothQualityReportReady(device, bqr,
800                 BluetoothStatusCodes.SUCCESS);
801 
802         assertThrows(NullPointerException.class, () ->
803                 mAdapter.registerBluetoothQualityReportReadyCallback(null, callback));
804         assertThrows(NullPointerException.class, () ->
805                 mAdapter.registerBluetoothQualityReportReadyCallback(executor, null));
806         assertThrows(NullPointerException.class, () ->
807                 mAdapter.unregisterBluetoothQualityReportReadyCallback(null));
808 
809         // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
810         assertThrows(SecurityException.class, () ->
811                 mAdapter.registerBluetoothQualityReportReadyCallback(executor, callback));
812         assertThrows(IllegalArgumentException.class, () ->
813                 mAdapter.unregisterBluetoothQualityReportReadyCallback(callback));
814 
815         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
816 
817         // Try the happy path
818         assertEquals(BluetoothStatusCodes.SUCCESS,
819                 mAdapter.registerBluetoothQualityReportReadyCallback(executor, callback));
820         assertEquals(BluetoothStatusCodes.SUCCESS,
821                 mAdapter.unregisterBluetoothQualityReportReadyCallback(callback));
822     }
823 
824     @Test
notifyActiveDeviceChangeApplied()825     public void notifyActiveDeviceChangeApplied() {
826         assumeTrue(mHasBluetooth);
827 
828         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
829         String deviceAddress = "00:11:22:AA:BB:CC";
830         BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress);
831 
832         assertThrows(NullPointerException.class, () ->
833                 mAdapter.notifyActiveDeviceChangeApplied(null));
834 
835         assertEquals(BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED,
836                 mAdapter.notifyActiveDeviceChangeApplied(device));
837     }
838 
isDualModeAudioEnabled()839     private boolean isDualModeAudioEnabled() {
840         return SystemProperties.getBoolean(ENABLE_DUAL_MODE_AUDIO, false);
841     }
842 
waitForAdapterNameChange()843     private boolean waitForAdapterNameChange() {
844         mAdapterNameChangedlock.lock();
845         try {
846             // Wait for the Adapter name to be changed
847             while (!mIsAdapterNameChanged) {
848                 if (!mConditionAdapterNameChanged.await(
849                         SET_NAME_TIMEOUT, TimeUnit.MILLISECONDS)) {
850                     Log.e(TAG, "Timeout while waiting for adapter name change");
851                     break;
852                 }
853             }
854         } catch (InterruptedException e) {
855             Log.e(TAG, "waitForAdapterNameChange: interrupted");
856         } finally {
857             mAdapterNameChangedlock.unlock();
858         }
859         return mIsAdapterNameChanged;
860     }
861 
862     private final BroadcastReceiver mAdapterNameChangeReceiver = new BroadcastReceiver() {
863         @Override
864         public void onReceive(Context context, Intent intent) {
865             String action = intent.getAction();
866             if (action.equals(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)) {
867                 mAdapterNameChangedlock.lock();
868                 mIsAdapterNameChanged = true;
869                 try {
870                     mConditionAdapterNameChanged.signal();
871                 } catch (IllegalMonitorStateException ex) {
872                 } finally {
873                     mAdapterNameChangedlock.unlock();
874                 }
875             }
876         }
877     };
878 }
879