1 /*
2  * Copyright (C) 2022 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 com.google.common.truth.Truth.assertThat;
24 
25 import static org.junit.Assert.assertThrows;
26 import static org.junit.Assume.assumeTrue;
27 import static org.mockito.ArgumentMatchers.any;
28 import static org.mockito.ArgumentMatchers.anyInt;
29 import static org.mockito.Mockito.eq;
30 import static org.mockito.Mockito.mock;
31 import static org.mockito.Mockito.timeout;
32 import static org.mockito.Mockito.verify;
33 
34 import android.app.UiAutomation;
35 import android.bluetooth.BluetoothAdapter;
36 import android.bluetooth.BluetoothDevice;
37 import android.bluetooth.BluetoothStatusCodes;
38 import android.bluetooth.BluetoothUuid;
39 import android.bluetooth.le.AdvertiseSettings;
40 import android.bluetooth.le.AdvertisingSetParameters;
41 import android.bluetooth.test_utils.BlockingBluetoothAdapter;
42 import android.bluetooth.test_utils.Permissions;
43 import android.content.BroadcastReceiver;
44 import android.content.Context;
45 import android.content.IntentFilter;
46 import android.content.pm.PackageManager;
47 import android.os.ParcelUuid;
48 import android.provider.Settings;
49 
50 import androidx.test.ext.junit.runners.AndroidJUnit4;
51 import androidx.test.platform.app.InstrumentationRegistry;
52 
53 import com.android.compatibility.common.util.CddTest;
54 
55 import com.google.common.truth.Expect;
56 
57 import org.junit.After;
58 import org.junit.Before;
59 import org.junit.Rule;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 
63 import java.time.Duration;
64 import java.util.concurrent.Executor;
65 
66 @RunWith(AndroidJUnit4.class)
67 public class SystemBluetoothTest {
68     @Rule public final Expect expect = Expect.create();
69     private static final String TAG = SystemBluetoothTest.class.getSimpleName();
70 
71     private static final Duration OOB_TIMEOUT = Duration.ofSeconds(1);
72     private static final long DEFAULT_DISCOVERY_TIMEOUT_MS = 12800;
73     private static final int DISCOVERY_START_TIMEOUT = 500;
74 
75     private static final Context sContext =
76             InstrumentationRegistry.getInstrumentation().getContext();
77     private static final BluetoothAdapter sAdapter = BlockingBluetoothAdapter.getAdapter();
78     private static final UiAutomation sUiAutomation =
79             InstrumentationRegistry.getInstrumentation().getUiAutomation();
80 
81     @Before
setUp()82     public void setUp() throws Exception {
83         assumeTrue(sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH));
84 
85         sUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
86 
87         assertThat(BlockingBluetoothAdapter.enable()).isTrue();
88     }
89 
90     @After
tearDown()91     public void tearDown() {
92         sUiAutomation.dropShellPermissionIdentity();
93     }
94 
95     /** Test enable/disable silence mode and check whether the device is in correct state. */
96     @CddTest(requirements = {"7.4.3/C-2-1"})
97     @Test
silenceMode()98     public void silenceMode() {
99         BluetoothDevice device = sAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
100         assertThat(device.setSilenceMode(true)).isTrue();
101         assertThat(device.isInSilenceMode()).isFalse();
102 
103         assertThat(device.setSilenceMode(false)).isTrue();
104         assertThat(device.isInSilenceMode()).isFalse();
105     }
106 
107     /**
108      * Test whether the metadata would be stored in Bluetooth storage successfully, also test
109      * whether OnMetadataChangedListener would callback correct values when metadata is changed..
110      */
111     @CddTest(requirements = {"7.4.3/C-2-1"})
112     @Test
setGetMetadata()113     public void setGetMetadata() {
114         byte[] testByteData = "Test Data".getBytes();
115         BluetoothDevice device = sAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
116         BluetoothAdapter.OnMetadataChangedListener listener =
117                 new BluetoothAdapter.OnMetadataChangedListener() {
118                     @Override
119                     public void onMetadataChanged(BluetoothDevice dev, int key, byte[] value) {
120                         assertThat(dev).isEqualTo(device);
121                         assertThat(key).isEqualTo(BluetoothDevice.METADATA_MANUFACTURER_NAME);
122                         assertThat(value).isEqualTo(testByteData);
123                     }
124                 };
125 
126         assertThat(
127                         sAdapter.addOnMetadataChangedListener(
128                                 device, sContext.getMainExecutor(), listener))
129                 .isTrue();
130         assertThat(device.setMetadata(BluetoothDevice.METADATA_MANUFACTURER_NAME, testByteData))
131                 .isTrue();
132         assertThat(testByteData)
133                 .isEqualTo(device.getMetadata(BluetoothDevice.METADATA_MANUFACTURER_NAME));
134         assertThat(sAdapter.removeOnMetadataChangedListener(device, listener)).isTrue();
135     }
136 
137     @CddTest(requirements = {"7.4.3/C-2-1"})
138     @Test
discoveryEndMillis()139     public void discoveryEndMillis() {
140         boolean recoverOffState = false;
141         try {
142             if (!TestUtils.isLocationOn(sContext)) {
143                 recoverOffState = true;
144                 TestUtils.enableLocation(sContext);
145                 sUiAutomation.grantRuntimePermission(
146                         "android.bluetooth.cts", android.Manifest.permission.ACCESS_FINE_LOCATION);
147             }
148 
149             IntentFilter filter = new IntentFilter();
150             filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
151             filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
152 
153             BroadcastReceiver mockReceiver = mock(BroadcastReceiver.class);
154             sContext.registerReceiver(mockReceiver, filter);
155 
156             try (var p = Permissions.withPermissions(BLUETOOTH_SCAN)) {
157                 sAdapter.startDiscovery();
158                 // Wait for any of ACTION_DISCOVERY_STARTED intent, while holding BLUETOOTH_SCAN
159                 verify(mockReceiver, timeout(DISCOVERY_START_TIMEOUT)).onReceive(any(), any());
160             }
161 
162             long discoveryEndTime = sAdapter.getDiscoveryEndMillis();
163             long currentTime = System.currentTimeMillis();
164             assertThat(discoveryEndTime > currentTime).isTrue();
165             assertThat(discoveryEndTime - currentTime < DEFAULT_DISCOVERY_TIMEOUT_MS).isTrue();
166 
167             sContext.unregisterReceiver(mockReceiver);
168         } finally {
169             if (recoverOffState) {
170                 TestUtils.disableLocation(sContext);
171                 sUiAutomation.revokeRuntimePermission(
172                         "android.bluetooth.cts", android.Manifest.permission.ACCESS_FINE_LOCATION);
173             }
174         }
175     }
176 
177     /**
178      * Tests whether the static function BluetoothUuid#containsAnyUuid properly identifies whether
179      * the ParcelUuid arrays have at least one common element.
180      */
181     @CddTest(requirements = {"7.4.3/C-2-1"})
182     @Test
containsAnyUuid()183     public void containsAnyUuid() {
184         ParcelUuid[] deviceAUuids =
185                 new ParcelUuid[] {
186                     BluetoothUuid.A2DP_SOURCE,
187                     BluetoothUuid.HFP,
188                     BluetoothUuid.ADV_AUDIO_DIST,
189                     BluetoothUuid.AVRCP_CONTROLLER,
190                     BluetoothUuid.BASE_UUID,
191                     BluetoothUuid.HID,
192                     BluetoothUuid.HEARING_AID
193                 };
194         ParcelUuid[] deviceBUuids =
195                 new ParcelUuid[] {
196                     BluetoothUuid.A2DP_SINK,
197                     BluetoothUuid.BNEP,
198                     BluetoothUuid.AVRCP_TARGET,
199                     BluetoothUuid.HFP_AG,
200                     BluetoothUuid.HOGP,
201                     BluetoothUuid.HSP_AG
202                 };
203         ParcelUuid[] deviceCUuids =
204                 new ParcelUuid[] {
205                     BluetoothUuid.HSP,
206                     BluetoothUuid.MAP,
207                     BluetoothUuid.MAS,
208                     BluetoothUuid.MNS,
209                     BluetoothUuid.NAP,
210                     BluetoothUuid.OBEX_OBJECT_PUSH,
211                     BluetoothUuid.PANU,
212                     BluetoothUuid.PBAP_PCE,
213                     BluetoothUuid.PBAP_PSE,
214                     BluetoothUuid.SAP,
215                     BluetoothUuid.A2DP_SOURCE
216                 };
217         expect.that(BluetoothUuid.containsAnyUuid(null, null)).isTrue();
218         expect.that(BluetoothUuid.containsAnyUuid(new ParcelUuid[] {}, null)).isTrue();
219         expect.that(BluetoothUuid.containsAnyUuid(null, new ParcelUuid[] {})).isTrue();
220         expect.that(BluetoothUuid.containsAnyUuid(null, deviceAUuids)).isFalse();
221         expect.that(BluetoothUuid.containsAnyUuid(deviceAUuids, null)).isFalse();
222         expect.that(BluetoothUuid.containsAnyUuid(deviceAUuids, deviceBUuids)).isFalse();
223         expect.that(BluetoothUuid.containsAnyUuid(deviceAUuids, deviceCUuids)).isTrue();
224         expect.that(BluetoothUuid.containsAnyUuid(deviceBUuids, deviceBUuids)).isTrue();
225     }
226 
227     @CddTest(requirements = {"7.4.3/C-2-1"})
228     @Test
parseUuidFrom()229     public void parseUuidFrom() {
230         byte[] uuid16 = new byte[] {0x0B, 0x11};
231         assertThat(BluetoothUuid.parseUuidFrom(uuid16)).isEqualTo(BluetoothUuid.A2DP_SINK);
232 
233         byte[] uuid32 = new byte[] {(byte) 0xF0, (byte) 0xFD, 0x00, 0x00};
234         assertThat(BluetoothUuid.parseUuidFrom(uuid32)).isEqualTo(BluetoothUuid.HEARING_AID);
235 
236         byte[] uuid128 =
237                 new byte[] {
238                     (byte) 0xFB,
239                     0x34,
240                     (byte) 0x9B,
241                     0x5F,
242                     (byte) 0x80,
243                     0x00,
244                     0x00,
245                     (byte) 0x80,
246                     0x00,
247                     0x10,
248                     0x00,
249                     0x00,
250                     0x1F,
251                     0x11,
252                     0x00,
253                     0x00
254                 };
255         assertThat(BluetoothUuid.parseUuidFrom(uuid128)).isEqualTo(BluetoothUuid.HFP_AG);
256     }
257 
258     @CddTest(requirements = {"7.4.3/C-2-1"})
259     @Test
canBondWithoutDialog()260     public void canBondWithoutDialog() {
261         // Verify the method returns false on a device that doesn't meet the criteria
262         BluetoothDevice testDevice = sAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
263         assertThat(testDevice.canBondWithoutDialog()).isFalse();
264     }
265 
266     @CddTest(requirements = {"7.4.3/C-2-1"})
267     @Test
bleOnlyMode()268     public void bleOnlyMode() {
269         assumeTrue(TestUtils.isBleSupported(sContext));
270 
271         assertThat(BlockingBluetoothAdapter.disable(true)).isTrue();
272         assertThat(BlockingBluetoothAdapter.enableBLE(false)).isFalse();
273 
274         try {
275             assertThat(BlockingBluetoothAdapter.enableBLE(true)).isTrue();
276             assertThat(BlockingBluetoothAdapter.disableBLE()).isTrue();
277         } finally {
278             // Tests are running with the scan disabled (See AndroidTest.xml)
279             Settings.Global.putInt(
280                     sContext.getContentResolver(), Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0);
281         }
282     }
283 
284     @CddTest(requirements = {"7.4.3/C-2-1"})
285     @Test
setGetOwnAddressType()286     public void setGetOwnAddressType() {
287         assumeTrue(TestUtils.isBleSupported(sContext));
288 
289         AdvertisingSetParameters.Builder paramsBuilder = new AdvertisingSetParameters.Builder();
290         assertThat(paramsBuilder.setOwnAddressType(AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT))
291                 .isEqualTo(paramsBuilder);
292 
293         assertThat(paramsBuilder.build().getOwnAddressType())
294                 .isEqualTo(AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT);
295 
296         assertThat(paramsBuilder.setOwnAddressType(AdvertisingSetParameters.ADDRESS_TYPE_PUBLIC))
297                 .isEqualTo(paramsBuilder);
298         assertThat(paramsBuilder.build().getOwnAddressType())
299                 .isEqualTo(AdvertisingSetParameters.ADDRESS_TYPE_PUBLIC);
300 
301         assertThat(paramsBuilder.setOwnAddressType(AdvertisingSetParameters.ADDRESS_TYPE_RANDOM))
302                 .isEqualTo(paramsBuilder);
303         assertThat(paramsBuilder.build().getOwnAddressType())
304                 .isEqualTo(AdvertisingSetParameters.ADDRESS_TYPE_RANDOM);
305 
306         AdvertiseSettings.Builder settingsBuilder = new AdvertiseSettings.Builder();
307 
308         assertThat(settingsBuilder.setOwnAddressType(AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT))
309                 .isEqualTo(settingsBuilder);
310         assertThat(settingsBuilder.build().getOwnAddressType())
311                 .isEqualTo(AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT);
312 
313         assertThat(settingsBuilder.setOwnAddressType(AdvertisingSetParameters.ADDRESS_TYPE_PUBLIC))
314                 .isEqualTo(settingsBuilder);
315         assertThat(settingsBuilder.build().getOwnAddressType())
316                 .isEqualTo(AdvertisingSetParameters.ADDRESS_TYPE_PUBLIC);
317 
318         assertThat(settingsBuilder.setOwnAddressType(AdvertisingSetParameters.ADDRESS_TYPE_RANDOM))
319                 .isEqualTo(settingsBuilder);
320         assertThat(settingsBuilder.build().getOwnAddressType())
321                 .isEqualTo(AdvertisingSetParameters.ADDRESS_TYPE_RANDOM);
322     }
323 
324     @CddTest(requirements = {"7.4.3/C-2-1"})
325     @Test
getSupportedProfiles()326     public void getSupportedProfiles() {
327         assertThat(sAdapter.getSupportedProfiles()).isNotNull();
328     }
329 
330     @CddTest(requirements = {"7.4.3/C-2-1"})
331     @Test
enableNoAutoConnect()332     public void enableNoAutoConnect() {
333         // Assert that when Bluetooth is already enabled, the method immediately returns true
334         assertThat(sAdapter.enableNoAutoConnect()).isTrue();
335     }
336 
isBluetoothPersistedOff()337     private boolean isBluetoothPersistedOff() {
338         // A value of "0" in Settings.Global.BLUETOOTH_ON means the OFF state was persisted
339         return (Settings.Global.getInt(
340                         sContext.getContentResolver(), Settings.Global.BLUETOOTH_ON, -1)
341                 == 0);
342     }
343 
344     @CddTest(requirements = {"7.4.3/C-2-1"})
345     @Test
disableBluetoothPersistFalse()346     public void disableBluetoothPersistFalse() {
347         assertThat(BlockingBluetoothAdapter.disable(/* persist= */ false)).isTrue();
348         assertThat(isBluetoothPersistedOff()).isFalse();
349     }
350 
351     @CddTest(requirements = {"7.4.3/C-2-1"})
352     @Test
disableBluetoothPersistTrue()353     public void disableBluetoothPersistTrue() {
354         assertThat(BlockingBluetoothAdapter.disable(/* persist= */ true)).isTrue();
355         assertThat(isBluetoothPersistedOff()).isTrue();
356     }
357 
358     @CddTest(requirements = {"7.4.3/C-2-1"})
359     @Test
setLowLatencyAudioAllowed()360     public void setLowLatencyAudioAllowed() {
361         BluetoothDevice device = sAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
362 
363         assertThat(device.setLowLatencyAudioAllowed(true)).isTrue();
364         assertThat(device.setLowLatencyAudioAllowed(false)).isTrue();
365 
366         assertThat(BlockingBluetoothAdapter.disable(true)).isTrue();
367         assertThat(device.setLowLatencyAudioAllowed(true)).isFalse();
368         assertThat(device.setLowLatencyAudioAllowed(false)).isFalse();
369     }
370 
371     @CddTest(requirements = {"7.4.3/C-2-1"})
372     @Test
generateLocalOobData()373     public void generateLocalOobData() {
374         Executor executor =
375                 new Executor() {
376                     @Override
377                     public void execute(Runnable command) {
378                         command.run();
379                     }
380                 };
381         BluetoothAdapter.OobDataCallback callback = mock(BluetoothAdapter.OobDataCallback.class);
382 
383         // Invalid transport
384         assertThrows(
385                 IllegalArgumentException.class,
386                 () ->
387                         sAdapter.generateLocalOobData(
388                                 BluetoothDevice.TRANSPORT_AUTO, executor, callback));
389 
390         // Null callback
391         assertThrows(
392                 NullPointerException.class,
393                 () ->
394                         sAdapter.generateLocalOobData(
395                                 BluetoothDevice.TRANSPORT_BREDR, executor, null));
396 
397         sAdapter.generateLocalOobData(BluetoothDevice.TRANSPORT_BREDR, executor, callback);
398         verify(callback, timeout(OOB_TIMEOUT.toMillis())).onOobData(anyInt(), any());
399 
400         assertThat(BlockingBluetoothAdapter.disable(true)).isTrue();
401         sAdapter.generateLocalOobData(BluetoothDevice.TRANSPORT_BREDR, executor, callback);
402         verify(callback, timeout(OOB_TIMEOUT.toMillis()))
403                 .onError(eq(BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED));
404     }
405 
406     @CddTest(requirements = {"7.4.3/C-2-1"})
407     @Test
setScanMode()408     public void setScanMode() {
409 
410         assertThrows(IllegalArgumentException.class, () -> sAdapter.setScanMode(0));
411 
412         /* TODO(rahulsabnis): Fix the callback system so these work as intended
413         assertThat(sAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_NONE))
414                 .isEqualTo(BluetoothStatusCodes.SUCCESS);
415         assertThat(sAdapter.getScanMode()).isEqualTo(BluetoothAdapter.SCAN_MODE_NONE);
416         assertThat(sAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE))
417                 .isEqualTo(BluetoothStatusCodes.SUCCESS);
418         assertThat(sAdapter.getScanMode()).isEqualTo(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
419 
420         assertThat(sAdapter.setDiscoverableTimeout(Duration.ofSeconds(1)))
421                 .isEqualTo(BluetoothStatusCodes.SUCCESS);
422         assertThat(sAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE))
423                 .isEqualTo(BluetoothStatusCodes.SUCCESS);
424         assertThat(sAdapter.getScanMode())
425                 .isEqualTo(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
426         try {
427             Thread.sleep(1000);
428         } catch (InterruptedException e) {
429             e.printStackTrace();
430         }
431         assertThat(sAdapter.getScanMode()).isEqualTo(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
432         */
433 
434         assertThat(BlockingBluetoothAdapter.disable(true)).isTrue();
435         assertThat(sAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE))
436                 .isEqualTo(BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED);
437     }
438 }
439