1 /* 2 * Copyright (C) 2023 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; 18 19 import static android.bluetooth.BluetoothGatt.GATT_SUCCESS; 20 import static android.bluetooth.BluetoothProfile.STATE_CONNECTED; 21 import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED; 22 23 import static com.google.common.truth.Truth.assertThat; 24 25 import static org.hamcrest.Matchers.greaterThan; 26 import static org.junit.Assume.assumeThat; 27 import static org.mockito.Mockito.any; 28 import static org.mockito.Mockito.anyInt; 29 import static org.mockito.Mockito.eq; 30 import static org.mockito.Mockito.inOrder; 31 import static org.mockito.Mockito.mock; 32 import static org.mockito.Mockito.mockingDetails; 33 import static org.mockito.Mockito.never; 34 import static org.mockito.Mockito.timeout; 35 import static org.mockito.Mockito.verify; 36 37 import android.bluetooth.test_utils.EnableBluetoothRule; 38 import android.content.Context; 39 import android.os.SystemProperties; 40 import android.platform.test.annotations.RequiresFlagsEnabled; 41 import android.platform.test.flag.junit.CheckFlagsRule; 42 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 43 import android.util.Log; 44 45 import androidx.test.core.app.ApplicationProvider; 46 import androidx.test.platform.app.InstrumentationRegistry; 47 48 import com.android.bluetooth.flags.Flags; 49 50 import com.google.protobuf.ByteString; 51 import com.google.testing.junit.testparameterinjector.TestParameter; 52 import com.google.testing.junit.testparameterinjector.TestParameterInjector; 53 54 import org.junit.After; 55 import org.junit.Assume; 56 import org.junit.Before; 57 import org.junit.Rule; 58 import org.junit.Test; 59 import org.junit.runner.RunWith; 60 import org.mockito.AdditionalMatchers; 61 import org.mockito.ArgumentCaptor; 62 import org.mockito.InOrder; 63 import org.mockito.invocation.Invocation; 64 65 import pandora.GattProto.AttStatusCode; 66 import pandora.GattProto.GattCharacteristicParams; 67 import pandora.GattProto.GattServiceParams; 68 import pandora.GattProto.IndicateOnCharacteristicRequest; 69 import pandora.GattProto.IndicateOnCharacteristicResponse; 70 import pandora.GattProto.NotifyOnCharacteristicRequest; 71 import pandora.GattProto.NotifyOnCharacteristicResponse; 72 import pandora.GattProto.RegisterServiceRequest; 73 import pandora.HostProto.AdvertiseRequest; 74 import pandora.HostProto.AdvertiseResponse; 75 import pandora.HostProto.OwnAddressType; 76 77 import java.nio.charset.StandardCharsets; 78 import java.util.ArrayList; 79 import java.util.Collection; 80 import java.util.List; 81 import java.util.Set; 82 import java.util.UUID; 83 84 @RunWith(TestParameterInjector.class) 85 public class GattClientTest { 86 private static final String TAG = GattClientTest.class.getSimpleName(); 87 88 private static final int ANDROID_MTU = 517; 89 private static final int MTU_REQUESTED = 23; 90 private static final int ANOTHER_MTU_REQUESTED = 42; 91 private static final String NOTIFICATION_VALUE = "hello world"; 92 93 private static final UUID GAP_UUID = UUID.fromString("00001800-0000-1000-8000-00805f9b34fb"); 94 private static final UUID CCCD_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); 95 96 private static final UUID TEST_SERVICE_UUID = 97 UUID.fromString("00000000-0000-0000-0000-00000000000"); 98 private static final UUID TEST_CHARACTERISTIC_UUID = 99 UUID.fromString("00010001-0000-0000-0000-000000000000"); 100 101 private static final int MIN_CONN_INTERVAL_RELAXED = 102 SystemProperties.getInt("bluetooth.core.le.min_connection_interval_relaxed", 0x0018); 103 private static final int MAX_CONN_INTERVAL_RELAXED = 104 SystemProperties.getInt("bluetooth.core.le.max_connection_interval_relaxed", 0x0028); 105 106 @Rule(order = 0) 107 public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); 108 109 @Rule(order = 1) 110 public final PandoraDevice mBumble = new PandoraDevice(); 111 112 @Rule(order = 2) 113 public final EnableBluetoothRule mEnableBluetoothRule = new EnableBluetoothRule(false, true); 114 115 private final Context mContext = ApplicationProvider.getApplicationContext(); 116 private final BluetoothManager mManager = mContext.getSystemService(BluetoothManager.class); 117 private final BluetoothAdapter mAdapter = mManager.getAdapter(); 118 119 private Host mHost; 120 private BluetoothDevice mRemoteLeDevice; 121 122 @Before setUp()123 public void setUp() throws Exception { 124 InstrumentationRegistry.getInstrumentation() 125 .getUiAutomation() 126 .adoptShellPermissionIdentity(); 127 128 mHost = new Host(mContext); 129 mRemoteLeDevice = 130 mAdapter.getRemoteLeDevice( 131 Utils.BUMBLE_RANDOM_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM); 132 mRemoteLeDevice.removeBond(); 133 } 134 135 @After tearUp()136 public void tearUp() throws Exception { 137 InstrumentationRegistry.getInstrumentation() 138 .getUiAutomation() 139 .dropShellPermissionIdentity(); 140 Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices(); 141 if (bondedDevices.contains(mRemoteLeDevice)) { 142 mRemoteLeDevice.removeBond(); 143 } 144 mHost.close(); 145 } 146 147 @Test directConnectGattAfterClose()148 public void directConnectGattAfterClose() throws Exception { 149 advertiseWithBumble(); 150 151 BluetoothDevice device = 152 mAdapter.getRemoteLeDevice( 153 Utils.BUMBLE_RANDOM_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM); 154 155 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 156 BluetoothGatt gatt = device.connectGatt(mContext, false, gattCallback); 157 gatt.close(); 158 159 // Save the number of call in the callback to be checked later 160 Collection<Invocation> invocations = mockingDetails(gattCallback).getInvocations(); 161 int numberOfCalls = invocations.size(); 162 163 BluetoothGattCallback gattCallback2 = mock(BluetoothGattCallback.class); 164 BluetoothGatt gatt2 = device.connectGatt(mContext, false, gattCallback2); 165 verify(gattCallback2, timeout(1000)) 166 .onConnectionStateChange(any(), anyInt(), eq(STATE_CONNECTED)); 167 disconnectAndWaitDisconnection(gatt2, gattCallback2); 168 169 // After reconnecting, verify the first callback was not invoked. 170 Collection<Invocation> invocationsAfterSomeTimes = 171 mockingDetails(gattCallback).getInvocations(); 172 int numberOfCallsAfterSomeTimes = invocationsAfterSomeTimes.size(); 173 assertThat(numberOfCallsAfterSomeTimes).isEqualTo(numberOfCalls); 174 } 175 176 @Test fullGattClientLifecycle(@estParameter boolean autoConnect)177 public void fullGattClientLifecycle(@TestParameter boolean autoConnect) { 178 if (autoConnect) { 179 createLeBondAndWaitBonding(mRemoteLeDevice); 180 } 181 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 182 BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback, autoConnect); 183 disconnectAndWaitDisconnection(gatt, gattCallback); 184 } 185 186 @RequiresFlagsEnabled(Flags.FLAG_INITIAL_CONN_PARAMS_P1) 187 @Test onConnectionUpdatedIsCalledOnlyOnceForRelaxingConnectionParameters_noGattCache()188 public void onConnectionUpdatedIsCalledOnlyOnceForRelaxingConnectionParameters_noGattCache() { 189 int aggressiveConnectionThreshold = 190 SystemProperties.getInt("bluetooth.core.le.aggressive_connection_threshold", 2); 191 // This test is for the case where aggressive initial parameters are used. 192 assumeThat(aggressiveConnectionThreshold, greaterThan(0)); 193 194 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 195 ArgumentCaptor<Integer> connectionIntervalCaptor = ArgumentCaptor.forClass(Integer.class); 196 197 BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback, false); 198 199 // Wait until service discovery is done and parameters are relaxed. 200 verify(gattCallback, timeout(10_000).times(1)) 201 .onConnectionUpdated( 202 any(), connectionIntervalCaptor.capture(), anyInt(), anyInt(), anyInt()); 203 204 List<Integer> capturedConnectionIntervals = connectionIntervalCaptor.getAllValues(); 205 assertThat(capturedConnectionIntervals).hasSize(1); 206 207 // Since aggressive parameters are used in the initial connection, 208 // there should be only one connection parameters update event for relaxing them. 209 int relaxedConnIntervalAfterServiceDiscovery = capturedConnectionIntervals.get(0); 210 assertThat(relaxedConnIntervalAfterServiceDiscovery).isAtLeast(MIN_CONN_INTERVAL_RELAXED); 211 assertThat(relaxedConnIntervalAfterServiceDiscovery).isAtMost(MAX_CONN_INTERVAL_RELAXED); 212 213 disconnectAndWaitDisconnection(gatt, gattCallback); 214 } 215 216 @Test reconnectExistingClient()217 public void reconnectExistingClient() throws Exception { 218 advertiseWithBumble(); 219 220 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 221 InOrder inOrder = inOrder(gattCallback); 222 223 BluetoothGatt gatt = mRemoteLeDevice.connectGatt(mContext, false, gattCallback); 224 inOrder.verify(gattCallback, timeout(1000)) 225 .onConnectionStateChange(any(), anyInt(), eq(STATE_CONNECTED)); 226 227 gatt.disconnect(); 228 inOrder.verify(gattCallback, timeout(1000)) 229 .onConnectionStateChange(any(), anyInt(), eq(STATE_DISCONNECTED)); 230 231 gatt.connect(); 232 inOrder.verify(gattCallback, timeout(1000)) 233 .onConnectionStateChange(any(), anyInt(), eq(STATE_CONNECTED)); 234 235 // TODO(323889717): Fix callback being called after gatt.close(). This disconnect shouldn't 236 // be necessary. 237 gatt.disconnect(); 238 inOrder.verify(gattCallback, timeout(1000)) 239 .onConnectionStateChange(any(), anyInt(), eq(STATE_DISCONNECTED)); 240 gatt.close(); 241 } 242 243 @Test clientGattDiscoverServices()244 public void clientGattDiscoverServices() throws Exception { 245 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 246 BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback); 247 248 try { 249 gatt.discoverServices(); 250 verify(gattCallback, timeout(10000)).onServicesDiscovered(any(), eq(GATT_SUCCESS)); 251 252 assertThat(gatt.getServices().stream().map(BluetoothGattService::getUuid)) 253 .contains(GAP_UUID); 254 255 } finally { 256 disconnectAndWaitDisconnection(gatt, gattCallback); 257 } 258 } 259 260 @Test clientGattReadCharacteristics()261 public void clientGattReadCharacteristics() throws Exception { 262 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 263 BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback); 264 265 try { 266 gatt.discoverServices(); 267 verify(gattCallback, timeout(10000)).onServicesDiscovered(any(), eq(GATT_SUCCESS)); 268 269 BluetoothGattService firstService = gatt.getServices().get(0); 270 271 BluetoothGattCharacteristic firstCharacteristic = 272 firstService.getCharacteristics().get(0); 273 274 gatt.readCharacteristic(firstCharacteristic); 275 276 verify(gattCallback, timeout(5000)).onCharacteristicRead(any(), any(), any(), anyInt()); 277 278 } finally { 279 disconnectAndWaitDisconnection(gatt, gattCallback); 280 } 281 } 282 283 @Test clientGattWriteCharacteristic()284 public void clientGattWriteCharacteristic() throws Exception { 285 registerGattService(); 286 287 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 288 BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback); 289 290 try { 291 gatt.discoverServices(); 292 verify(gattCallback, timeout(10000)).onServicesDiscovered(any(), eq(GATT_SUCCESS)); 293 294 BluetoothGattCharacteristic characteristic = 295 gatt.getService(TEST_SERVICE_UUID).getCharacteristic(TEST_CHARACTERISTIC_UUID); 296 297 byte[] newValue = new byte[] {13}; 298 299 gatt.writeCharacteristic( 300 characteristic, newValue, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); 301 302 verify(gattCallback, timeout(5000)) 303 .onCharacteristicWrite(any(), eq(characteristic), eq(GATT_SUCCESS)); 304 305 } finally { 306 disconnectAndWaitDisconnection(gatt, gattCallback); 307 } 308 } 309 310 @Test clientGattNotifyOrIndicateCharacteristic(@estParameter boolean isIndicate)311 public void clientGattNotifyOrIndicateCharacteristic(@TestParameter boolean isIndicate) 312 throws Exception { 313 registerNotificationIndicationGattService(isIndicate); 314 315 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 316 BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback); 317 318 try { 319 gatt.discoverServices(); 320 verify(gattCallback, timeout(10000)).onServicesDiscovered(any(), eq(GATT_SUCCESS)); 321 322 BluetoothGattCharacteristic characteristic = 323 gatt.getService(TEST_SERVICE_UUID).getCharacteristic(TEST_CHARACTERISTIC_UUID); 324 325 BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CCCD_UUID); 326 descriptor.setValue( 327 isIndicate 328 ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE 329 : BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); 330 assertThat(gatt.writeDescriptor(descriptor)).isTrue(); 331 332 verify(gattCallback, timeout(5000)) 333 .onDescriptorWrite(any(), eq(descriptor), eq(GATT_SUCCESS)); 334 335 gatt.setCharacteristicNotification(characteristic, true); 336 337 if (isIndicate) { 338 Log.i(TAG, "Triggering characteristic indication"); 339 triggerCharacteristicIndication(characteristic.getInstanceId()); 340 } else { 341 Log.i(TAG, "Triggering characteristic notification"); 342 triggerCharacteristicNotification(characteristic.getInstanceId()); 343 } 344 345 verify(gattCallback, timeout(5000)) 346 .onCharacteristicChanged( 347 any(), any(), eq(NOTIFICATION_VALUE.getBytes(StandardCharsets.UTF_8))); 348 349 } finally { 350 disconnectAndWaitDisconnection(gatt, gattCallback); 351 } 352 } 353 354 @Test connectTimeout()355 public void connectTimeout() { 356 BluetoothDevice device = 357 mAdapter.getRemoteLeDevice( 358 Utils.BUMBLE_RANDOM_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM); 359 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 360 361 // Connecting to a device not advertising results in connection timeout after 30 seconds 362 device.connectGatt(mContext, false, gattCallback); 363 364 verify(gattCallback, timeout(35000)) 365 .onConnectionStateChange( 366 any(), eq(BluetoothGatt.GATT_CONNECTION_TIMEOUT), eq(STATE_DISCONNECTED)); 367 } 368 369 @Test consecutiveWriteCharacteristicFails_thenSuccess()370 public void consecutiveWriteCharacteristicFails_thenSuccess() throws Exception { 371 registerGattService(); 372 373 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 374 BluetoothGattCallback gattCallback2 = mock(BluetoothGattCallback.class); 375 376 BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback); 377 BluetoothGatt gatt2 = connectGattAndWaitConnection(gattCallback2); 378 379 try { 380 gatt.discoverServices(); 381 gatt2.discoverServices(); 382 verify(gattCallback, timeout(10000)).onServicesDiscovered(any(), eq(GATT_SUCCESS)); 383 verify(gattCallback2, timeout(10000)).onServicesDiscovered(any(), eq(GATT_SUCCESS)); 384 385 BluetoothGattCharacteristic characteristic = 386 gatt.getService(TEST_SERVICE_UUID).getCharacteristic(TEST_CHARACTERISTIC_UUID); 387 388 BluetoothGattCharacteristic characteristic2 = 389 gatt2.getService(TEST_SERVICE_UUID).getCharacteristic(TEST_CHARACTERISTIC_UUID); 390 391 byte[] newValue = new byte[] {13}; 392 393 gatt.writeCharacteristic( 394 characteristic, newValue, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); 395 396 // TODO: b/324355496 - Make the test consistent when Bumble supports holding a response. 397 // Skip the test if the second write succeeded. 398 Assume.assumeFalse( 399 gatt2.writeCharacteristic( 400 characteristic2, 401 newValue, 402 BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) 403 == BluetoothStatusCodes.SUCCESS); 404 405 verify(gattCallback, timeout(5000)) 406 .onCharacteristicWrite(any(), eq(characteristic), eq(GATT_SUCCESS)); 407 verify(gattCallback2, never()) 408 .onCharacteristicWrite(any(), eq(characteristic), eq(GATT_SUCCESS)); 409 410 assertThat( 411 gatt2.writeCharacteristic( 412 characteristic2, 413 newValue, 414 BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)) 415 .isEqualTo(BluetoothStatusCodes.SUCCESS); 416 verify(gattCallback2, timeout(5000)) 417 .onCharacteristicWrite(any(), eq(characteristic2), eq(GATT_SUCCESS)); 418 } finally { 419 disconnectAndWaitDisconnection(gatt, gattCallback); 420 disconnectAndWaitDisconnection(gatt2, gattCallback2); 421 } 422 } 423 424 @Test connectMultiple_closeOne_shouldSuccess()425 public void connectMultiple_closeOne_shouldSuccess() { 426 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 427 BluetoothGattCallback gattCallback2 = mock(BluetoothGattCallback.class); 428 429 advertiseWithBumble(); 430 BluetoothDevice device = 431 mAdapter.getRemoteLeDevice( 432 Utils.BUMBLE_RANDOM_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM); 433 BluetoothGatt gatt = device.connectGatt(mContext, false, gattCallback); 434 BluetoothGatt gatt2 = device.connectGatt(mContext, false, gattCallback2); 435 436 try { 437 verify(gattCallback2, timeout(1000)) 438 .onConnectionStateChange(eq(gatt2), eq(GATT_SUCCESS), eq(STATE_CONNECTED)); 439 440 gatt.disconnect(); 441 gatt.close(); 442 } finally { 443 gatt2.disconnect(); 444 gatt2.close(); 445 } 446 } 447 registerGattService()448 private void registerGattService() { 449 GattCharacteristicParams characteristicParams = 450 GattCharacteristicParams.newBuilder() 451 .setProperties( 452 BluetoothGattCharacteristic.PROPERTY_READ 453 | BluetoothGattCharacteristic.PROPERTY_WRITE) 454 .setUuid(TEST_CHARACTERISTIC_UUID.toString()) 455 .build(); 456 457 GattServiceParams serviceParams = 458 GattServiceParams.newBuilder() 459 .addCharacteristics(characteristicParams) 460 .setUuid(TEST_SERVICE_UUID.toString()) 461 .build(); 462 463 RegisterServiceRequest request = 464 RegisterServiceRequest.newBuilder().setService(serviceParams).build(); 465 466 mBumble.gattBlocking().registerService(request); 467 } 468 registerNotificationIndicationGattService(boolean isIndicate)469 private void registerNotificationIndicationGattService(boolean isIndicate) { 470 GattCharacteristicParams characteristicParams = 471 GattCharacteristicParams.newBuilder() 472 .setProperties( 473 isIndicate 474 ? BluetoothGattCharacteristic.PROPERTY_INDICATE 475 : BluetoothGattCharacteristic.PROPERTY_NOTIFY) 476 .setUuid(TEST_CHARACTERISTIC_UUID.toString()) 477 .build(); 478 479 GattServiceParams serviceParams = 480 GattServiceParams.newBuilder() 481 .addCharacteristics(characteristicParams) 482 .setUuid(TEST_SERVICE_UUID.toString()) 483 .build(); 484 485 RegisterServiceRequest request = 486 RegisterServiceRequest.newBuilder().setService(serviceParams).build(); 487 488 mBumble.gattBlocking().registerService(request); 489 } 490 triggerCharacteristicNotification(int instanceId)491 private void triggerCharacteristicNotification(int instanceId) { 492 NotifyOnCharacteristicRequest req = 493 NotifyOnCharacteristicRequest.newBuilder() 494 .setHandle(instanceId) 495 .setValue(ByteString.copyFromUtf8(NOTIFICATION_VALUE)) 496 .build(); 497 NotifyOnCharacteristicResponse resp = mBumble.gattBlocking().notifyOnCharacteristic(req); 498 assertThat(resp.getStatus()).isEqualTo(AttStatusCode.SUCCESS); 499 } 500 triggerCharacteristicIndication(int instanceId)501 private void triggerCharacteristicIndication(int instanceId) { 502 IndicateOnCharacteristicRequest req = 503 IndicateOnCharacteristicRequest.newBuilder() 504 .setHandle(instanceId) 505 .setValue(ByteString.copyFromUtf8(NOTIFICATION_VALUE)) 506 .build(); 507 IndicateOnCharacteristicResponse resp = 508 mBumble.gattBlocking().indicateOnCharacteristic(req); 509 assertThat(resp.getStatus()).isEqualTo(AttStatusCode.SUCCESS); 510 } 511 512 @Test multipleGattClientsSeparateInteractions()513 public void multipleGattClientsSeparateInteractions() throws Exception { 514 advertiseWithBumble(); 515 516 BluetoothDevice device = 517 mAdapter.getRemoteLeDevice( 518 Utils.BUMBLE_RANDOM_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM); 519 520 BluetoothGattCallback gattCallbackA = mock(BluetoothGattCallback.class); 521 BluetoothGattCallback gattCallbackB = mock(BluetoothGattCallback.class); 522 InOrder inOrder = inOrder(gattCallbackA, gattCallbackB); 523 524 BluetoothGatt gattA = device.connectGatt(mContext, false, gattCallbackA); 525 inOrder.verify(gattCallbackA, timeout(1000)) 526 .onConnectionStateChange(any(), anyInt(), eq(BluetoothProfile.STATE_CONNECTED)); 527 528 BluetoothGatt gattB = device.connectGatt(mContext, false, gattCallbackB); 529 inOrder.verify(gattCallbackB, timeout(1000)) 530 .onConnectionStateChange(any(), anyInt(), eq(BluetoothProfile.STATE_CONNECTED)); 531 532 gattA.disconnect(); 533 inOrder.verify(gattCallbackA, timeout(1000)) 534 .onConnectionStateChange(any(), anyInt(), eq(BluetoothProfile.STATE_DISCONNECTED)); 535 536 gattA.connect(); 537 inOrder.verify(gattCallbackA, timeout(1000)) 538 .onConnectionStateChange(any(), anyInt(), eq(BluetoothProfile.STATE_CONNECTED)); 539 540 gattB.disconnect(); 541 inOrder.verify(gattCallbackB, timeout(1000)) 542 .onConnectionStateChange(any(), anyInt(), eq(BluetoothProfile.STATE_DISCONNECTED)); 543 544 gattB.close(); 545 546 gattA.disconnect(); 547 inOrder.verify(gattCallbackA, timeout(1000)) 548 .onConnectionStateChange(any(), anyInt(), eq(BluetoothProfile.STATE_DISCONNECTED)); 549 550 gattA.connect(); 551 inOrder.verify(gattCallbackA, timeout(1000)) 552 .onConnectionStateChange(any(), anyInt(), eq(BluetoothProfile.STATE_CONNECTED)); 553 554 gattA.close(); 555 } 556 advertiseWithBumble()557 private void advertiseWithBumble() { 558 AdvertiseRequest request = 559 AdvertiseRequest.newBuilder() 560 .setLegacy(true) 561 .setConnectable(true) 562 .setOwnAddressType(OwnAddressType.RANDOM) 563 .build(); 564 565 StreamObserverSpliterator<AdvertiseResponse> responseObserver = 566 new StreamObserverSpliterator<>(); 567 568 mBumble.host().advertise(request, responseObserver); 569 } 570 connectGattAndWaitConnection(BluetoothGattCallback callback)571 private BluetoothGatt connectGattAndWaitConnection(BluetoothGattCallback callback) { 572 return connectGattAndWaitConnection(callback, /* autoConnect= */ false); 573 } 574 connectGattAndWaitConnection( BluetoothGattCallback callback, boolean autoConnect)575 private BluetoothGatt connectGattAndWaitConnection( 576 BluetoothGattCallback callback, boolean autoConnect) { 577 final int status = GATT_SUCCESS; 578 final int state = STATE_CONNECTED; 579 580 advertiseWithBumble(); 581 582 BluetoothGatt gatt = mRemoteLeDevice.connectGatt(mContext, autoConnect, callback); 583 verify(callback, timeout(1000)).onConnectionStateChange(eq(gatt), eq(status), eq(state)); 584 585 return gatt; 586 } 587 588 /** Tries to connect GATT, it could fail and return null. */ tryConnectGatt(BluetoothGattCallback callback, boolean autoConnect)589 private BluetoothGatt tryConnectGatt(BluetoothGattCallback callback, boolean autoConnect) { 590 advertiseWithBumble(); 591 592 BluetoothGatt gatt = mRemoteLeDevice.connectGatt(mContext, autoConnect, callback); 593 594 ArgumentCaptor<Integer> statusCaptor = ArgumentCaptor.forClass(Integer.class); 595 ArgumentCaptor<Integer> stateCaptor = ArgumentCaptor.forClass(Integer.class); 596 verify(callback, timeout(1000)) 597 .onConnectionStateChange(eq(gatt), statusCaptor.capture(), stateCaptor.capture()); 598 599 if (statusCaptor.getValue() == GATT_SUCCESS && stateCaptor.getValue() == STATE_CONNECTED) { 600 return gatt; 601 } 602 gatt.close(); 603 return null; 604 } 605 disconnectAndWaitDisconnection( BluetoothGatt gatt, BluetoothGattCallback callback)606 private void disconnectAndWaitDisconnection( 607 BluetoothGatt gatt, BluetoothGattCallback callback) { 608 final int state = STATE_DISCONNECTED; 609 gatt.disconnect(); 610 verify(callback, timeout(1000)).onConnectionStateChange(eq(gatt), anyInt(), eq(state)); 611 612 gatt.close(); 613 gatt = null; 614 } 615 616 @Test 617 @RequiresFlagsEnabled(Flags.FLAG_GATT_CALLBACK_ON_FAILURE) requestMtu_invalidParameter_isFalse()618 public void requestMtu_invalidParameter_isFalse() { 619 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 620 BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback); 621 622 try { 623 assertThat(gatt.requestMtu(1024)).isTrue(); 624 // status should be 0x87 (GATT_ILLEGAL_PARAMETER) but not defined. 625 verify(gattCallback, timeout(5000).atLeast(1)) 626 .onMtuChanged( 627 eq(gatt), 628 anyInt(), 629 AdditionalMatchers.not(eq(BluetoothGatt.GATT_SUCCESS))); 630 } finally { 631 disconnectAndWaitDisconnection(gatt, gattCallback); 632 } 633 } 634 635 @Test requestMtu_once_isSuccess()636 public void requestMtu_once_isSuccess() { 637 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 638 BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback); 639 640 try { 641 assertThat(gatt.requestMtu(MTU_REQUESTED)).isTrue(); 642 // Check that only the ANDROID_MTU is returned, not the MTU_REQUESTED 643 verify(gattCallback, timeout(5000)) 644 .onMtuChanged(eq(gatt), eq(ANDROID_MTU), eq(GATT_SUCCESS)); 645 } finally { 646 disconnectAndWaitDisconnection(gatt, gattCallback); 647 } 648 } 649 650 @Test requestMtu_multipleTimeFromSameClient_isRejected()651 public void requestMtu_multipleTimeFromSameClient_isRejected() { 652 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 653 BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback); 654 655 try { 656 assertThat(gatt.requestMtu(MTU_REQUESTED)).isTrue(); 657 // Check that only the ANDROID_MTU is returned, not the MTU_REQUESTED 658 verify(gattCallback, timeout(5000)) 659 .onMtuChanged(eq(gatt), eq(ANDROID_MTU), eq(GATT_SUCCESS)); 660 661 assertThat(gatt.requestMtu(ANOTHER_MTU_REQUESTED)).isTrue(); 662 verify(gattCallback, timeout(5000).times(2)) 663 .onMtuChanged(eq(gatt), eq(ANDROID_MTU), eq(GATT_SUCCESS)); 664 } finally { 665 disconnectAndWaitDisconnection(gatt, gattCallback); 666 } 667 } 668 669 @Test requestMtu_onceFromMultipleClient_secondIsSuccessWithoutUpdate()670 public void requestMtu_onceFromMultipleClient_secondIsSuccessWithoutUpdate() { 671 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 672 BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback); 673 674 try { 675 assertThat(gatt.requestMtu(MTU_REQUESTED)).isTrue(); 676 verify(gattCallback, timeout(5000)) 677 .onMtuChanged(eq(gatt), eq(ANDROID_MTU), eq(GATT_SUCCESS)); 678 679 BluetoothGattCallback gattCallback2 = mock(BluetoothGattCallback.class); 680 BluetoothGatt gatt2 = connectGattAndWaitConnection(gattCallback2); 681 try { 682 // first callback because there is already a connected device 683 verify(gattCallback2, timeout(9000)) 684 .onMtuChanged(eq(gatt2), eq(ANDROID_MTU), eq(GATT_SUCCESS)); 685 assertThat(gatt2.requestMtu(ANOTHER_MTU_REQUESTED)).isTrue(); 686 verify(gattCallback2, timeout(9000).times(2)) 687 .onMtuChanged(eq(gatt2), eq(ANDROID_MTU), eq(GATT_SUCCESS)); 688 } finally { 689 disconnectAndWaitDisconnection(gatt2, gattCallback2); 690 } 691 } finally { 692 disconnectAndWaitDisconnection(gatt, gattCallback); 693 } 694 } 695 696 // Check if we can have 100 simultaneous clients 697 @Test connectGatt_multipleClients()698 public void connectGatt_multipleClients() { 699 registerGattService(); 700 701 List<BluetoothGatt> gatts = new ArrayList<>(); 702 boolean failed = false; 703 final int repeatTimes = 100; 704 705 try { 706 for (int i = 0; i < repeatTimes; i++) { 707 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 708 BluetoothGatt gatt = tryConnectGatt(gattCallback, false); 709 // If it fails, close an existing gatt instance and try again. 710 if (gatt == null) { 711 failed = true; 712 BluetoothGatt connectedGatt = gatts.remove(0); 713 connectedGatt.disconnect(); 714 connectedGatt.close(); 715 gattCallback = mock(BluetoothGattCallback.class); 716 gatt = connectGattAndWaitConnection(gattCallback); 717 } 718 gatts.add(gatt); 719 gatt.discoverServices(); 720 verify(gattCallback, timeout(10000)).onServicesDiscovered(any(), eq(GATT_SUCCESS)); 721 722 BluetoothGattCharacteristic characteristic = 723 gatt.getService(TEST_SERVICE_UUID) 724 .getCharacteristic(TEST_CHARACTERISTIC_UUID); 725 gatt.readCharacteristic(characteristic); 726 verify(gattCallback, timeout(5000)) 727 .onCharacteristicRead(any(), any(), any(), anyInt()); 728 } 729 } finally { 730 for (BluetoothGatt gatt : gatts) { 731 gatt.disconnect(); 732 gatt.close(); 733 } 734 } 735 // We should fail because we reached the limit. 736 assertThat(failed).isTrue(); 737 } 738 739 @Test writeCharacteristic_disconnected_shouldNotCrash()740 public void writeCharacteristic_disconnected_shouldNotCrash() { 741 registerGattService(); 742 743 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 744 745 BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback); 746 747 try { 748 gatt.discoverServices(); 749 verify(gattCallback, timeout(10000)).onServicesDiscovered(any(), eq(GATT_SUCCESS)); 750 751 BluetoothGattCharacteristic characteristic = 752 gatt.getService(TEST_SERVICE_UUID).getCharacteristic(TEST_CHARACTERISTIC_UUID); 753 754 byte[] newValue = new byte[] {13}; 755 756 gatt.writeCharacteristic( 757 characteristic, newValue, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); 758 // TODO(b/370607862): disconnect from the remote 759 gatt.disconnect(); 760 gatt.close(); 761 } finally { 762 // it's okay to close twice. 763 gatt.close(); 764 } 765 } 766 767 @Test 768 @RequiresFlagsEnabled(Flags.FLAG_UNREGISTER_GATT_CLIENT_DISCONNECTED) connectAndDisconnectManyClientsWithoutClose()769 public void connectAndDisconnectManyClientsWithoutClose() throws Exception { 770 advertiseWithBumble(); 771 772 List<BluetoothGatt> gatts = new ArrayList<>(); 773 try { 774 for (int i = 0; i < 100; i++) { 775 BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); 776 InOrder inOrder = inOrder(gattCallback); 777 778 BluetoothGatt gatt = mRemoteLeDevice.connectGatt(mContext, false, gattCallback); 779 gatts.add(gatt); 780 781 inOrder.verify(gattCallback, timeout(1000)) 782 .onConnectionStateChange(any(), anyInt(), eq(STATE_CONNECTED)); 783 784 gatt.disconnect(); 785 inOrder.verify(gattCallback, timeout(1000)) 786 .onConnectionStateChange(any(), anyInt(), eq(STATE_DISCONNECTED)); 787 788 gatt.connect(); 789 inOrder.verify(gattCallback, timeout(1000)) 790 .onConnectionStateChange(any(), anyInt(), eq(STATE_CONNECTED)); 791 792 gatt.disconnect(); 793 inOrder.verify(gattCallback, timeout(1000)) 794 .onConnectionStateChange(any(), anyInt(), eq(STATE_DISCONNECTED)); 795 } 796 } finally { 797 for (BluetoothGatt gatt : gatts) { 798 gatt.close(); 799 } 800 } 801 } 802 createLeBondAndWaitBonding(BluetoothDevice device)803 private void createLeBondAndWaitBonding(BluetoothDevice device) { 804 advertiseWithBumble(); 805 mHost.createBondAndVerify(device); 806 } 807 } 808