• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.bluetooth.BluetoothDevice.ACCESS_ALLOWED;
22 import static android.bluetooth.BluetoothDevice.ACCESS_REJECTED;
23 import static android.bluetooth.BluetoothDevice.ACCESS_UNKNOWN;
24 import static android.bluetooth.BluetoothDevice.TRANSPORT_AUTO;
25 import static android.bluetooth.BluetoothDevice.TRANSPORT_BREDR;
26 import static android.bluetooth.BluetoothDevice.TRANSPORT_LE;
27 
28 import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
29 import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra;
30 
31 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
32 
33 import static com.google.common.truth.Truth.assertThat;
34 
35 import static org.junit.Assert.assertThrows;
36 import static org.junit.Assume.assumeTrue;
37 import static org.mockito.Mockito.any;
38 import static org.mockito.Mockito.mock;
39 import static org.mockito.Mockito.timeout;
40 import static org.mockito.Mockito.verify;
41 
42 import android.app.UiAutomation;
43 import android.bluetooth.BluetoothAdapter;
44 import android.bluetooth.BluetoothDevice;
45 import android.bluetooth.BluetoothDevice.BluetoothAddress;
46 import android.bluetooth.BluetoothManager;
47 import android.bluetooth.BluetoothProfile;
48 import android.bluetooth.BluetoothSinkAudioPolicy;
49 import android.bluetooth.BluetoothSocket;
50 import android.bluetooth.BluetoothSocketException;
51 import android.bluetooth.BluetoothStatusCodes;
52 import android.bluetooth.OobData;
53 import android.bluetooth.test_utils.Permissions;
54 import android.content.AttributionSource;
55 import android.content.BroadcastReceiver;
56 import android.content.Context;
57 import android.content.Intent;
58 import android.content.IntentFilter;
59 import android.content.pm.PackageManager;
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.LargeTest;
66 import androidx.test.platform.app.InstrumentationRegistry;
67 
68 import com.android.bluetooth.flags.Flags;
69 import com.android.compatibility.common.util.CddTest;
70 
71 import org.hamcrest.Matcher;
72 import org.hamcrest.core.AllOf;
73 import org.junit.After;
74 import org.junit.Before;
75 import org.junit.Rule;
76 import org.junit.Test;
77 import org.junit.runner.RunWith;
78 import org.mockito.hamcrest.MockitoHamcrest;
79 
80 import java.io.IOException;
81 import java.io.UnsupportedEncodingException;
82 import java.time.Duration;
83 import java.util.List;
84 import java.util.UUID;
85 
86 @RunWith(AndroidJUnit4.class)
87 @LargeTest
88 public class BluetoothDeviceTest {
89 
90     private Context mContext;
91     private boolean mHasBluetooth;
92     private boolean mHasCompanionDevice;
93     private BluetoothAdapter mAdapter;
94     private UiAutomation mUiAutomation;
95 
96     private final String mFakeDeviceAddress = "00:11:22:AA:BB:CC";
97     private BluetoothDevice mFakeDevice;
98     private int mFakePsm = 100;
99     private UUID mFakeUuid = UUID.fromString("0000111E-0000-1000-8000-00805F9B34FB");
100 
101     @Rule
102     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
103 
104     @Before
setUp()105     public void setUp() throws Exception {
106         mContext = InstrumentationRegistry.getInstrumentation().getContext();
107 
108         mHasBluetooth =
109                 mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
110 
111         mHasCompanionDevice =
112                 mContext.getPackageManager()
113                         .hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP);
114 
115         if (mHasBluetooth && mHasCompanionDevice) {
116             BluetoothManager manager = mContext.getSystemService(BluetoothManager.class);
117             mAdapter = manager.getAdapter();
118             mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
119             mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
120             assertThat(BTAdapterUtils.enableAdapter(mAdapter, mContext)).isTrue();
121             mFakeDevice = mAdapter.getRemoteDevice(mFakeDeviceAddress);
122         }
123     }
124 
125     @After
tearDown()126     public void tearDown() throws Exception {
127         if (mHasBluetooth && mHasCompanionDevice) {
128             mAdapter = null;
129             mUiAutomation.dropShellPermissionIdentity();
130         }
131     }
132 
133     @Test
setAlias_getAlias()134     public void setAlias_getAlias() {
135         // Skip the test if bluetooth or companion device are not present.
136         assumeTrue(mHasBluetooth && mHasCompanionDevice);
137 
138         int userId = mContext.getUser().getIdentifier();
139         String packageName = mContext.getOpPackageName();
140 
141         AttributionSource source = AttributionSource.myAttributionSource();
142         assertThat(source.getPackageName()).isEqualTo("android.bluetooth.cts");
143 
144         // Verifies that when there is no alias, we return the device name
145         assertThat(mFakeDevice.getAlias()).isNull();
146 
147         assertThrows(IllegalArgumentException.class, () -> mFakeDevice.setAlias(""));
148 
149         String testDeviceAlias = "Test Device Alias";
150 
151         // This should throw a SecurityException because there is no CDM association
152         assertThrows(
153                 "BluetoothDevice.setAlias without"
154                         + " a CDM association or BLUETOOTH_PRIVILEGED permission",
155                 SecurityException.class,
156                 () -> mFakeDevice.setAlias(testDeviceAlias));
157 
158         runShellCommand(
159                 String.format(
160                         "cmd companiondevice associate %d %s %s",
161                         userId, packageName, mFakeDeviceAddress));
162         String output = runShellCommand("dumpsys companiondevice");
163         assertThat(output).contains(packageName);
164         assertThat(output.toLowerCase()).contains(mFakeDeviceAddress.toLowerCase());
165 
166         // Takes time to update the CDM cache, so sleep to ensure the association is cached
167         try {
168             Thread.sleep(1000);
169         } catch (Exception e) {
170             e.printStackTrace();
171         }
172 
173         /*
174          * Device properties don't exist for non-existent BluetoothDevice, so calling setAlias with
175          * permissions should return false
176          */
177         assertThat(mFakeDevice.setAlias(testDeviceAlias))
178                 .isEqualTo(BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED);
179         runShellCommand(
180                 String.format(
181                         "cmd companiondevice disassociate %d %s %s",
182                         userId, packageName, mFakeDeviceAddress));
183 
184         assertThat(BTAdapterUtils.disableAdapter(mAdapter, mContext)).isTrue();
185         assertThat(mFakeDevice.getAlias()).isNull();
186         assertThat(mFakeDevice.setAlias(testDeviceAlias))
187                 .isEqualTo(BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED);
188     }
189 
190     @Test
getAddressType()191     public void getAddressType() {
192         // Skip the test if bluetooth or companion device are not present.
193         assumeTrue(mHasBluetooth && mHasCompanionDevice);
194 
195         assertThat(mFakeDevice.getAddressType()).isEqualTo(BluetoothDevice.ADDRESS_TYPE_PUBLIC);
196     }
197 
198     @Test
getIdentityAddress()199     public void getIdentityAddress() {
200         // Skip the test if bluetooth or companion device are not present.
201         assumeTrue(mHasBluetooth && mHasCompanionDevice);
202 
203         // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
204         assertThrows(
205                 "No BLUETOOTH_PRIVILEGED permission",
206                 SecurityException.class,
207                 () -> mFakeDevice.getIdentityAddress());
208     }
209 
210     @Test
211     @RequiresFlagsEnabled(Flags.FLAG_IDENTITY_ADDRESS_TYPE_API)
getIdentityAddressWithType()212     public void getIdentityAddressWithType() {
213         // Skip the test if bluetooth or companion device are not present.
214         assumeTrue(mHasBluetooth && mHasCompanionDevice);
215 
216         // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
217         assertThrows(
218                 "No BLUETOOTH_PRIVILEGED permission",
219                 SecurityException.class,
220                 () -> mFakeDevice.getIdentityAddressWithType());
221     }
222 
223     @CddTest(requirements = {"7.4.3/C-2-1"})
224     @Test
225     @RequiresFlagsEnabled(Flags.FLAG_IDENTITY_ADDRESS_TYPE_API)
testBluetoothAddress()226     public void testBluetoothAddress() {
227         int addressType = BluetoothDevice.ADDRESS_TYPE_PUBLIC;
228         BluetoothAddress bluetoothAddress = new BluetoothAddress(mFakeDeviceAddress, addressType);
229 
230         assertThat(bluetoothAddress.getAddress()).isEqualTo(mFakeDeviceAddress);
231         assertThat(bluetoothAddress.getAddressType()).isEqualTo(addressType);
232     }
233 
234     @Test
getConnectionHandle()235     public void getConnectionHandle() {
236         // Skip the test if bluetooth or companion device are not present.
237         assumeTrue(mHasBluetooth && mHasCompanionDevice);
238 
239         // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
240         assertThrows(
241                 "No BLUETOOTH_PRIVILEGED permission",
242                 SecurityException.class,
243                 () -> mFakeDevice.getConnectionHandle(TRANSPORT_LE));
244 
245         // but it should work after we get the permission
246         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
247         assertThat(mFakeDevice.getConnectionHandle(TRANSPORT_LE)).isEqualTo(BluetoothDevice.ERROR);
248     }
249 
250     @Test
getAnonymizedAddress()251     public void getAnonymizedAddress() {
252         // Skip the test if bluetooth or companion device are not present.
253         assumeTrue(mHasBluetooth && mHasCompanionDevice);
254 
255         assertThat(mFakeDevice.getAnonymizedAddress()).isEqualTo("XX:XX:XX:XX:BB:CC");
256     }
257 
258     @Test
getBatteryLevel()259     public void getBatteryLevel() {
260         // Skip the test if bluetooth or companion device are not present.
261         assumeTrue(mHasBluetooth && mHasCompanionDevice);
262 
263         assertThat(mFakeDevice.getBatteryLevel()).isEqualTo(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
264 
265         mUiAutomation.dropShellPermissionIdentity();
266         assertThrows(SecurityException.class, () -> mFakeDevice.getBatteryLevel());
267         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
268 
269         assertThat(BTAdapterUtils.disableAdapter(mAdapter, mContext)).isTrue();
270         assertThat(mFakeDevice.getBatteryLevel())
271                 .isEqualTo(BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF);
272     }
273 
274     @Test
isBondingInitiatedLocally()275     public void isBondingInitiatedLocally() {
276         // Skip the test if bluetooth or companion device are not present.
277         assumeTrue(mHasBluetooth && mHasCompanionDevice);
278 
279         assertThat(mFakeDevice.isBondingInitiatedLocally()).isFalse();
280 
281         mUiAutomation.dropShellPermissionIdentity();
282         assertThrows(SecurityException.class, () -> mFakeDevice.isBondingInitiatedLocally());
283         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
284 
285         assertThat(BTAdapterUtils.disableAdapter(mAdapter, mContext)).isTrue();
286         assertThat(mFakeDevice.isBondingInitiatedLocally()).isFalse();
287     }
288 
289     @Test
prepareToEnterProcess()290     public void prepareToEnterProcess() {
291         // Skip the test if bluetooth or companion device are not present.
292         assumeTrue(mHasBluetooth && mHasCompanionDevice);
293 
294         mFakeDevice.prepareToEnterProcess(null);
295     }
296 
297     @Test
setPin()298     public void setPin() {
299         // Skip the test if bluetooth or companion device are not present.
300         assumeTrue(mHasBluetooth && mHasCompanionDevice);
301 
302         assertThat(mFakeDevice.setPin((String) null)).isFalse();
303         assertThat(mFakeDevice.setPin("12345678901234567")).isFalse(); // check PIN too big
304 
305         assertThat(mFakeDevice.setPin("123456")).isFalse(); // device is not bonding
306 
307         mUiAutomation.dropShellPermissionIdentity();
308         assertThrows(SecurityException.class, () -> mFakeDevice.setPin("123456"));
309         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
310 
311         assertThat(BTAdapterUtils.disableAdapter(mAdapter, mContext)).isTrue();
312         assertThat(mFakeDevice.setPin("123456")).isFalse();
313     }
314 
315     @Test
connect_disconnect()316     public void connect_disconnect() {
317         // Skip the test if bluetooth or companion device are not present.
318         assumeTrue(mHasBluetooth && mHasCompanionDevice);
319 
320         // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
321         assertThrows(SecurityException.class, () -> mFakeDevice.connect());
322         assertThrows(SecurityException.class, () -> mFakeDevice.disconnect());
323     }
324 
325     @Test
cancelBondProcess()326     public void cancelBondProcess() {
327         // Skip the test if bluetooth or companion device are not present.
328         assumeTrue(mHasBluetooth && mHasCompanionDevice);
329 
330         mUiAutomation.dropShellPermissionIdentity();
331         assertThrows(SecurityException.class, () -> mFakeDevice.cancelBondProcess());
332         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
333 
334         assertThat(BTAdapterUtils.disableAdapter(mAdapter, mContext)).isTrue();
335         assertThat(mFakeDevice.cancelBondProcess()).isFalse();
336     }
337 
338     @Test
createBond()339     public void createBond() {
340         // Skip the test if bluetooth or companion device are not present.
341         assumeTrue(mHasBluetooth && mHasCompanionDevice);
342 
343         mUiAutomation.dropShellPermissionIdentity();
344         assertThrows(SecurityException.class, () -> mFakeDevice.createBond(TRANSPORT_AUTO));
345         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
346 
347         assertThat(BTAdapterUtils.disableAdapter(mAdapter, mContext)).isTrue();
348         assertThat(mFakeDevice.createBond(TRANSPORT_AUTO)).isFalse();
349     }
350 
351     @Test
createBondOutOfBand()352     public void createBondOutOfBand() {
353         // Skip the test if bluetooth or companion device are not present.
354         assumeTrue(mHasBluetooth && mHasCompanionDevice);
355 
356         OobData data = new OobData.ClassicBuilder(new byte[16], new byte[2], new byte[7]).build();
357 
358         assertThrows(
359                 IllegalArgumentException.class,
360                 () -> mFakeDevice.createBondOutOfBand(TRANSPORT_AUTO, null, null));
361 
362         mUiAutomation.dropShellPermissionIdentity();
363         assertThrows(
364                 SecurityException.class,
365                 () -> mFakeDevice.createBondOutOfBand(TRANSPORT_AUTO, data, null));
366         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
367     }
368 
369     @Test
getUuids()370     public void getUuids() {
371         // Skip the test if bluetooth or companion device are not present.
372         assumeTrue(mHasBluetooth && mHasCompanionDevice);
373 
374         assertThat(mFakeDevice.getUuids()).isNull();
375         mUiAutomation.dropShellPermissionIdentity();
376         assertThrows(SecurityException.class, () -> mFakeDevice.getUuids());
377         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
378 
379         assertThat(BTAdapterUtils.disableAdapter(mAdapter, mContext)).isTrue();
380         assertThat(mFakeDevice.getUuids()).isNull();
381     }
382 
383     @Test
isEncrypted()384     public void isEncrypted() {
385         // Skip the test if bluetooth or companion device are not present.
386         assumeTrue(mHasBluetooth && mHasCompanionDevice);
387 
388         // Device is not connected
389         assertThat(mFakeDevice.isEncrypted()).isFalse();
390 
391         mUiAutomation.dropShellPermissionIdentity();
392         assertThrows(SecurityException.class, () -> mFakeDevice.isEncrypted());
393         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
394 
395         assertThat(BTAdapterUtils.disableAdapter(mAdapter, mContext)).isTrue();
396         assertThat(mFakeDevice.isEncrypted()).isFalse();
397     }
398 
399     @Test
removeBond()400     public void removeBond() {
401         // Skip the test if bluetooth or companion device are not present.
402         assumeTrue(mHasBluetooth && mHasCompanionDevice);
403 
404         // Device is not bonded
405         assertThat(mFakeDevice.removeBond()).isFalse();
406 
407         mUiAutomation.dropShellPermissionIdentity();
408         assertThrows(SecurityException.class, () -> mFakeDevice.removeBond());
409         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
410 
411         assertThat(BTAdapterUtils.disableAdapter(mAdapter, mContext)).isTrue();
412         assertThat(mFakeDevice.removeBond()).isFalse();
413     }
414 
415     @Test
setPinByteArray()416     public void setPinByteArray() {
417         // Skip the test if bluetooth or companion device are not present.
418         assumeTrue(mHasBluetooth && mHasCompanionDevice);
419 
420         assertThrows(NullPointerException.class, () -> mFakeDevice.setPin((byte[]) null));
421 
422         // check PIN too big
423         assertThat(mFakeDevice.setPin(convertPinToBytes("12345678901234567"))).isFalse();
424         assertThat(mFakeDevice.setPin(convertPinToBytes("123456")))
425                 .isFalse(); // device is not bonding
426 
427         mUiAutomation.dropShellPermissionIdentity();
428         assertThrows(
429                 SecurityException.class, () -> mFakeDevice.setPin(convertPinToBytes("123456")));
430         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
431 
432         assertThat(BTAdapterUtils.disableAdapter(mAdapter, mContext)).isTrue();
433         assertThat(mFakeDevice.setPin(convertPinToBytes("123456"))).isFalse();
434     }
435 
436     @Test
connectGatt()437     public void connectGatt() {
438         // Skip the test if bluetooth or companion device are not present.
439         assumeTrue(mHasBluetooth && mHasCompanionDevice);
440 
441         assertThrows(
442                 NullPointerException.class,
443                 () ->
444                         mFakeDevice.connectGatt(
445                                 mContext,
446                                 false,
447                                 null,
448                                 TRANSPORT_AUTO,
449                                 BluetoothDevice.PHY_LE_1M_MASK));
450 
451         assertThrows(
452                 NullPointerException.class,
453                 () ->
454                         mFakeDevice.connectGatt(
455                                 mContext,
456                                 false,
457                                 null,
458                                 TRANSPORT_AUTO,
459                                 BluetoothDevice.PHY_LE_1M_MASK,
460                                 null));
461     }
462 
463     @Test
fetchUuidsWithSdp()464     public void fetchUuidsWithSdp() {
465         // Skip the test if bluetooth or companion device are not present.
466         assumeTrue(mHasBluetooth && mHasCompanionDevice);
467 
468         assertThat(mFakeDevice.fetchUuidsWithSdp()).isTrue();
469 
470         // TRANSPORT_AUTO doesn't need BLUETOOTH_PRIVILEGED permission
471         assertThat(mFakeDevice.fetchUuidsWithSdp(TRANSPORT_AUTO)).isTrue();
472 
473         // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
474         assertThrows(SecurityException.class, () -> mFakeDevice.fetchUuidsWithSdp(TRANSPORT_BREDR));
475         assertThrows(SecurityException.class, () -> mFakeDevice.fetchUuidsWithSdp(TRANSPORT_LE));
476 
477         assertThat(BTAdapterUtils.disableAdapter(mAdapter, mContext)).isTrue();
478         assertThat(mFakeDevice.fetchUuidsWithSdp(TRANSPORT_AUTO)).isFalse();
479     }
480 
481     @Test
messageAccessPermission()482     public void messageAccessPermission() {
483         // Skip the test if bluetooth or companion device are not present
484         // or if MAP is not enabled.
485         assumeTrue(
486                 mHasBluetooth
487                         && mHasCompanionDevice
488                         && TestUtils.isProfileEnabled(BluetoothProfile.MAP));
489 
490         // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
491         assertThrows(
492                 SecurityException.class,
493                 () -> mFakeDevice.setMessageAccessPermission(ACCESS_ALLOWED));
494         assertThrows(
495                 SecurityException.class,
496                 () -> mFakeDevice.setMessageAccessPermission(ACCESS_UNKNOWN));
497         assertThrows(
498                 SecurityException.class,
499                 () -> mFakeDevice.setMessageAccessPermission(ACCESS_REJECTED));
500 
501         TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
502 
503         // Should be able to set permissions after adopting the BLUETOOTH_PRIVILEGED permission
504         assertThat(mFakeDevice.setMessageAccessPermission(ACCESS_UNKNOWN)).isTrue();
505         assertThat(mFakeDevice.getMessageAccessPermission()).isEqualTo(ACCESS_UNKNOWN);
506         assertThat(mFakeDevice.setMessageAccessPermission(ACCESS_ALLOWED)).isTrue();
507         assertThat(mFakeDevice.getMessageAccessPermission()).isEqualTo(ACCESS_ALLOWED);
508         assertThat(mFakeDevice.setMessageAccessPermission(ACCESS_REJECTED)).isTrue();
509         assertThat(mFakeDevice.getMessageAccessPermission()).isEqualTo(ACCESS_REJECTED);
510     }
511 
512     @Test
phonebookAccessPermission()513     public void phonebookAccessPermission() {
514         // Skip the test if bluetooth or companion device are not present
515         // or if PBAP is not enabled.
516         assumeTrue(
517                 mHasBluetooth
518                         && mHasCompanionDevice
519                         && TestUtils.isProfileEnabled(BluetoothProfile.PBAP));
520 
521         // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
522         assertThrows(
523                 SecurityException.class,
524                 () -> mFakeDevice.setPhonebookAccessPermission(ACCESS_ALLOWED));
525         assertThrows(
526                 SecurityException.class,
527                 () -> mFakeDevice.setPhonebookAccessPermission(ACCESS_UNKNOWN));
528         assertThrows(
529                 SecurityException.class,
530                 () -> mFakeDevice.setPhonebookAccessPermission(ACCESS_REJECTED));
531 
532         TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
533 
534         // Should be able to set permissions after adopting the BLUETOOTH_PRIVILEGED permission
535         assertThat(mFakeDevice.setPhonebookAccessPermission(ACCESS_UNKNOWN)).isTrue();
536         assertThat(mFakeDevice.getPhonebookAccessPermission()).isEqualTo(ACCESS_UNKNOWN);
537         assertThat(mFakeDevice.setPhonebookAccessPermission(ACCESS_ALLOWED)).isTrue();
538         assertThat(mFakeDevice.getPhonebookAccessPermission()).isEqualTo(ACCESS_ALLOWED);
539         assertThat(mFakeDevice.setPhonebookAccessPermission(ACCESS_REJECTED)).isTrue();
540         assertThat(mFakeDevice.getPhonebookAccessPermission()).isEqualTo(ACCESS_REJECTED);
541     }
542 
543     @Test
simAccessPermission()544     public void simAccessPermission() {
545         // Skip the test if bluetooth or companion device are not present
546         // or if SAP is not enabled.
547         assumeTrue(
548                 mHasBluetooth
549                         && mHasCompanionDevice
550                         && TestUtils.isProfileEnabled(BluetoothProfile.SAP));
551 
552         // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
553         assertThrows(
554                 SecurityException.class, () -> mFakeDevice.setSimAccessPermission(ACCESS_ALLOWED));
555         assertThrows(
556                 SecurityException.class, () -> mFakeDevice.setSimAccessPermission(ACCESS_UNKNOWN));
557         assertThrows(
558                 SecurityException.class, () -> mFakeDevice.setSimAccessPermission(ACCESS_REJECTED));
559 
560         TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
561 
562         // Should be able to set permissions after adopting the BLUETOOTH_PRIVILEGED permission
563         assertThat(mFakeDevice.setSimAccessPermission(ACCESS_UNKNOWN)).isTrue();
564         assertThat(mFakeDevice.getSimAccessPermission()).isEqualTo(ACCESS_UNKNOWN);
565         assertThat(mFakeDevice.setSimAccessPermission(ACCESS_ALLOWED)).isTrue();
566         assertThat(mFakeDevice.getSimAccessPermission()).isEqualTo(ACCESS_ALLOWED);
567         assertThat(mFakeDevice.setSimAccessPermission(ACCESS_REJECTED)).isTrue();
568         assertThat(mFakeDevice.getSimAccessPermission()).isEqualTo(ACCESS_REJECTED);
569     }
570 
571     @Test
isRequestAudioPolicyAsSinkSupported()572     public void isRequestAudioPolicyAsSinkSupported() {
573         // Skip the test if bluetooth or companion device are not present.
574         assumeTrue(mHasBluetooth && mHasCompanionDevice);
575 
576         assertThrows(
577                 SecurityException.class, () -> mFakeDevice.isRequestAudioPolicyAsSinkSupported());
578 
579         TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
580 
581         assertThat(mFakeDevice.isRequestAudioPolicyAsSinkSupported())
582                 .isEqualTo(BluetoothStatusCodes.FEATURE_NOT_CONFIGURED);
583     }
584 
585     @Test
setGetAudioPolicy()586     public void setGetAudioPolicy() {
587         // Skip the test if bluetooth or companion device are not present.
588         assumeTrue(mHasBluetooth && mHasCompanionDevice);
589 
590         BluetoothSinkAudioPolicy demoAudioPolicy = new BluetoothSinkAudioPolicy.Builder().build();
591 
592         // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
593         assertThrows(
594                 SecurityException.class,
595                 () -> mFakeDevice.requestAudioPolicyAsSink(demoAudioPolicy));
596         assertThrows(SecurityException.class, () -> mFakeDevice.getRequestedAudioPolicyAsSink());
597 
598         TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
599 
600         assertThat(mFakeDevice.requestAudioPolicyAsSink(demoAudioPolicy))
601                 .isEqualTo(BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED);
602         assertThat(mFakeDevice.getRequestedAudioPolicyAsSink()).isNull();
603 
604         BluetoothSinkAudioPolicy newPolicy =
605                 new BluetoothSinkAudioPolicy.Builder(demoAudioPolicy)
606                         .setCallEstablishPolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
607                         .setActiveDevicePolicyAfterConnection(
608                                 BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED)
609                         .setInBandRingtonePolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
610                         .build();
611 
612         assertThat(mFakeDevice.requestAudioPolicyAsSink(newPolicy))
613                 .isEqualTo(BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED);
614         assertThat(mFakeDevice.getRequestedAudioPolicyAsSink()).isNull();
615 
616         assertThat(newPolicy.getCallEstablishPolicy())
617                 .isEqualTo(BluetoothSinkAudioPolicy.POLICY_ALLOWED);
618         assertThat(newPolicy.getActiveDevicePolicyAfterConnection())
619                 .isEqualTo(BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED);
620         assertThat(newPolicy.getInBandRingtonePolicy())
621                 .isEqualTo(BluetoothSinkAudioPolicy.POLICY_ALLOWED);
622     }
623 
convertPinToBytes(String pin)624     private byte[] convertPinToBytes(String pin) {
625         if (pin == null) {
626             return null;
627         }
628         byte[] pinBytes;
629         try {
630             pinBytes = pin.getBytes("UTF-8");
631         } catch (UnsupportedEncodingException uee) {
632             return null;
633         }
634         return pinBytes;
635     }
636 
637     @Test
getPackageNameOfBondingApplication()638     public void getPackageNameOfBondingApplication() {
639         IntentFilter filter = new IntentFilter();
640         filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
641         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
642         BroadcastReceiver mockReceiver = mock(BroadcastReceiver.class);
643         mContext.registerReceiver(mockReceiver, filter);
644 
645         // Skip the test if bluetooth or companion device are not present.
646         assumeTrue(mHasBluetooth && mHasCompanionDevice);
647 
648         mUiAutomation.dropShellPermissionIdentity();
649         assertThrows(
650                 SecurityException.class, () -> mFakeDevice.getPackageNameOfBondingApplication());
651         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
652         assertThrows(
653                 SecurityException.class, () -> mFakeDevice.getPackageNameOfBondingApplication());
654 
655         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_PRIVILEGED, BLUETOOTH_CONNECT);
656         // Since no application actually start bonding with this device, this should return null
657         assertThat(mFakeDevice.getPackageNameOfBondingApplication()).isNull();
658 
659         mFakeDevice.createBond();
660         assertThat(mFakeDevice.getPackageNameOfBondingApplication())
661                 .isEqualTo(mContext.getPackageName());
662         verifyIntentReceived(
663                 mockReceiver,
664                 Duration.ofSeconds(5),
665                 hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
666                 hasExtra(BluetoothDevice.EXTRA_DEVICE, mFakeDevice),
667                 hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING));
668 
669         // Clean up create bond
670         // Either cancel the bonding process or remove bond
671         mFakeDevice.cancelBondProcess();
672         mFakeDevice.removeBond();
673         verifyIntentReceived(
674                 mockReceiver,
675                 Duration.ofSeconds(5),
676                 hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
677                 hasExtra(BluetoothDevice.EXTRA_DEVICE, mFakeDevice),
678                 hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE));
679     }
680 
681     @Test
setActiveAudioDevicePolicy_getActiveAudioDevicePolicy()682     public void setActiveAudioDevicePolicy_getActiveAudioDevicePolicy() {
683         if (!mHasBluetooth || !mHasCompanionDevice) {
684             // Skip the test if bluetooth or companion device are not present.
685             return;
686         }
687         String deviceAddress = "00:11:22:AA:AA:AA";
688         BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress);
689 
690         // This should throw a SecurityException because no BLUETOOTH_CONNECT permission
691         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_PRIVILEGED);
692         assertThrows(
693                 SecurityException.class,
694                 () ->
695                         device.setActiveAudioDevicePolicy(
696                                 BluetoothDevice
697                                         .ACTIVE_AUDIO_DEVICE_POLICY_ALL_PROFILES_INACTIVE_UPON_CONNECTION));
698         assertThrows(SecurityException.class, () -> device.getActiveAudioDevicePolicy());
699 
700         // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
701         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
702         assertThrows(
703                 SecurityException.class,
704                 () ->
705                         device.setActiveAudioDevicePolicy(
706                                 BluetoothDevice
707                                         .ACTIVE_AUDIO_DEVICE_POLICY_ALL_PROFILES_INACTIVE_UPON_CONNECTION));
708         assertThrows(SecurityException.class, () -> device.getActiveAudioDevicePolicy());
709 
710         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
711 
712         assertThat(device.getActiveAudioDevicePolicy())
713                 .isEqualTo(BluetoothDevice.ACTIVE_AUDIO_DEVICE_POLICY_DEFAULT);
714         assertThat(
715                         device.setActiveAudioDevicePolicy(
716                                 BluetoothDevice
717                                         .ACTIVE_AUDIO_DEVICE_POLICY_ALL_PROFILES_INACTIVE_UPON_CONNECTION))
718                 .isEqualTo(BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED);
719     }
720 
721     @RequiresFlagsEnabled(Flags.FLAG_BT_SOCKET_API_L2CAP_CID)
722     @Test
getL2capChannel()723     public void getL2capChannel() throws IOException {
724         // Skip the test if bluetooth or companion device are not present.
725         assumeTrue(mHasBluetooth && mHasCompanionDevice);
726 
727         BluetoothSocket l2capSocket = mFakeDevice.createInsecureL2capChannel(mFakePsm);
728         BluetoothSocket rfcommSocket =
729                 mFakeDevice.createInsecureRfcommSocketToServiceRecord(mFakeUuid);
730 
731         mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
732 
733         // This should throw a BluetoothSocketException because it is not L2CAP socket
734         assertThrows(
735                 "Unknown L2CAP socket",
736                 BluetoothSocketException.class,
737                 () -> rfcommSocket.getL2capLocalChannelId());
738         assertThrows(
739                 "Unknown L2CAP socket",
740                 BluetoothSocketException.class,
741                 () -> rfcommSocket.getL2capRemoteChannelId());
742 
743         // This should throw a BluetoothSocketException because L2CAP socket is not connected
744         assertThrows(
745                 "Socket closed",
746                 BluetoothSocketException.class,
747                 () -> l2capSocket.getL2capLocalChannelId());
748         assertThrows(
749                 "Socket closed",
750                 BluetoothSocketException.class,
751                 () -> l2capSocket.getL2capRemoteChannelId());
752     }
753 
754     @RequiresFlagsEnabled(Flags.FLAG_METADATA_API_MICROPHONE_FOR_CALL_ENABLED)
755     @Test
setMicrophonePreferredForCalls_isMicrophonePreferredForCalls()756     public void setMicrophonePreferredForCalls_isMicrophonePreferredForCalls() {
757         // Skip the test if bluetooth or companion device are not present.
758         assumeTrue(mHasBluetooth && mHasCompanionDevice);
759 
760         // Use alternate address to prevent another test from having unwanted consequences here
761         mFakeDevice = mAdapter.getRemoteDevice("AB:11:22:AA:BB:CC");
762         Permissions.enforceEachPermissions(
763                 () -> mFakeDevice.setMicrophonePreferredForCalls(false),
764                 List.of(BLUETOOTH_PRIVILEGED, BLUETOOTH_CONNECT));
765         Permissions.enforceEachPermissions(
766                 () -> mFakeDevice.isMicrophonePreferredForCalls(),
767                 List.of(BLUETOOTH_PRIVILEGED, BLUETOOTH_CONNECT));
768 
769         // default value should be true
770         try (var p = Permissions.withPermissions(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED)) {
771             assertThat(mFakeDevice.isMicrophonePreferredForCalls()).isTrue();
772             assertThat(mFakeDevice.setMicrophonePreferredForCalls(true))
773                     .isEqualTo(BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED);
774         }
775     }
776 
verifyIntentReceived( BroadcastReceiver receiver, Duration timeout, Matcher<Intent>... matchers)777     private void verifyIntentReceived(
778             BroadcastReceiver receiver, Duration timeout, Matcher<Intent>... matchers) {
779         verify(receiver, timeout(timeout.toMillis()))
780                 .onReceive(any(), MockitoHamcrest.argThat(AllOf.allOf(matchers)));
781     }
782 }
783