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