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