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