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