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