1 /* 2 * Copyright (C) 2009 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 org.junit.Assert.assertEquals; 24 import static org.junit.Assert.assertFalse; 25 import static org.junit.Assert.assertNotNull; 26 import static org.junit.Assert.assertNotSame; 27 import static org.junit.Assert.assertNull; 28 import static org.junit.Assert.assertThrows; 29 import static org.junit.Assert.assertTrue; 30 import static org.junit.Assume.assumeTrue; 31 import static org.mockito.Mockito.mock; 32 33 import android.app.UiAutomation; 34 import android.bluetooth.BluetoothActivityEnergyInfo; 35 import android.bluetooth.BluetoothAdapter; 36 import android.bluetooth.BluetoothDevice; 37 import android.bluetooth.BluetoothManager; 38 import android.bluetooth.BluetoothProfile; 39 import android.bluetooth.BluetoothQualityReport; 40 import android.bluetooth.BluetoothServerSocket; 41 import android.bluetooth.BluetoothStatusCodes; 42 import android.bluetooth.test_utils.Permissions; 43 import android.content.BroadcastReceiver; 44 import android.content.Context; 45 import android.content.Intent; 46 import android.content.IntentFilter; 47 import android.content.pm.PackageManager; 48 import android.os.Build; 49 import android.os.Bundle; 50 import android.os.SystemProperties; 51 import android.platform.test.annotations.RequiresFlagsEnabled; 52 import android.platform.test.flag.junit.CheckFlagsRule; 53 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 54 import android.util.Log; 55 56 import androidx.test.ext.junit.runners.AndroidJUnit4; 57 import androidx.test.filters.MediumTest; 58 import androidx.test.platform.app.InstrumentationRegistry; 59 60 import com.android.bluetooth.flags.Flags; 61 import com.android.compatibility.common.util.ApiLevelUtil; 62 63 import org.junit.After; 64 import org.junit.Before; 65 import org.junit.Rule; 66 import org.junit.Test; 67 import org.junit.runner.RunWith; 68 69 import java.io.IOException; 70 import java.time.Duration; 71 import java.util.List; 72 import java.util.Set; 73 import java.util.UUID; 74 import java.util.concurrent.Executor; 75 import java.util.concurrent.TimeUnit; 76 import java.util.concurrent.locks.Condition; 77 import java.util.concurrent.locks.ReentrantLock; 78 79 /** Very basic test, just of the static methods of {@link BluetoothAdapter}. */ 80 @RunWith(AndroidJUnit4.class) 81 @MediumTest 82 public class BluetoothAdapterTest { 83 private static final String TAG = "BluetoothAdapterTest"; 84 private static final int SET_NAME_TIMEOUT = 5000; // ms timeout for setting adapter name 85 private static final String ENABLE_DUAL_MODE_AUDIO = 86 "persist.bluetooth.enable_dual_mode_audio"; 87 88 @Rule 89 public final CheckFlagsRule mCheckFlagsRule = 90 DeviceFlagsValueProvider.createCheckFlagsRule(); 91 92 private Context mContext; 93 private boolean mHasBluetooth; 94 private ReentrantLock mAdapterNameChangedlock; 95 private Condition mConditionAdapterNameChanged; 96 private boolean mIsAdapterNameChanged; 97 98 private BluetoothAdapter mAdapter; 99 private UiAutomation mUiAutomation; 100 101 @Before setUp()102 public void setUp() { 103 mContext = InstrumentationRegistry.getInstrumentation().getContext(); 104 mHasBluetooth = mContext.getPackageManager().hasSystemFeature( 105 PackageManager.FEATURE_BLUETOOTH); 106 if (mHasBluetooth) { 107 mAdapter = mContext.getSystemService(BluetoothManager.class).getAdapter(); 108 assertNotNull(mAdapter); 109 mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 110 mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT); 111 } 112 mAdapterNameChangedlock = new ReentrantLock(); 113 mConditionAdapterNameChanged = mAdapterNameChangedlock.newCondition(); 114 mIsAdapterNameChanged = false; 115 } 116 117 @After tearDown()118 public void tearDown() { 119 if (mHasBluetooth) { 120 mUiAutomation.dropShellPermissionIdentity(); 121 } 122 } 123 124 @Test getDefaultAdapter()125 public void getDefaultAdapter() { 126 /* 127 * Note: If the target doesn't support Bluetooth at all, then 128 * this method should return null. 129 */ 130 if (mHasBluetooth) { 131 assertNotNull(BluetoothAdapter.getDefaultAdapter()); 132 } else { 133 assertNull(BluetoothAdapter.getDefaultAdapter()); 134 } 135 } 136 137 @Test checkBluetoothAddress()138 public void checkBluetoothAddress() { 139 // Can't be null. 140 assertFalse(BluetoothAdapter.checkBluetoothAddress(null)); 141 142 // Must be 17 characters long. 143 assertFalse(BluetoothAdapter.checkBluetoothAddress("")); 144 assertFalse(BluetoothAdapter.checkBluetoothAddress("0")); 145 assertFalse(BluetoothAdapter.checkBluetoothAddress("00")); 146 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:")); 147 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:0")); 148 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00")); 149 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:")); 150 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:0")); 151 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00")); 152 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:")); 153 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:0")); 154 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00")); 155 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:")); 156 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:0")); 157 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00")); 158 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00:")); 159 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00:0")); 160 161 // Must have colons between octets. 162 assertFalse(BluetoothAdapter.checkBluetoothAddress("00x00:00:00:00:00")); 163 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00.00:00:00:00")); 164 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00-00:00:00")); 165 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00900:00")); 166 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00?00")); 167 168 // Hex letters must be uppercase. 169 assertFalse(BluetoothAdapter.checkBluetoothAddress("a0:00:00:00:00:00")); 170 assertFalse(BluetoothAdapter.checkBluetoothAddress("0b:00:00:00:00:00")); 171 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:c0:00:00:00:00")); 172 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:0d:00:00:00:00")); 173 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:e0:00:00:00")); 174 assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:0f:00:00:00")); 175 176 assertTrue(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00:00")); 177 assertTrue(BluetoothAdapter.checkBluetoothAddress("12:34:56:78:9A:BC")); 178 assertTrue(BluetoothAdapter.checkBluetoothAddress("DE:F0:FE:DC:B8:76")); 179 } 180 181 /** Checks enable(), disable(), getState(), isEnabled() */ 182 @Test enableDisable()183 public void enableDisable() { 184 assumeTrue(mHasBluetooth); 185 186 for (int i = 0; i < 5; i++) { 187 assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext)); 188 assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext)); 189 } 190 } 191 192 @Test getAddress()193 public void getAddress() { 194 assumeTrue(mHasBluetooth); 195 196 assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext)); 197 assertTrue(BluetoothAdapter.checkBluetoothAddress(mAdapter.getAddress())); 198 199 mUiAutomation.dropShellPermissionIdentity(); 200 assertThrows(SecurityException.class, () -> mAdapter.getAddress()); 201 202 } 203 204 @Test setName_getName()205 public void setName_getName() { 206 assumeTrue(mHasBluetooth); 207 208 assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext)); 209 210 IntentFilter filter = new IntentFilter(); 211 filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED); 212 filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 213 mContext.registerReceiver(mAdapterNameChangeReceiver, filter); 214 215 String name = mAdapter.getName(); 216 assertNotNull(name); 217 218 // Check renaming the adapter 219 String genericName = "Generic Device 1"; 220 mIsAdapterNameChanged = false; 221 assertTrue(mAdapter.setName(genericName)); 222 assertTrue(waitForAdapterNameChange()); 223 mIsAdapterNameChanged = false; 224 assertEquals(genericName, mAdapter.getName()); 225 226 // Check setting adapter back to original name 227 assertTrue(mAdapter.setName(name)); 228 assertTrue(waitForAdapterNameChange()); 229 mIsAdapterNameChanged = false; 230 assertEquals(name, mAdapter.getName()); 231 232 mUiAutomation.dropShellPermissionIdentity(); 233 assertThrows(SecurityException.class, () -> mAdapter.setName("The name")); 234 assertThrows(SecurityException.class, () -> mAdapter.getName()); 235 } 236 237 @Test getBondedDevices()238 public void getBondedDevices() { 239 assumeTrue(mHasBluetooth); 240 241 assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext)); 242 243 // empty value is returned when Bluetooth is disabled 244 Set<BluetoothDevice> devices = mAdapter.getBondedDevices(); 245 assertNotNull(devices); 246 assertTrue(devices.isEmpty()); 247 248 assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext)); 249 devices = mAdapter.getBondedDevices(); 250 assertNotNull(devices); 251 for (BluetoothDevice device : devices) { 252 assertTrue(BluetoothAdapter.checkBluetoothAddress(device.getAddress())); 253 } 254 255 mUiAutomation.dropShellPermissionIdentity(); 256 assertThrows(SecurityException.class, () -> mAdapter.getBondedDevices()); 257 258 } 259 260 @Test getProfileConnectionState()261 public void getProfileConnectionState() { 262 assumeTrue(mHasBluetooth); 263 264 mUiAutomation.dropShellPermissionIdentity(); 265 // getProfileConnectionState is caching it's return value and cts test doesn't know how to 266 // deal with it 267 // assertThrows(SecurityException.class, 268 // () -> mAdapter.getProfileConnectionState(BluetoothProfile.A2DP)); 269 mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT); 270 assertEquals(mAdapter.getProfileConnectionState(BluetoothProfile.A2DP), 271 BluetoothAdapter.STATE_DISCONNECTED); 272 assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext)); 273 assertEquals(mAdapter.getProfileConnectionState(BluetoothProfile.A2DP), 274 BluetoothAdapter.STATE_DISCONNECTED); 275 } 276 277 @Test getRemoteDevice()278 public void getRemoteDevice() { 279 assumeTrue(mHasBluetooth); 280 281 // getRemoteDevice() should work even with Bluetooth disabled 282 assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext)); 283 mUiAutomation.dropShellPermissionIdentity(); 284 285 // test bad addresses 286 assertThrows(IllegalArgumentException.class, () -> mAdapter.getRemoteDevice((String) null)); 287 assertThrows(IllegalArgumentException.class, () -> 288 mAdapter.getRemoteDevice("00:00:00:00:00:00:00:00")); 289 assertThrows(IllegalArgumentException.class, () -> mAdapter.getRemoteDevice((byte[]) null)); 290 assertThrows(IllegalArgumentException.class, () -> 291 mAdapter.getRemoteDevice(new byte[] {0x00, 0x00, 0x00, 0x00, 0x00})); 292 293 // test success 294 BluetoothDevice device = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC"); 295 assertNotNull(device); 296 assertEquals("00:11:22:AA:BB:CC", device.getAddress()); 297 device = mAdapter.getRemoteDevice( 298 new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}); 299 assertNotNull(device); 300 assertEquals("01:02:03:04:05:06", device.getAddress()); 301 } 302 303 @Test getRemoteLeDevice()304 public void getRemoteLeDevice() { 305 assumeTrue(mHasBluetooth); 306 307 // getRemoteLeDevice() should work even with Bluetooth disabled 308 assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext)); 309 mUiAutomation.dropShellPermissionIdentity(); 310 311 // test bad addresses 312 assertThrows(IllegalArgumentException.class, 313 () -> mAdapter.getRemoteLeDevice((String) null, 314 BluetoothDevice.ADDRESS_TYPE_PUBLIC)); 315 assertThrows(IllegalArgumentException.class, 316 () -> mAdapter.getRemoteLeDevice("01:02:03:04:05:06:07:08", 317 BluetoothDevice.ADDRESS_TYPE_PUBLIC)); 318 assertThrows(IllegalArgumentException.class, 319 () -> mAdapter.getRemoteLeDevice("01:02:03:04:05", 320 BluetoothDevice.ADDRESS_TYPE_PUBLIC)); 321 assertThrows(IllegalArgumentException.class, 322 () -> mAdapter.getRemoteLeDevice("00:01:02:03:04:05", 323 BluetoothDevice.ADDRESS_TYPE_RANDOM + 1)); 324 assertThrows(IllegalArgumentException.class, 325 () -> mAdapter.getRemoteLeDevice("00:01:02:03:04:05", 326 BluetoothDevice.ADDRESS_TYPE_PUBLIC - 1)); 327 328 // test success 329 BluetoothDevice device = mAdapter.getRemoteLeDevice("00:11:22:AA:BB:CC", 330 BluetoothDevice.ADDRESS_TYPE_PUBLIC); 331 assertNotNull(device); 332 assertEquals("00:11:22:AA:BB:CC", device.getAddress()); 333 device = mAdapter.getRemoteLeDevice("01:02:03:04:05:06", 334 BluetoothDevice.ADDRESS_TYPE_RANDOM); 335 assertNotNull(device); 336 assertEquals("01:02:03:04:05:06", device.getAddress()); 337 } 338 339 @Test isLeAudioSupported()340 public void isLeAudioSupported() throws IOException { 341 assumeTrue(mHasBluetooth); 342 343 assertNotSame(BluetoothStatusCodes.ERROR_UNKNOWN, mAdapter.isLeAudioSupported()); 344 } 345 346 @Test isLeAudioBroadcastSourceSupported()347 public void isLeAudioBroadcastSourceSupported() throws IOException { 348 assumeTrue(mHasBluetooth); 349 350 assertNotSame(BluetoothStatusCodes.ERROR_UNKNOWN, 351 mAdapter.isLeAudioBroadcastSourceSupported()); 352 } 353 354 @Test isLeAudioBroadcastAssistantSupported()355 public void isLeAudioBroadcastAssistantSupported() throws IOException { 356 assumeTrue(mHasBluetooth); 357 358 assertNotSame(BluetoothStatusCodes.ERROR_UNKNOWN, 359 mAdapter.isLeAudioBroadcastAssistantSupported()); 360 } 361 362 @Test isDistanceMeasurementSupported()363 public void isDistanceMeasurementSupported() throws IOException { 364 assumeTrue(mHasBluetooth); 365 366 TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED); 367 assertNotSame(BluetoothStatusCodes.ERROR_UNKNOWN, 368 mAdapter.isDistanceMeasurementSupported()); 369 TestUtils.dropPermissionAsShellUid(); 370 } 371 372 @Test getMaxConnectedAudioDevices()373 public void getMaxConnectedAudioDevices() { 374 assumeTrue(mHasBluetooth); 375 376 // Defined in com.android.bluetooth.btservice.AdapterProperties 377 int maxConnectedAudioDevicesLowerBound = 1; 378 // Defined in com.android.bluetooth.btservice.AdapterProperties 379 int maxConnectedAudioDevicesUpperBound = 5; 380 381 assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext)); 382 assertTrue(mAdapter.getMaxConnectedAudioDevices() >= maxConnectedAudioDevicesLowerBound); 383 assertTrue(mAdapter.getMaxConnectedAudioDevices() <= maxConnectedAudioDevicesUpperBound); 384 385 mUiAutomation.dropShellPermissionIdentity(); 386 assertThrows(SecurityException.class, () -> mAdapter.getMaxConnectedAudioDevices()); 387 } 388 389 @Test listenUsingRfcommWithServiceRecord()390 public void listenUsingRfcommWithServiceRecord() throws IOException { 391 assumeTrue(mHasBluetooth); 392 393 assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext)); 394 BluetoothServerSocket socket = mAdapter.listenUsingRfcommWithServiceRecord( 395 "test", UUID.randomUUID()); 396 assertNotNull(socket); 397 socket.close(); 398 399 mUiAutomation.dropShellPermissionIdentity(); 400 assertThrows(SecurityException.class, () -> mAdapter.listenUsingRfcommWithServiceRecord( 401 "test", UUID.randomUUID())); 402 } 403 404 @Test discoverableTimeout()405 public void discoverableTimeout() { 406 assumeTrue(mHasBluetooth); 407 408 Duration minutes = Duration.ofMinutes(2); 409 410 assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext)); 411 assertEquals(null, mAdapter.getDiscoverableTimeout()); 412 assertEquals(BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, 413 mAdapter.setDiscoverableTimeout(minutes)); 414 415 assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext)); 416 TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED); 417 assertThrows(IllegalArgumentException.class, () -> mAdapter.setDiscoverableTimeout( 418 Duration.ofDays(25000))); 419 Permissions.enforceEachPermissions( 420 () -> mAdapter.setDiscoverableTimeout(minutes), 421 List.of(BLUETOOTH_PRIVILEGED, BLUETOOTH_SCAN)); 422 try (var p = Permissions.withPermissions(BLUETOOTH_SCAN, BLUETOOTH_PRIVILEGED)) { 423 assertEquals(BluetoothStatusCodes.SUCCESS, 424 mAdapter.setDiscoverableTimeout(minutes)); 425 assertEquals(minutes, mAdapter.getDiscoverableTimeout()); 426 } 427 } 428 429 @Test getConnectionState()430 public void getConnectionState() { 431 assumeTrue(mHasBluetooth); 432 433 // Verify return value if Bluetooth is not enabled 434 assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext)); 435 assertEquals(BluetoothProfile.STATE_DISCONNECTED, mAdapter.getConnectionState()); 436 } 437 438 @Test getMostRecentlyConnectedDevices()439 public void getMostRecentlyConnectedDevices() { 440 assumeTrue(mHasBluetooth); 441 442 assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext)); 443 444 // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED 445 assertThrows(SecurityException.class, () -> mAdapter.getMostRecentlyConnectedDevices()); 446 447 // Verify return value if Bluetooth is not enabled 448 assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext)); 449 List<BluetoothDevice> devices = mAdapter.getMostRecentlyConnectedDevices(); 450 assertTrue(devices.isEmpty()); 451 } 452 453 @Test getUuids()454 public void getUuids() { 455 assumeTrue(mHasBluetooth); 456 457 assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext)); 458 459 // Verify return value without permission.BLUETOOTH_CONNECT 460 mUiAutomation.dropShellPermissionIdentity(); 461 assertThrows(SecurityException.class, () -> mAdapter.getUuidsList()); 462 mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT); 463 464 assertNotNull(mAdapter.getUuidsList()); 465 assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext)); 466 467 // Verify return value if Bluetooth is not enabled 468 assertEquals(0, mAdapter.getUuidsList().size()); 469 470 } 471 472 @Test nameForState()473 public void nameForState() { 474 assertEquals("ON", BluetoothAdapter.nameForState(BluetoothAdapter.STATE_ON)); 475 assertEquals("OFF", BluetoothAdapter.nameForState(BluetoothAdapter.STATE_OFF)); 476 assertEquals("TURNING_ON", 477 BluetoothAdapter.nameForState(BluetoothAdapter.STATE_TURNING_ON)); 478 assertEquals("TURNING_OFF", 479 BluetoothAdapter.nameForState(BluetoothAdapter.STATE_TURNING_OFF)); 480 481 assertEquals("BLE_ON", BluetoothAdapter.nameForState(BluetoothAdapter.STATE_BLE_ON)); 482 483 // Check value before state range 484 for (int state = 0; state < BluetoothAdapter.STATE_OFF; state++) { 485 assertEquals("?!?!? (" + state + ")", BluetoothAdapter.nameForState(state)); 486 } 487 // Check value after state range (skip TURNING_OFF) 488 for (int state = BluetoothAdapter.STATE_BLE_ON + 2; state < 100; state++) { 489 assertEquals("?!?!? (" + state + ")", BluetoothAdapter.nameForState(state)); 490 } 491 } 492 493 @Test BluetoothConnectionCallback_disconnectReasonText()494 public void BluetoothConnectionCallback_disconnectReasonText() { 495 assertEquals("Reason unknown", BluetoothAdapter.BluetoothConnectionCallback 496 .disconnectReasonToString(BluetoothStatusCodes.ERROR_UNKNOWN)); 497 } 498 499 @Test registerBluetoothConnectionCallback()500 public void registerBluetoothConnectionCallback() { 501 assumeTrue(mHasBluetooth); 502 503 Executor executor = mContext.getMainExecutor(); 504 BluetoothAdapter.BluetoothConnectionCallback callback = 505 mock(BluetoothAdapter.BluetoothConnectionCallback.class); 506 507 // placeholder call for coverage 508 callback.onDeviceConnected(null); 509 callback.onDeviceDisconnected(null, BluetoothStatusCodes.ERROR_UNKNOWN); 510 511 // Verify parameter 512 assertFalse(mAdapter.registerBluetoothConnectionCallback(null, callback)); 513 assertFalse(mAdapter.registerBluetoothConnectionCallback(executor, null)); 514 assertFalse(mAdapter.unregisterBluetoothConnectionCallback(null)); 515 516 try (var p = Permissions.withPermissions(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED)) { 517 assertTrue(mAdapter.registerBluetoothConnectionCallback(executor, callback)); 518 assertTrue(mAdapter.unregisterBluetoothConnectionCallback(callback)); 519 } 520 } 521 522 @Test requestControllerActivityEnergyInfo()523 public void requestControllerActivityEnergyInfo() { 524 assumeTrue(mHasBluetooth); 525 526 BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback callback = 527 new BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback() { 528 @Override 529 public void onBluetoothActivityEnergyInfoAvailable( 530 BluetoothActivityEnergyInfo info) { 531 assertNotNull(info); 532 } 533 534 @Override 535 public void onBluetoothActivityEnergyInfoError(int errorCode) {} 536 }; 537 538 // Verify parameter 539 assertThrows(NullPointerException.class, 540 () -> mAdapter.requestControllerActivityEnergyInfo(null, callback)); 541 } 542 543 @Test clearBluetooth()544 public void clearBluetooth() { 545 assumeTrue(mHasBluetooth); 546 547 assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext)); 548 549 // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED 550 assertThrows(SecurityException.class, () -> mAdapter.clearBluetooth()); 551 mUiAutomation.dropShellPermissionIdentity(); 552 // Verify throws SecurityException without permission.BLUETOOTH_CONNECT 553 assertThrows(SecurityException.class, () -> mAdapter.clearBluetooth()); 554 555 mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT); 556 assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext)); 557 // Verify throws RuntimeException when trying to save sysprop for later (permission denied) 558 assertThrows(RuntimeException.class, () -> mAdapter.clearBluetooth()); 559 } 560 561 @Test BluetoothProfile_getConnectionStateName()562 public void BluetoothProfile_getConnectionStateName() { 563 assumeTrue(mHasBluetooth); 564 565 assertEquals("STATE_DISCONNECTED", 566 BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_DISCONNECTED)); 567 assertEquals("STATE_CONNECTED", 568 BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_CONNECTED)); 569 assertEquals("STATE_CONNECTING", 570 BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_CONNECTING)); 571 assertEquals("STATE_CONNECTED", 572 BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_CONNECTED)); 573 assertEquals("STATE_DISCONNECTING", 574 BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_DISCONNECTING)); 575 assertEquals("STATE_UNKNOWN", 576 BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_DISCONNECTING + 1)); 577 } 578 579 @Test BluetoothProfile_getProfileName()580 public void BluetoothProfile_getProfileName() { 581 assertEquals("HEADSET", 582 BluetoothProfile.getProfileName(BluetoothProfile.HEADSET)); 583 assertEquals("A2DP", 584 BluetoothProfile.getProfileName(BluetoothProfile.A2DP)); 585 assertEquals("HID_HOST", 586 BluetoothProfile.getProfileName(BluetoothProfile.HID_HOST)); 587 assertEquals("PAN", 588 BluetoothProfile.getProfileName(BluetoothProfile.PAN)); 589 assertEquals("PBAP", 590 BluetoothProfile.getProfileName(BluetoothProfile.PBAP)); 591 assertEquals("GATT", 592 BluetoothProfile.getProfileName(BluetoothProfile.GATT)); 593 assertEquals("GATT_SERVER", 594 BluetoothProfile.getProfileName(BluetoothProfile.GATT_SERVER)); 595 assertEquals("MAP", 596 BluetoothProfile.getProfileName(BluetoothProfile.MAP)); 597 assertEquals("SAP", 598 BluetoothProfile.getProfileName(BluetoothProfile.SAP)); 599 assertEquals("A2DP_SINK", 600 BluetoothProfile.getProfileName(BluetoothProfile.A2DP_SINK)); 601 assertEquals("AVRCP_CONTROLLER", 602 BluetoothProfile.getProfileName(BluetoothProfile.AVRCP_CONTROLLER)); 603 // assertEquals("AVRCP", 604 // BluetoothProfile.getProfileName(BluetoothProfile.AVRCP)); 605 assertEquals("HEADSET_CLIENT", 606 BluetoothProfile.getProfileName(BluetoothProfile.HEADSET_CLIENT)); 607 assertEquals("PBAP_CLIENT", 608 BluetoothProfile.getProfileName(BluetoothProfile.PBAP_CLIENT)); 609 assertEquals("MAP_CLIENT", 610 BluetoothProfile.getProfileName(BluetoothProfile.MAP_CLIENT)); 611 assertEquals("HID_DEVICE", 612 BluetoothProfile.getProfileName(BluetoothProfile.HID_DEVICE)); 613 assertEquals("OPP", 614 BluetoothProfile.getProfileName(BluetoothProfile.OPP)); 615 assertEquals("HEARING_AID", 616 BluetoothProfile.getProfileName(BluetoothProfile.HEARING_AID)); 617 assertEquals("LE_AUDIO", 618 BluetoothProfile.getProfileName(BluetoothProfile.LE_AUDIO)); 619 assertEquals("HAP_CLIENT", 620 BluetoothProfile.getProfileName(BluetoothProfile.HAP_CLIENT)); 621 622 if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) { 623 return; 624 } 625 626 assertEquals("VOLUME_CONTROL", 627 BluetoothProfile.getProfileName(BluetoothProfile.VOLUME_CONTROL)); 628 assertEquals("CSIP_SET_COORDINATOR", 629 BluetoothProfile.getProfileName(BluetoothProfile.CSIP_SET_COORDINATOR)); 630 assertEquals("LE_AUDIO_BROADCAST", 631 BluetoothProfile.getProfileName(BluetoothProfile.LE_AUDIO_BROADCAST)); 632 assertEquals("LE_AUDIO_BROADCAST_ASSISTANT", 633 BluetoothProfile.getProfileName(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)); 634 } 635 636 @Test 637 @RequiresFlagsEnabled(Flags.FLAG_AUTO_ON_FEATURE) autoOnApi()638 public void autoOnApi() { 639 assumeTrue(mHasBluetooth); 640 641 assertThrows(SecurityException.class, () -> mAdapter.isAutoOnSupported()); 642 assertThrows(SecurityException.class, () -> mAdapter.isAutoOnEnabled()); 643 assertThrows(SecurityException.class, () -> mAdapter.setAutoOnEnabled(false)); 644 645 TestUtils.adoptPermissionAsShellUid(BLUETOOTH_PRIVILEGED); 646 647 // Not all devices support the auto on feature 648 assumeTrue(mAdapter.isAutoOnSupported()); 649 650 mAdapter.setAutoOnEnabled(false); 651 assertEquals(false, mAdapter.isAutoOnEnabled()); 652 653 mAdapter.setAutoOnEnabled(true); 654 assertEquals(true, mAdapter.isAutoOnEnabled()); 655 } 656 657 @Test getSetBluetoothHciSnoopLoggingMode()658 public void getSetBluetoothHciSnoopLoggingMode() { 659 assumeTrue(mHasBluetooth); 660 661 assertThrows(SecurityException.class, () -> mAdapter 662 .setBluetoothHciSnoopLoggingMode(BluetoothAdapter.BT_SNOOP_LOG_MODE_FULL)); 663 assertThrows(SecurityException.class, () -> mAdapter 664 .getBluetoothHciSnoopLoggingMode()); 665 666 TestUtils.adoptPermissionAsShellUid(BLUETOOTH_PRIVILEGED); 667 668 assertThrows(IllegalArgumentException.class, () -> mAdapter 669 .setBluetoothHciSnoopLoggingMode(-1)); 670 671 assertEquals(BluetoothStatusCodes.SUCCESS, mAdapter 672 .setBluetoothHciSnoopLoggingMode(BluetoothAdapter.BT_SNOOP_LOG_MODE_FULL)); 673 assertEquals(mAdapter.getBluetoothHciSnoopLoggingMode(), 674 BluetoothAdapter.BT_SNOOP_LOG_MODE_FULL); 675 676 assertEquals(BluetoothStatusCodes.SUCCESS, mAdapter 677 .setBluetoothHciSnoopLoggingMode(BluetoothAdapter.BT_SNOOP_LOG_MODE_FILTERED)); 678 assertEquals(mAdapter.getBluetoothHciSnoopLoggingMode(), 679 BluetoothAdapter.BT_SNOOP_LOG_MODE_FILTERED); 680 681 assertEquals(BluetoothStatusCodes.SUCCESS, mAdapter 682 .setBluetoothHciSnoopLoggingMode(BluetoothAdapter.BT_SNOOP_LOG_MODE_DISABLED)); 683 assertEquals(mAdapter.getBluetoothHciSnoopLoggingMode(), 684 BluetoothAdapter.BT_SNOOP_LOG_MODE_DISABLED); 685 686 } 687 688 @Test setPreferredAudioProfiles_getPreferredAudioProfiles()689 public void setPreferredAudioProfiles_getPreferredAudioProfiles() { 690 assumeTrue(mHasBluetooth); 691 692 assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext)); 693 String deviceAddress = "00:11:22:AA:BB:CC"; 694 BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress); 695 696 Bundle preferences = new Bundle(); 697 preferences.putInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY, BluetoothProfile.HEADSET); 698 699 // Test invalid input 700 assertThrows(NullPointerException.class, () -> 701 mAdapter.setPreferredAudioProfiles(device, null)); 702 assertThrows(IllegalArgumentException.class, 703 () -> mAdapter.setPreferredAudioProfiles(device, preferences)); 704 assertThrows(NullPointerException.class, () -> mAdapter.getPreferredAudioProfiles(null)); 705 706 preferences.putInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY, BluetoothProfile.HID_HOST); 707 assertThrows(IllegalArgumentException.class, 708 () -> mAdapter.setPreferredAudioProfiles(device, preferences)); 709 710 preferences.putInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY, BluetoothProfile.LE_AUDIO); 711 preferences.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.A2DP); 712 assertThrows(IllegalArgumentException.class, 713 () -> mAdapter.setPreferredAudioProfiles(device, preferences)); 714 715 preferences.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.GATT); 716 assertThrows(IllegalArgumentException.class, 717 () -> mAdapter.setPreferredAudioProfiles(device, preferences)); 718 719 preferences.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.HEADSET); 720 721 assertThrows(NullPointerException.class, () -> 722 mAdapter.setPreferredAudioProfiles(null, preferences)); 723 724 // Check what happens when the device is not bonded 725 assertTrue(mAdapter.getPreferredAudioProfiles(device).isEmpty()); 726 assertEquals(BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED, 727 mAdapter.setPreferredAudioProfiles(device, preferences)); 728 } 729 730 @Test preferredAudioProfileCallbacks()731 public void preferredAudioProfileCallbacks() { 732 assumeTrue(mHasBluetooth); 733 734 assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext)); 735 String deviceAddress = "00:11:22:AA:BB:CC"; 736 BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress); 737 738 Executor executor = mContext.getMainExecutor(); 739 BluetoothAdapter.PreferredAudioProfilesChangedCallback callback = 740 new BluetoothAdapter.PreferredAudioProfilesChangedCallback() { 741 @Override 742 public void onPreferredAudioProfilesChanged( 743 @androidx.annotation.NonNull BluetoothDevice device, 744 @androidx.annotation.NonNull Bundle preferredAudioProfiles, int status) {} 745 }; 746 747 callback.onPreferredAudioProfilesChanged(device, Bundle.EMPTY, 748 BluetoothStatusCodes.SUCCESS); 749 750 assertThrows(NullPointerException.class, () -> 751 mAdapter.registerPreferredAudioProfilesChangedCallback(null, callback)); 752 assertThrows(NullPointerException.class, () -> 753 mAdapter.registerPreferredAudioProfilesChangedCallback(executor, null)); 754 assertThrows(NullPointerException.class, () -> 755 mAdapter.unregisterPreferredAudioProfilesChangedCallback(null)); 756 757 // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission 758 assertThrows(SecurityException.class, () -> 759 mAdapter.registerPreferredAudioProfilesChangedCallback(executor, callback)); 760 assertThrows(IllegalArgumentException.class, () -> 761 mAdapter.unregisterPreferredAudioProfilesChangedCallback(callback)); 762 763 mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED); 764 765 if (isDualModeAudioEnabled()) { 766 assertEquals(BluetoothStatusCodes.SUCCESS, 767 mAdapter.registerPreferredAudioProfilesChangedCallback(executor, callback)); 768 assertEquals(BluetoothStatusCodes.SUCCESS, 769 mAdapter.unregisterPreferredAudioProfilesChangedCallback(callback)); 770 } else { 771 assertEquals(BluetoothStatusCodes.FEATURE_NOT_SUPPORTED, 772 mAdapter.registerPreferredAudioProfilesChangedCallback(executor, callback)); 773 assertThrows(IllegalArgumentException.class, () -> 774 mAdapter.unregisterPreferredAudioProfilesChangedCallback(callback)); 775 } 776 } 777 778 @Test bluetoothQualityReportReadyCallbacks()779 public void bluetoothQualityReportReadyCallbacks() { 780 assumeTrue(mHasBluetooth); 781 782 assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext)); 783 String deviceAddress = "00:11:22:AA:BB:CC"; 784 BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress); 785 786 Executor executor = mContext.getMainExecutor(); 787 BluetoothAdapter.BluetoothQualityReportReadyCallback callback = 788 new BluetoothAdapter.BluetoothQualityReportReadyCallback() { 789 @Override 790 public void onBluetoothQualityReportReady( 791 @androidx.annotation.NonNull BluetoothDevice device, 792 @androidx.annotation.NonNull BluetoothQualityReport bluetoothQualityReport, 793 int status) {} 794 }; 795 796 BluetoothQualityReport bqr = 797 BluetoothQualityReportTest.getBqr(BluetoothQualityReport.QUALITY_REPORT_ID_MONITOR); 798 799 callback.onBluetoothQualityReportReady(device, bqr, 800 BluetoothStatusCodes.SUCCESS); 801 802 assertThrows(NullPointerException.class, () -> 803 mAdapter.registerBluetoothQualityReportReadyCallback(null, callback)); 804 assertThrows(NullPointerException.class, () -> 805 mAdapter.registerBluetoothQualityReportReadyCallback(executor, null)); 806 assertThrows(NullPointerException.class, () -> 807 mAdapter.unregisterBluetoothQualityReportReadyCallback(null)); 808 809 // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission 810 assertThrows(SecurityException.class, () -> 811 mAdapter.registerBluetoothQualityReportReadyCallback(executor, callback)); 812 assertThrows(IllegalArgumentException.class, () -> 813 mAdapter.unregisterBluetoothQualityReportReadyCallback(callback)); 814 815 mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED); 816 817 // Try the happy path 818 assertEquals(BluetoothStatusCodes.SUCCESS, 819 mAdapter.registerBluetoothQualityReportReadyCallback(executor, callback)); 820 assertEquals(BluetoothStatusCodes.SUCCESS, 821 mAdapter.unregisterBluetoothQualityReportReadyCallback(callback)); 822 } 823 824 @Test notifyActiveDeviceChangeApplied()825 public void notifyActiveDeviceChangeApplied() { 826 assumeTrue(mHasBluetooth); 827 828 assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext)); 829 String deviceAddress = "00:11:22:AA:BB:CC"; 830 BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress); 831 832 assertThrows(NullPointerException.class, () -> 833 mAdapter.notifyActiveDeviceChangeApplied(null)); 834 835 assertEquals(BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED, 836 mAdapter.notifyActiveDeviceChangeApplied(device)); 837 } 838 isDualModeAudioEnabled()839 private boolean isDualModeAudioEnabled() { 840 return SystemProperties.getBoolean(ENABLE_DUAL_MODE_AUDIO, false); 841 } 842 waitForAdapterNameChange()843 private boolean waitForAdapterNameChange() { 844 mAdapterNameChangedlock.lock(); 845 try { 846 // Wait for the Adapter name to be changed 847 while (!mIsAdapterNameChanged) { 848 if (!mConditionAdapterNameChanged.await( 849 SET_NAME_TIMEOUT, TimeUnit.MILLISECONDS)) { 850 Log.e(TAG, "Timeout while waiting for adapter name change"); 851 break; 852 } 853 } 854 } catch (InterruptedException e) { 855 Log.e(TAG, "waitForAdapterNameChange: interrupted"); 856 } finally { 857 mAdapterNameChangedlock.unlock(); 858 } 859 return mIsAdapterNameChanged; 860 } 861 862 private final BroadcastReceiver mAdapterNameChangeReceiver = new BroadcastReceiver() { 863 @Override 864 public void onReceive(Context context, Intent intent) { 865 String action = intent.getAction(); 866 if (action.equals(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)) { 867 mAdapterNameChangedlock.lock(); 868 mIsAdapterNameChanged = true; 869 try { 870 mConditionAdapterNameChanged.signal(); 871 } catch (IllegalMonitorStateException ex) { 872 } finally { 873 mAdapterNameChangedlock.unlock(); 874 } 875 } 876 } 877 }; 878 } 879