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