1 /* 2 * Copyright (C) 2021 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 com.android.server.nearby.common.bluetooth.gatt; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.mockito.ArgumentMatchers.any; 22 import static org.mockito.ArgumentMatchers.anyInt; 23 import static org.mockito.ArgumentMatchers.anyLong; 24 import static org.mockito.ArgumentMatchers.eq; 25 import static org.mockito.ArgumentMatchers.isA; 26 import static org.mockito.Mockito.doThrow; 27 import static org.mockito.Mockito.never; 28 import static org.mockito.Mockito.reset; 29 import static org.mockito.Mockito.times; 30 import static org.mockito.Mockito.verify; 31 import static org.mockito.Mockito.verifyNoMoreInteractions; 32 import static org.mockito.Mockito.when; 33 import static org.mockito.MockitoAnnotations.initMocks; 34 35 import android.bluetooth.BluetoothGattCharacteristic; 36 import android.bluetooth.BluetoothGattDescriptor; 37 import android.bluetooth.BluetoothGattService; 38 import android.bluetooth.BluetoothStatusCodes; 39 import android.os.Build.VERSION; 40 import android.os.Build.VERSION_CODES; 41 42 import androidx.test.filters.SdkSuppress; 43 44 import com.android.server.nearby.common.bluetooth.BluetoothConsts; 45 import com.android.server.nearby.common.bluetooth.BluetoothException; 46 import com.android.server.nearby.common.bluetooth.BluetoothGattException; 47 import com.android.server.nearby.common.bluetooth.ReservedUuids; 48 import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection.ChangeObserver; 49 import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattHelper.OperationType; 50 import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice; 51 import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattWrapper; 52 import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor; 53 import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.Operation; 54 import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.SynchronousOperation; 55 56 import junit.framework.TestCase; 57 58 import org.mockito.ArgumentCaptor; 59 import org.mockito.ArgumentMatchers; 60 import org.mockito.Captor; 61 import org.mockito.Mock; 62 63 import java.util.Arrays; 64 import java.util.UUID; 65 import java.util.concurrent.TimeUnit; 66 67 /** 68 * Unit tests for {@link BluetoothGattConnection}. 69 */ 70 public class BluetoothGattConnectionTest extends TestCase { 71 72 private static final UUID SERVICE_UUID = UUID.randomUUID(); 73 private static final UUID CHARACTERISTIC_UUID = UUID.randomUUID(); 74 private static final UUID DESCRIPTOR_UUID = UUID.randomUUID(); 75 private static final byte[] DATA = "data".getBytes(); 76 private static final int RSSI = -63; 77 private static final int CONNECTION_PRIORITY = 128; 78 private static final int MTU_REQUEST = 512; 79 private static final BluetoothGattHelper.ConnectionOptions CONNECTION_OPTIONS = 80 BluetoothGattHelper.ConnectionOptions.builder().build(); 81 82 @Mock 83 private BluetoothGattWrapper mMockBluetoothGattWrapper; 84 @Mock 85 private BluetoothDevice mMockBluetoothDevice; 86 @Mock 87 private BluetoothOperationExecutor mMockBluetoothOperationExecutor; 88 @Mock 89 private BluetoothGattService mMockBluetoothGattService; 90 @Mock 91 private BluetoothGattService mMockBluetoothGattService2; 92 @Mock 93 private BluetoothGattCharacteristic mMockBluetoothGattCharacteristic; 94 @Mock 95 private BluetoothGattCharacteristic mMockBluetoothGattCharacteristic2; 96 @Mock 97 private BluetoothGattDescriptor mMockBluetoothGattDescriptor; 98 @Mock 99 private BluetoothGattConnection.CharacteristicChangeListener mMockCharChangeListener; 100 @Mock 101 private BluetoothGattConnection.ChangeObserver mMockChangeObserver; 102 @Mock 103 private BluetoothGattConnection.ConnectionCloseListener mMockConnectionCloseListener; 104 105 @Captor 106 private ArgumentCaptor<Operation<?>> mOperationCaptor; 107 @Captor 108 private ArgumentCaptor<SynchronousOperation<?>> mSynchronousOperationCaptor; 109 @Captor 110 private ArgumentCaptor<BluetoothGattCharacteristic> mCharacteristicCaptor; 111 @Captor 112 private ArgumentCaptor<BluetoothGattDescriptor> mDescriptorCaptor; 113 114 private BluetoothGattConnection mBluetoothGattConnection; 115 116 @Override setUp()117 protected void setUp() throws Exception { 118 super.setUp(); 119 120 initMocks(this); 121 122 mBluetoothGattConnection = new BluetoothGattConnection( 123 mMockBluetoothGattWrapper, 124 mMockBluetoothOperationExecutor, 125 CONNECTION_OPTIONS); 126 mBluetoothGattConnection.onConnected(); 127 128 when(mMockBluetoothGattWrapper.getDevice()).thenReturn(mMockBluetoothDevice); 129 when(mMockBluetoothGattWrapper.discoverServices()).thenReturn(true); 130 when(mMockBluetoothGattWrapper.refresh()).thenReturn(true); 131 when(mMockBluetoothGattWrapper.readCharacteristic(mMockBluetoothGattCharacteristic)) 132 .thenReturn(true); 133 when(mMockBluetoothGattWrapper 134 .writeCharacteristic(ArgumentMatchers.<BluetoothGattCharacteristic>any(), any(), 135 anyInt())) 136 .thenReturn(BluetoothStatusCodes.SUCCESS); 137 when(mMockBluetoothGattWrapper.readDescriptor(mMockBluetoothGattDescriptor)) 138 .thenReturn(true); 139 when(mMockBluetoothGattWrapper.writeDescriptor( 140 ArgumentMatchers.<BluetoothGattDescriptor>any(), any())) 141 .thenReturn(BluetoothStatusCodes.SUCCESS); 142 when(mMockBluetoothGattWrapper.readRemoteRssi()).thenReturn(true); 143 when(mMockBluetoothGattWrapper.requestConnectionPriority(CONNECTION_PRIORITY)) 144 .thenReturn(true); 145 when(mMockBluetoothGattWrapper.requestMtu(MTU_REQUEST)).thenReturn(true); 146 when(mMockBluetoothGattWrapper.getServices()) 147 .thenReturn(Arrays.asList(mMockBluetoothGattService)); 148 when(mMockBluetoothGattService.getUuid()).thenReturn(SERVICE_UUID); 149 when(mMockBluetoothGattService.getCharacteristics()) 150 .thenReturn(Arrays.asList(mMockBluetoothGattCharacteristic)); 151 when(mMockBluetoothGattCharacteristic.getUuid()).thenReturn(CHARACTERISTIC_UUID); 152 when(mMockBluetoothGattCharacteristic.getProperties()) 153 .thenReturn( 154 BluetoothGattCharacteristic.PROPERTY_NOTIFY 155 | BluetoothGattCharacteristic.PROPERTY_WRITE); 156 BluetoothGattDescriptor clientConfigDescriptor = 157 new BluetoothGattDescriptor( 158 ReservedUuids.Descriptors.CLIENT_CHARACTERISTIC_CONFIGURATION, 159 BluetoothGattDescriptor.PERMISSION_WRITE); 160 when(mMockBluetoothGattCharacteristic.getDescriptor( 161 ReservedUuids.Descriptors.CLIENT_CHARACTERISTIC_CONFIGURATION)) 162 .thenReturn(clientConfigDescriptor); 163 when(mMockBluetoothGattCharacteristic.getDescriptors()) 164 .thenReturn(Arrays.asList(mMockBluetoothGattDescriptor, clientConfigDescriptor)); 165 when(mMockBluetoothGattDescriptor.getUuid()).thenReturn(DESCRIPTOR_UUID); 166 when(mMockBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); 167 } 168 169 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_getDevice()170 public void test_getDevice() { 171 BluetoothDevice result = mBluetoothGattConnection.getDevice(); 172 173 assertThat(result).isEqualTo(mMockBluetoothDevice); 174 } 175 176 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_getConnectionOptions()177 public void test_getConnectionOptions() { 178 BluetoothGattHelper.ConnectionOptions result = mBluetoothGattConnection 179 .getConnectionOptions(); 180 181 assertThat(result).isSameInstanceAs(CONNECTION_OPTIONS); 182 } 183 184 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_isConnected_false_beforeConnection()185 public void test_isConnected_false_beforeConnection() { 186 mBluetoothGattConnection = new BluetoothGattConnection( 187 mMockBluetoothGattWrapper, 188 mMockBluetoothOperationExecutor, 189 CONNECTION_OPTIONS); 190 191 boolean result = mBluetoothGattConnection.isConnected(); 192 193 assertThat(result).isFalse(); 194 } 195 196 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_isConnected_true_afterConnection()197 public void test_isConnected_true_afterConnection() { 198 boolean result = mBluetoothGattConnection.isConnected(); 199 200 assertThat(result).isTrue(); 201 } 202 203 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_isConnected_false_afterDisconnection()204 public void test_isConnected_false_afterDisconnection() { 205 mBluetoothGattConnection.onClosed(); 206 207 boolean result = mBluetoothGattConnection.isConnected(); 208 209 assertThat(result).isFalse(); 210 } 211 212 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_getService_notDiscovered()213 public void test_getService_notDiscovered() throws Exception { 214 BluetoothGattService result = mBluetoothGattConnection.getService(SERVICE_UUID); 215 verify(mMockBluetoothOperationExecutor).execute(mSynchronousOperationCaptor.capture()); 216 mSynchronousOperationCaptor.getValue().call(); 217 verify(mMockBluetoothOperationExecutor) 218 .execute( 219 mOperationCaptor.capture(), 220 eq(BluetoothGattConnection.SLOW_OPERATION_TIMEOUT_MILLIS)); 221 mOperationCaptor.getValue().run(); 222 223 assertThat(result).isEqualTo(mMockBluetoothGattService); 224 verify(mMockBluetoothGattWrapper).discoverServices(); 225 } 226 227 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_getService_alreadyDiscovered()228 public void test_getService_alreadyDiscovered() throws Exception { 229 mBluetoothGattConnection.getService(SERVICE_UUID); 230 verify(mMockBluetoothOperationExecutor).execute(mSynchronousOperationCaptor.capture()); 231 mSynchronousOperationCaptor.getValue().call(); 232 reset(mMockBluetoothOperationExecutor); 233 234 BluetoothGattService result = mBluetoothGattConnection.getService(SERVICE_UUID); 235 236 assertThat(result).isEqualTo(mMockBluetoothGattService); 237 // Verify that service discovery has been done only once 238 verifyNoMoreInteractions(mMockBluetoothOperationExecutor); 239 } 240 241 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_getService_notFound()242 public void test_getService_notFound() throws Exception { 243 when(mMockBluetoothGattWrapper.getServices()).thenReturn( 244 Arrays.<BluetoothGattService>asList()); 245 246 try { 247 mBluetoothGattConnection.getService(SERVICE_UUID); 248 fail("Expected BluetoothException"); 249 } catch (BluetoothException expected) { 250 } 251 } 252 253 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_getService_moreThanOne()254 public void test_getService_moreThanOne() throws Exception { 255 when(mMockBluetoothGattWrapper.getServices()) 256 .thenReturn(Arrays.asList(mMockBluetoothGattService, mMockBluetoothGattService)); 257 258 try { 259 mBluetoothGattConnection.getService(SERVICE_UUID); 260 fail("Expected BluetoothException"); 261 } catch (BluetoothException expected) { 262 } 263 } 264 265 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_getCharacteristic()266 public void test_getCharacteristic() throws Exception { 267 BluetoothGattCharacteristic result = 268 mBluetoothGattConnection.getCharacteristic(SERVICE_UUID, CHARACTERISTIC_UUID); 269 270 assertThat(result).isEqualTo(mMockBluetoothGattCharacteristic); 271 } 272 273 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_getCharacteristic_notFound()274 public void test_getCharacteristic_notFound() throws Exception { 275 when(mMockBluetoothGattService.getCharacteristics()) 276 .thenReturn(Arrays.<BluetoothGattCharacteristic>asList()); 277 278 try { 279 mBluetoothGattConnection.getCharacteristic(SERVICE_UUID, CHARACTERISTIC_UUID); 280 fail("Expected BluetoothException"); 281 } catch (BluetoothException expected) { 282 } 283 } 284 285 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_getCharacteristic_moreThanOne()286 public void test_getCharacteristic_moreThanOne() throws Exception { 287 when(mMockBluetoothGattService.getCharacteristics()) 288 .thenReturn( 289 Arrays.asList(mMockBluetoothGattCharacteristic, 290 mMockBluetoothGattCharacteristic)); 291 292 try { 293 mBluetoothGattConnection.getCharacteristic(SERVICE_UUID, CHARACTERISTIC_UUID); 294 fail("Expected BluetoothException"); 295 } catch (BluetoothException expected) { 296 } 297 } 298 299 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_getCharacteristic_moreThanOneService()300 public void test_getCharacteristic_moreThanOneService() throws Exception { 301 // Add a new service with the same service UUID as our existing one, but add a different 302 // characteristic inside of it. 303 when(mMockBluetoothGattWrapper.getServices()) 304 .thenReturn(Arrays.asList(mMockBluetoothGattService, mMockBluetoothGattService2)); 305 when(mMockBluetoothGattService2.getUuid()).thenReturn(SERVICE_UUID); 306 when(mMockBluetoothGattService2.getCharacteristics()) 307 .thenReturn(Arrays.asList(mMockBluetoothGattCharacteristic2)); 308 when(mMockBluetoothGattCharacteristic2.getUuid()) 309 .thenReturn( 310 new UUID( 311 CHARACTERISTIC_UUID.getMostSignificantBits(), 312 CHARACTERISTIC_UUID.getLeastSignificantBits() + 1)); 313 when(mMockBluetoothGattCharacteristic2.getProperties()) 314 .thenReturn( 315 BluetoothGattCharacteristic.PROPERTY_NOTIFY 316 | BluetoothGattCharacteristic.PROPERTY_WRITE); 317 318 mBluetoothGattConnection.getCharacteristic(SERVICE_UUID, CHARACTERISTIC_UUID); 319 } 320 321 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_getDescriptor()322 public void test_getDescriptor() throws Exception { 323 when(mMockBluetoothGattCharacteristic.getDescriptors()) 324 .thenReturn(Arrays.asList(mMockBluetoothGattDescriptor)); 325 326 BluetoothGattDescriptor result = 327 mBluetoothGattConnection 328 .getDescriptor(SERVICE_UUID, CHARACTERISTIC_UUID, DESCRIPTOR_UUID); 329 330 assertThat(result).isEqualTo(mMockBluetoothGattDescriptor); 331 } 332 333 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_getDescriptor_notFound()334 public void test_getDescriptor_notFound() throws Exception { 335 when(mMockBluetoothGattCharacteristic.getDescriptors()) 336 .thenReturn(Arrays.<BluetoothGattDescriptor>asList()); 337 338 try { 339 mBluetoothGattConnection 340 .getDescriptor(SERVICE_UUID, CHARACTERISTIC_UUID, DESCRIPTOR_UUID); 341 fail("Expected BluetoothException"); 342 } catch (BluetoothException expected) { 343 } 344 } 345 346 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_getDescriptor_moreThanOne()347 public void test_getDescriptor_moreThanOne() throws Exception { 348 when(mMockBluetoothGattCharacteristic.getDescriptors()) 349 .thenReturn( 350 Arrays.asList(mMockBluetoothGattDescriptor, mMockBluetoothGattDescriptor)); 351 352 try { 353 mBluetoothGattConnection 354 .getDescriptor(SERVICE_UUID, CHARACTERISTIC_UUID, DESCRIPTOR_UUID); 355 fail("Expected BluetoothException"); 356 } catch (BluetoothException expected) { 357 } 358 } 359 360 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_discoverServices()361 public void test_discoverServices() throws Exception { 362 when(mMockBluetoothOperationExecutor.executeNonnull( 363 new SynchronousOperation<>( 364 mMockBluetoothOperationExecutor, OperationType.NOTIFICATION_CHANGE))) 365 .thenReturn(mMockChangeObserver); 366 367 mBluetoothGattConnection.discoverServices(); 368 369 verify(mMockBluetoothOperationExecutor).execute(mSynchronousOperationCaptor.capture()); 370 mSynchronousOperationCaptor.getValue().call(); 371 verify(mMockBluetoothOperationExecutor) 372 .execute( 373 mOperationCaptor.capture(), 374 eq(BluetoothGattConnection.SLOW_OPERATION_TIMEOUT_MILLIS)); 375 mOperationCaptor.getValue().run(); 376 verify(mMockBluetoothGattWrapper).discoverServices(); 377 verify(mMockBluetoothGattWrapper, never()).refresh(); 378 } 379 380 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_discoverServices_serviceChange()381 public void test_discoverServices_serviceChange() throws Exception { 382 when(mMockBluetoothGattWrapper.getService(ReservedUuids.Services.GENERIC_ATTRIBUTE)) 383 .thenReturn(mMockBluetoothGattService); 384 when(mMockBluetoothGattService 385 .getCharacteristic(ReservedUuids.Characteristics.SERVICE_CHANGE)) 386 .thenReturn(mMockBluetoothGattCharacteristic); 387 388 mBluetoothGattConnection.discoverServices(); 389 390 verify(mMockBluetoothOperationExecutor).execute(mSynchronousOperationCaptor.capture()); 391 mSynchronousOperationCaptor.getValue().call(); 392 verify(mMockBluetoothOperationExecutor, times(2)) 393 .execute( 394 mOperationCaptor.capture(), 395 eq(BluetoothGattConnection.SLOW_OPERATION_TIMEOUT_MILLIS)); 396 verify(mMockBluetoothGattWrapper).refresh(); 397 } 398 399 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_discoverServices_SelfDefinedServiceDynamic()400 public void test_discoverServices_SelfDefinedServiceDynamic() throws Exception { 401 when(mMockBluetoothGattWrapper.getService(BluetoothConsts.SERVICE_DYNAMIC_SERVICE)) 402 .thenReturn(mMockBluetoothGattService); 403 when(mMockBluetoothGattService 404 .getCharacteristic(BluetoothConsts.SERVICE_DYNAMIC_CHARACTERISTIC)) 405 .thenReturn(mMockBluetoothGattCharacteristic); 406 407 mBluetoothGattConnection.discoverServices(); 408 409 verify(mMockBluetoothOperationExecutor).execute(mSynchronousOperationCaptor.capture()); 410 mSynchronousOperationCaptor.getValue().call(); 411 verify(mMockBluetoothOperationExecutor, times(2)) 412 .execute( 413 mOperationCaptor.capture(), 414 eq(BluetoothGattConnection.SLOW_OPERATION_TIMEOUT_MILLIS)); 415 verify(mMockBluetoothGattWrapper).refresh(); 416 } 417 418 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_discoverServices_refreshWithGattErrorOnMncAbove()419 public void test_discoverServices_refreshWithGattErrorOnMncAbove() throws Exception { 420 if (VERSION.SDK_INT <= VERSION_CODES.LOLLIPOP_MR1) { 421 return; 422 } 423 mBluetoothGattConnection.discoverServices(); 424 verify(mMockBluetoothOperationExecutor).execute(mSynchronousOperationCaptor.capture()); 425 426 doThrow(new BluetoothGattException("fail", BluetoothGattConnection.GATT_ERROR)) 427 .doReturn(null) 428 .when(mMockBluetoothOperationExecutor) 429 .execute(isA(Operation.class), 430 eq(BluetoothGattConnection.SLOW_OPERATION_TIMEOUT_MILLIS)); 431 mSynchronousOperationCaptor.getValue().call(); 432 verify(mMockBluetoothOperationExecutor, times(2)) 433 .execute( 434 mOperationCaptor.capture(), 435 eq(BluetoothGattConnection.SLOW_OPERATION_TIMEOUT_MILLIS)); 436 verify(mMockBluetoothGattWrapper).refresh(); 437 } 438 439 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_discoverServices_refreshWithGattInternalErrorOnMncAbove()440 public void test_discoverServices_refreshWithGattInternalErrorOnMncAbove() throws Exception { 441 if (VERSION.SDK_INT <= VERSION_CODES.LOLLIPOP_MR1) { 442 return; 443 } 444 mBluetoothGattConnection.discoverServices(); 445 verify(mMockBluetoothOperationExecutor).execute(mSynchronousOperationCaptor.capture()); 446 447 doThrow(new BluetoothGattException("fail", BluetoothGattConnection.GATT_INTERNAL_ERROR)) 448 .doReturn(null) 449 .when(mMockBluetoothOperationExecutor) 450 .execute(isA(Operation.class), 451 eq(BluetoothGattConnection.SLOW_OPERATION_TIMEOUT_MILLIS)); 452 mSynchronousOperationCaptor.getValue().call(); 453 verify(mMockBluetoothOperationExecutor, times(2)) 454 .execute( 455 mOperationCaptor.capture(), 456 eq(BluetoothGattConnection.SLOW_OPERATION_TIMEOUT_MILLIS)); 457 verify(mMockBluetoothGattWrapper).refresh(); 458 } 459 460 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_discoverServices_dynamicServices_notBonded()461 public void test_discoverServices_dynamicServices_notBonded() throws Exception { 462 when(mMockBluetoothGattWrapper.getService(ReservedUuids.Services.GENERIC_ATTRIBUTE)) 463 .thenReturn(mMockBluetoothGattService); 464 when(mMockBluetoothGattService 465 .getCharacteristic(ReservedUuids.Characteristics.SERVICE_CHANGE)) 466 .thenReturn(mMockBluetoothGattCharacteristic); 467 when(mMockBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE); 468 469 mBluetoothGattConnection.discoverServices(); 470 471 verify(mMockBluetoothGattWrapper, never()).refresh(); 472 } 473 474 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_readCharacteristic()475 public void test_readCharacteristic() throws Exception { 476 when(mMockBluetoothOperationExecutor.executeNonnull( 477 new Operation<byte[]>( 478 OperationType.READ_CHARACTERISTIC, 479 mMockBluetoothGattWrapper, 480 mMockBluetoothGattCharacteristic), 481 BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS)) 482 .thenReturn(DATA); 483 484 byte[] result = mBluetoothGattConnection 485 .readCharacteristic(mMockBluetoothGattCharacteristic); 486 487 assertThat(result).isEqualTo(DATA); 488 verify(mMockBluetoothOperationExecutor) 489 .executeNonnull(mOperationCaptor.capture(), anyLong()); 490 mOperationCaptor.getValue().run(); 491 verify(mMockBluetoothGattWrapper).readCharacteristic(mMockBluetoothGattCharacteristic); 492 } 493 494 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_readCharacteristic_by_uuid()495 public void test_readCharacteristic_by_uuid() throws Exception { 496 when(mMockBluetoothOperationExecutor.executeNonnull( 497 new Operation<byte[]>( 498 OperationType.READ_CHARACTERISTIC, 499 mMockBluetoothGattWrapper, 500 mMockBluetoothGattCharacteristic), 501 BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS)) 502 .thenReturn(DATA); 503 504 byte[] result = mBluetoothGattConnection 505 .readCharacteristic(SERVICE_UUID, CHARACTERISTIC_UUID); 506 507 assertThat(result).isEqualTo(DATA); 508 verify(mMockBluetoothOperationExecutor) 509 .executeNonnull(mOperationCaptor.capture(), anyLong()); 510 mOperationCaptor.getValue().run(); 511 verify(mMockBluetoothGattWrapper).readCharacteristic(mMockBluetoothGattCharacteristic); 512 } 513 514 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_writeCharacteristic()515 public void test_writeCharacteristic() throws Exception { 516 BluetoothGattCharacteristic characteristic = 517 new BluetoothGattCharacteristic( 518 CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE, 0); 519 mBluetoothGattConnection.writeCharacteristic(characteristic, DATA); 520 521 verify(mMockBluetoothOperationExecutor) 522 .execute(mOperationCaptor.capture(), 523 eq(BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS)); 524 mOperationCaptor.getValue().run(); 525 verify(mMockBluetoothGattWrapper).writeCharacteristic(mCharacteristicCaptor.capture(), 526 eq(DATA), eq(characteristic.getWriteType())); 527 BluetoothGattCharacteristic writtenCharacteristic = mCharacteristicCaptor.getValue(); 528 assertThat(writtenCharacteristic.getUuid()).isEqualTo(CHARACTERISTIC_UUID); 529 assertThat(writtenCharacteristic).isEqualTo(characteristic); 530 } 531 532 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_writeCharacteristic_by_uuid()533 public void test_writeCharacteristic_by_uuid() throws Exception { 534 mBluetoothGattConnection.writeCharacteristic(SERVICE_UUID, CHARACTERISTIC_UUID, DATA); 535 536 verify(mMockBluetoothOperationExecutor) 537 .execute(mOperationCaptor.capture(), 538 eq(BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS)); 539 mOperationCaptor.getValue().run(); 540 verify(mMockBluetoothGattWrapper).writeCharacteristic(mCharacteristicCaptor.capture(), 541 eq(DATA), anyInt()); 542 BluetoothGattCharacteristic writtenCharacteristic = mCharacteristicCaptor.getValue(); 543 assertThat(writtenCharacteristic.getUuid()).isEqualTo(CHARACTERISTIC_UUID); 544 } 545 546 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_readDescriptor()547 public void test_readDescriptor() throws Exception { 548 when(mMockBluetoothOperationExecutor.executeNonnull( 549 new Operation<byte[]>( 550 OperationType.READ_DESCRIPTOR, mMockBluetoothGattWrapper, 551 mMockBluetoothGattDescriptor), 552 BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS)) 553 .thenReturn(DATA); 554 555 byte[] result = mBluetoothGattConnection.readDescriptor(mMockBluetoothGattDescriptor); 556 557 assertThat(result).isEqualTo(DATA); 558 verify(mMockBluetoothOperationExecutor) 559 .executeNonnull(mOperationCaptor.capture(), anyLong()); 560 mOperationCaptor.getValue().run(); 561 verify(mMockBluetoothGattWrapper).readDescriptor(mMockBluetoothGattDescriptor); 562 } 563 564 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_readDescriptor_by_uuid()565 public void test_readDescriptor_by_uuid() throws Exception { 566 when(mMockBluetoothOperationExecutor.executeNonnull( 567 new Operation<byte[]>( 568 OperationType.READ_DESCRIPTOR, mMockBluetoothGattWrapper, 569 mMockBluetoothGattDescriptor), 570 BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS)) 571 .thenReturn(DATA); 572 573 byte[] result = 574 mBluetoothGattConnection 575 .readDescriptor(SERVICE_UUID, CHARACTERISTIC_UUID, DESCRIPTOR_UUID); 576 577 assertThat(result).isEqualTo(DATA); 578 verify(mMockBluetoothOperationExecutor) 579 .executeNonnull(mOperationCaptor.capture(), anyLong()); 580 mOperationCaptor.getValue().run(); 581 verify(mMockBluetoothGattWrapper).readDescriptor(mMockBluetoothGattDescriptor); 582 } 583 584 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_writeDescriptor()585 public void test_writeDescriptor() throws Exception { 586 BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(DESCRIPTOR_UUID, 0); 587 mBluetoothGattConnection.writeDescriptor(descriptor, DATA); 588 589 verify(mMockBluetoothOperationExecutor) 590 .execute(mOperationCaptor.capture(), 591 eq(BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS)); 592 mOperationCaptor.getValue().run(); 593 verify(mMockBluetoothGattWrapper).writeDescriptor(mDescriptorCaptor.capture(), eq(DATA)); 594 BluetoothGattDescriptor writtenDescriptor = mDescriptorCaptor.getValue(); 595 assertThat(writtenDescriptor.getUuid()).isEqualTo(DESCRIPTOR_UUID); 596 assertThat(writtenDescriptor).isEqualTo(descriptor); 597 } 598 599 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_writeDescriptor_by_uuid()600 public void test_writeDescriptor_by_uuid() throws Exception { 601 mBluetoothGattConnection.writeDescriptor( 602 SERVICE_UUID, CHARACTERISTIC_UUID, DESCRIPTOR_UUID, DATA); 603 604 verify(mMockBluetoothOperationExecutor) 605 .execute(mOperationCaptor.capture(), 606 eq(BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS)); 607 mOperationCaptor.getValue().run(); 608 verify(mMockBluetoothGattWrapper).writeDescriptor(mDescriptorCaptor.capture(), eq(DATA)); 609 BluetoothGattDescriptor writtenDescriptor = mDescriptorCaptor.getValue(); 610 assertThat(writtenDescriptor.getUuid()).isEqualTo(DESCRIPTOR_UUID); 611 } 612 613 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_readRemoteRssi()614 public void test_readRemoteRssi() throws Exception { 615 when(mMockBluetoothOperationExecutor.executeNonnull( 616 new Operation<Integer>(OperationType.READ_RSSI, mMockBluetoothGattWrapper), 617 BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS)) 618 .thenReturn(RSSI); 619 620 int result = mBluetoothGattConnection.readRemoteRssi(); 621 622 assertThat(result).isEqualTo(RSSI); 623 verify(mMockBluetoothOperationExecutor) 624 .executeNonnull( 625 mOperationCaptor.capture(), 626 eq(BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS)); 627 mOperationCaptor.getValue().run(); 628 verify(mMockBluetoothGattWrapper).readRemoteRssi(); 629 } 630 631 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_getMaxDataPacketSize()632 public void test_getMaxDataPacketSize() throws Exception { 633 int result = mBluetoothGattConnection.getMaxDataPacketSize(); 634 635 assertThat(result).isEqualTo(mBluetoothGattConnection.getMtu() - 3); 636 } 637 638 @SdkSuppress(minSdkVersion = 32, codeName = "T") testSetNotificationEnabled_indication_enable()639 public void testSetNotificationEnabled_indication_enable() throws Exception { 640 when(mMockBluetoothGattCharacteristic.getProperties()) 641 .thenReturn(BluetoothGattCharacteristic.PROPERTY_INDICATE); 642 643 mBluetoothGattConnection.setNotificationEnabled(mMockBluetoothGattCharacteristic, true); 644 645 verify(mMockBluetoothGattWrapper) 646 .setCharacteristicNotification(mMockBluetoothGattCharacteristic, true); 647 verify(mMockBluetoothOperationExecutor).execute(mOperationCaptor.capture(), anyLong()); 648 mOperationCaptor.getValue().run(); 649 verify(mMockBluetoothGattWrapper).writeDescriptor(mDescriptorCaptor.capture(), 650 eq(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)); 651 BluetoothGattDescriptor writtenDescriptor = mDescriptorCaptor.getValue(); 652 assertThat(writtenDescriptor.getUuid()) 653 .isEqualTo(ReservedUuids.Descriptors.CLIENT_CHARACTERISTIC_CONFIGURATION); 654 } 655 656 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_getNotificationEnabled_notification_enable()657 public void test_getNotificationEnabled_notification_enable() throws Exception { 658 mBluetoothGattConnection.setNotificationEnabled(mMockBluetoothGattCharacteristic, true); 659 660 verify(mMockBluetoothGattWrapper) 661 .setCharacteristicNotification(mMockBluetoothGattCharacteristic, true); 662 verify(mMockBluetoothOperationExecutor).execute(mOperationCaptor.capture(), anyLong()); 663 mOperationCaptor.getValue().run(); 664 verify(mMockBluetoothGattWrapper).writeDescriptor(mDescriptorCaptor.capture(), 665 eq(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)); 666 BluetoothGattDescriptor writtenDescriptor = mDescriptorCaptor.getValue(); 667 assertThat(writtenDescriptor.getUuid()) 668 .isEqualTo(ReservedUuids.Descriptors.CLIENT_CHARACTERISTIC_CONFIGURATION); 669 } 670 671 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_setNotificationEnabled_indication_disable()672 public void test_setNotificationEnabled_indication_disable() throws Exception { 673 when(mMockBluetoothGattCharacteristic.getProperties()) 674 .thenReturn(BluetoothGattCharacteristic.PROPERTY_INDICATE); 675 676 mBluetoothGattConnection.setNotificationEnabled(mMockBluetoothGattCharacteristic, false); 677 678 verify(mMockBluetoothGattWrapper) 679 .setCharacteristicNotification(mMockBluetoothGattCharacteristic, false); 680 verify(mMockBluetoothOperationExecutor).execute(mOperationCaptor.capture(), anyLong()); 681 mOperationCaptor.getValue().run(); 682 verify(mMockBluetoothGattWrapper).writeDescriptor(mDescriptorCaptor.capture(), 683 eq(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)); 684 BluetoothGattDescriptor writtenDescriptor = mDescriptorCaptor.getValue(); 685 assertThat(writtenDescriptor.getUuid()) 686 .isEqualTo(ReservedUuids.Descriptors.CLIENT_CHARACTERISTIC_CONFIGURATION); 687 } 688 689 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_setNotificationEnabled_notification_disable()690 public void test_setNotificationEnabled_notification_disable() throws Exception { 691 mBluetoothGattConnection.setNotificationEnabled(mMockBluetoothGattCharacteristic, false); 692 693 verify(mMockBluetoothGattWrapper) 694 .setCharacteristicNotification(mMockBluetoothGattCharacteristic, false); 695 verify(mMockBluetoothOperationExecutor).execute(mOperationCaptor.capture(), anyLong()); 696 mOperationCaptor.getValue().run(); 697 verify(mMockBluetoothGattWrapper).writeDescriptor(mDescriptorCaptor.capture(), 698 eq(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)); 699 BluetoothGattDescriptor writtenDescriptor = mDescriptorCaptor.getValue(); 700 assertThat(writtenDescriptor.getUuid()) 701 .isEqualTo(ReservedUuids.Descriptors.CLIENT_CHARACTERISTIC_CONFIGURATION); 702 } 703 704 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_setNotificationEnabled_failure()705 public void test_setNotificationEnabled_failure() throws Exception { 706 when(mMockBluetoothGattCharacteristic.getProperties()) 707 .thenReturn(BluetoothGattCharacteristic.PROPERTY_READ); 708 709 try { 710 mBluetoothGattConnection.setNotificationEnabled(mMockBluetoothGattCharacteristic, 711 true); 712 fail("BluetoothException was expected"); 713 } catch (BluetoothException expected) { 714 } 715 } 716 717 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_enableNotification_Uuid()718 public void test_enableNotification_Uuid() throws Exception { 719 when(mMockBluetoothOperationExecutor.executeNonnull( 720 new SynchronousOperation<>( 721 mMockBluetoothOperationExecutor, 722 OperationType.NOTIFICATION_CHANGE, 723 mMockBluetoothGattCharacteristic))) 724 .thenReturn(mMockChangeObserver); 725 mBluetoothGattConnection.enableNotification(SERVICE_UUID, CHARACTERISTIC_UUID); 726 727 verify(mMockBluetoothOperationExecutor) 728 .executeNonnull(mSynchronousOperationCaptor.capture()); 729 ((ChangeObserver) mSynchronousOperationCaptor.getValue().call()) 730 .setListener(mMockCharChangeListener); 731 mBluetoothGattConnection.onCharacteristicChanged(mMockBluetoothGattCharacteristic, DATA); 732 verify(mMockCharChangeListener).onValueChange(DATA); 733 } 734 735 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_enableNotification()736 public void test_enableNotification() throws Exception { 737 when(mMockBluetoothOperationExecutor.executeNonnull( 738 new SynchronousOperation<>( 739 mMockBluetoothOperationExecutor, 740 OperationType.NOTIFICATION_CHANGE, 741 mMockBluetoothGattCharacteristic))) 742 .thenReturn(mMockChangeObserver); 743 mBluetoothGattConnection.enableNotification(mMockBluetoothGattCharacteristic); 744 745 verify(mMockBluetoothOperationExecutor) 746 .executeNonnull(mSynchronousOperationCaptor.capture()); 747 ((ChangeObserver) mSynchronousOperationCaptor.getValue().call()) 748 .setListener(mMockCharChangeListener); 749 750 mBluetoothGattConnection.onCharacteristicChanged(mMockBluetoothGattCharacteristic, DATA); 751 752 verify(mMockCharChangeListener).onValueChange(DATA); 753 } 754 755 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_enableNotification_observe()756 public void test_enableNotification_observe() throws Exception { 757 when(mMockBluetoothOperationExecutor.executeNonnull( 758 new SynchronousOperation<>( 759 mMockBluetoothOperationExecutor, 760 OperationType.NOTIFICATION_CHANGE, 761 mMockBluetoothGattCharacteristic))) 762 .thenReturn(mMockChangeObserver); 763 mBluetoothGattConnection.enableNotification(mMockBluetoothGattCharacteristic); 764 765 verify(mMockBluetoothOperationExecutor) 766 .executeNonnull(mSynchronousOperationCaptor.capture()); 767 ChangeObserver changeObserver = (ChangeObserver) mSynchronousOperationCaptor.getValue() 768 .call(); 769 mBluetoothGattConnection.onCharacteristicChanged(mMockBluetoothGattCharacteristic, DATA); 770 assertThat(changeObserver.waitForUpdate(TimeUnit.SECONDS.toMillis(1))).isEqualTo(DATA); 771 } 772 773 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_disableNotification_Uuid()774 public void test_disableNotification_Uuid() throws Exception { 775 when(mMockBluetoothOperationExecutor.executeNonnull( 776 new SynchronousOperation<>( 777 OperationType.NOTIFICATION_CHANGE, mMockBluetoothGattCharacteristic))) 778 .thenReturn(mMockChangeObserver); 779 mBluetoothGattConnection 780 .enableNotification(SERVICE_UUID, CHARACTERISTIC_UUID) 781 .setListener(mMockCharChangeListener); 782 783 mBluetoothGattConnection.disableNotification(SERVICE_UUID, CHARACTERISTIC_UUID); 784 785 mBluetoothGattConnection.onCharacteristicChanged(mMockBluetoothGattCharacteristic, DATA); 786 verify(mMockCharChangeListener, never()).onValueChange(DATA); 787 } 788 789 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_disableNotification()790 public void test_disableNotification() throws Exception { 791 when(mMockBluetoothOperationExecutor.executeNonnull( 792 new SynchronousOperation<ChangeObserver>( 793 OperationType.NOTIFICATION_CHANGE, mMockBluetoothGattCharacteristic))) 794 .thenReturn(mMockChangeObserver); 795 mBluetoothGattConnection 796 .enableNotification(mMockBluetoothGattCharacteristic) 797 .setListener(mMockCharChangeListener); 798 verify(mMockBluetoothOperationExecutor) 799 .executeNonnull(mSynchronousOperationCaptor.capture()); 800 mSynchronousOperationCaptor.getValue().call(); 801 802 mBluetoothGattConnection.disableNotification(mMockBluetoothGattCharacteristic); 803 verify(mMockBluetoothOperationExecutor).execute(mSynchronousOperationCaptor.capture()); 804 mSynchronousOperationCaptor.getValue().call(); 805 806 mBluetoothGattConnection.onCharacteristicChanged(mMockBluetoothGattCharacteristic, DATA); 807 verify(mMockCharChangeListener, never()).onValueChange(DATA); 808 } 809 810 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_addCloseListener()811 public void test_addCloseListener() throws Exception { 812 mBluetoothGattConnection.addCloseListener(mMockConnectionCloseListener); 813 814 mBluetoothGattConnection.onClosed(); 815 verify(mMockConnectionCloseListener).onClose(); 816 } 817 818 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_removeCloseListener()819 public void test_removeCloseListener() throws Exception { 820 mBluetoothGattConnection.addCloseListener(mMockConnectionCloseListener); 821 822 mBluetoothGattConnection.removeCloseListener(mMockConnectionCloseListener); 823 824 mBluetoothGattConnection.onClosed(); 825 verify(mMockConnectionCloseListener, never()).onClose(); 826 } 827 828 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_close()829 public void test_close() throws Exception { 830 mBluetoothGattConnection.close(); 831 832 verify(mMockBluetoothOperationExecutor) 833 .execute(mOperationCaptor.capture(), 834 eq(BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS)); 835 mOperationCaptor.getValue().run(); 836 verify(mMockBluetoothGattWrapper).disconnect(); 837 verify(mMockBluetoothGattWrapper).close(); 838 } 839 840 @SdkSuppress(minSdkVersion = 32, codeName = "T") test_onClosed()841 public void test_onClosed() throws Exception { 842 mBluetoothGattConnection.onClosed(); 843 844 verify(mMockBluetoothOperationExecutor, never()) 845 .execute(mOperationCaptor.capture(), anyLong()); 846 verify(mMockBluetoothGattWrapper).close(); 847 } 848 } 849