• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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