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 com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth8.assertThat;
21 
22 import static org.mockito.Mockito.any;
23 import static org.mockito.Mockito.anyInt;
24 import static org.mockito.Mockito.eq;
25 import static org.mockito.Mockito.inOrder;
26 import static org.mockito.Mockito.mock;
27 import static org.mockito.Mockito.mockingDetails;
28 import static org.mockito.Mockito.never;
29 import static org.mockito.Mockito.timeout;
30 import static org.mockito.Mockito.verify;
31 
32 import android.bluetooth.le.BluetoothLeScanner;
33 import android.content.Context;
34 import android.platform.test.annotations.RequiresFlagsEnabled;
35 import android.platform.test.flag.junit.CheckFlagsRule;
36 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
37 import android.util.Log;
38 
39 import androidx.test.core.app.ApplicationProvider;
40 
41 import com.android.bluetooth.flags.Flags;
42 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
43 
44 import com.google.protobuf.ByteString;
45 import com.google.testing.junit.testparameterinjector.TestParameter;
46 import com.google.testing.junit.testparameterinjector.TestParameterInjector;
47 
48 import org.junit.Assume;
49 import org.junit.ClassRule;
50 import org.junit.Ignore;
51 import org.junit.Rule;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 import org.mockito.InOrder;
55 import org.mockito.invocation.Invocation;
56 
57 import pandora.GattProto.AttStatusCode;
58 import pandora.GattProto.GattCharacteristicParams;
59 import pandora.GattProto.GattServiceParams;
60 import pandora.GattProto.IndicateOnCharacteristicRequest;
61 import pandora.GattProto.IndicateOnCharacteristicResponse;
62 import pandora.GattProto.NotifyOnCharacteristicRequest;
63 import pandora.GattProto.NotifyOnCharacteristicResponse;
64 import pandora.GattProto.RegisterServiceRequest;
65 import pandora.HostProto.AdvertiseRequest;
66 import pandora.HostProto.AdvertiseResponse;
67 import pandora.HostProto.OwnAddressType;
68 
69 import java.nio.charset.StandardCharsets;
70 import java.util.Collection;
71 import java.util.UUID;
72 
73 @RunWith(TestParameterInjector.class)
74 public class GattClientTest {
75     private static final String TAG = "GattClientTest";
76     private static final int ANDROID_MTU = 517;
77     private static final int MTU_REQUESTED = 23;
78     private static final int ANOTHER_MTU_REQUESTED = 42;
79     private static final String NOTIFICATION_VALUE = "hello world";
80 
81     private static final UUID GAP_UUID = UUID.fromString("00001800-0000-1000-8000-00805f9b34fb");
82     private static final UUID CCCD_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
83 
84     private static final UUID TEST_SERVICE_UUID =
85             UUID.fromString("00000000-0000-0000-0000-00000000000");
86     private static final UUID TEST_CHARACTERISTIC_UUID =
87             UUID.fromString("00010001-0000-0000-0000-000000000000");
88     @ClassRule public static final AdoptShellPermissionsRule PERM = new AdoptShellPermissionsRule();
89 
90     @Rule public final PandoraDevice mBumble = new PandoraDevice();
91 
92     @Rule
93     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
94 
95     private final Context mContext = ApplicationProvider.getApplicationContext();
96     private final BluetoothManager mManager = mContext.getSystemService(BluetoothManager.class);
97     private final BluetoothAdapter mAdapter = mManager.getAdapter();
98     private final BluetoothLeScanner mLeScanner = mAdapter.getBluetoothLeScanner();
99 
100     @Test
directConnectGattAfterClose()101     public void directConnectGattAfterClose() throws Exception {
102         advertiseWithBumble();
103 
104         BluetoothDevice device =
105                 mAdapter.getRemoteLeDevice(
106                         Utils.BUMBLE_RANDOM_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM);
107 
108         BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
109         BluetoothGatt gatt = device.connectGatt(mContext, false, gattCallback);
110         gatt.close();
111 
112         // Save the number of call in the callback to be checked later
113         Collection<Invocation> invocations = mockingDetails(gattCallback).getInvocations();
114         int numberOfCalls = invocations.size();
115 
116         BluetoothGattCallback gattCallback2 = mock(BluetoothGattCallback.class);
117         BluetoothGatt gatt2 = device.connectGatt(mContext, false, gattCallback2);
118         verify(gattCallback2, timeout(1000))
119                 .onConnectionStateChange(any(), anyInt(), eq(BluetoothProfile.STATE_CONNECTED));
120         disconnectAndWaitDisconnection(gatt2, gattCallback2);
121 
122         // After reconnecting, verify the first callback was not invoked.
123         Collection<Invocation> invocationsAfterSomeTimes =
124                 mockingDetails(gattCallback).getInvocations();
125         int numberOfCallsAfterSomeTimes = invocationsAfterSomeTimes.size();
126         assertThat(numberOfCallsAfterSomeTimes).isEqualTo(numberOfCalls);
127     }
128 
129     @Test
fullGattClientLifecycle()130     public void fullGattClientLifecycle() throws Exception {
131         BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
132         BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback);
133         disconnectAndWaitDisconnection(gatt, gattCallback);
134     }
135 
136     @Test
reconnectExistingClient()137     public void reconnectExistingClient() throws Exception {
138         advertiseWithBumble();
139 
140         BluetoothDevice device =
141                 mAdapter.getRemoteLeDevice(
142                         Utils.BUMBLE_RANDOM_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM);
143         BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
144         InOrder inOrder = inOrder(gattCallback);
145 
146         BluetoothGatt gatt = device.connectGatt(mContext, false, gattCallback);
147         inOrder.verify(gattCallback, timeout(1000))
148                 .onConnectionStateChange(any(), anyInt(), eq(BluetoothProfile.STATE_CONNECTED));
149 
150         gatt.disconnect();
151         inOrder.verify(gattCallback, timeout(1000))
152                 .onConnectionStateChange(any(), anyInt(), eq(BluetoothProfile.STATE_DISCONNECTED));
153 
154         gatt.connect();
155         inOrder.verify(gattCallback, timeout(1000))
156                 .onConnectionStateChange(any(), anyInt(), eq(BluetoothProfile.STATE_CONNECTED));
157 
158         // TODO(323889717): Fix callback being called after gatt.close(). This disconnect shouldn't
159         //  be necessary.
160         gatt.disconnect();
161         inOrder.verify(gattCallback, timeout(1000))
162                 .onConnectionStateChange(any(), anyInt(), eq(BluetoothProfile.STATE_DISCONNECTED));
163         gatt.close();
164     }
165 
166     @Test
clientGattDiscoverServices()167     public void clientGattDiscoverServices() throws Exception {
168         BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
169         BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback);
170 
171         try {
172             gatt.discoverServices();
173             verify(gattCallback, timeout(10000))
174                     .onServicesDiscovered(any(), eq(BluetoothGatt.GATT_SUCCESS));
175 
176             assertThat(gatt.getServices().stream().map(BluetoothGattService::getUuid))
177                     .contains(GAP_UUID);
178 
179         } finally {
180             disconnectAndWaitDisconnection(gatt, gattCallback);
181         }
182     }
183 
184     @Test
clientGattReadCharacteristics()185     public void clientGattReadCharacteristics() throws Exception {
186         BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
187         BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback);
188 
189         try {
190             gatt.discoverServices();
191             verify(gattCallback, timeout(10000))
192                     .onServicesDiscovered(any(), eq(BluetoothGatt.GATT_SUCCESS));
193 
194             BluetoothGattService firstService = gatt.getServices().get(0);
195 
196             BluetoothGattCharacteristic firstCharacteristic =
197                     firstService.getCharacteristics().get(0);
198 
199             gatt.readCharacteristic(firstCharacteristic);
200 
201             verify(gattCallback, timeout(5000)).onCharacteristicRead(any(), any(), any(), anyInt());
202 
203         } finally {
204             disconnectAndWaitDisconnection(gatt, gattCallback);
205         }
206     }
207 
208     @Test
clientGattWriteCharacteristic()209     public void clientGattWriteCharacteristic() throws Exception {
210         registerWritableGattService();
211 
212         BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
213         BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback);
214 
215         try {
216             gatt.discoverServices();
217             verify(gattCallback, timeout(10000))
218                     .onServicesDiscovered(any(), eq(BluetoothGatt.GATT_SUCCESS));
219 
220             BluetoothGattCharacteristic characteristic =
221                     gatt.getService(TEST_SERVICE_UUID).getCharacteristic(TEST_CHARACTERISTIC_UUID);
222 
223             byte[] newValue = new byte[] {13};
224 
225             gatt.writeCharacteristic(
226                     characteristic, newValue, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
227 
228             verify(gattCallback, timeout(5000))
229                     .onCharacteristicWrite(
230                             any(), eq(characteristic), eq(BluetoothGatt.GATT_SUCCESS));
231 
232         } finally {
233             disconnectAndWaitDisconnection(gatt, gattCallback);
234         }
235     }
236 
237     @Test
clientGattNotifyOrIndicateCharacteristic(@estParameter boolean isIndicate)238     public void clientGattNotifyOrIndicateCharacteristic(@TestParameter boolean isIndicate)
239             throws Exception {
240         registerNotificationIndicationGattService(isIndicate);
241 
242         BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
243         BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback);
244 
245         try {
246             gatt.discoverServices();
247             verify(gattCallback, timeout(10000))
248                     .onServicesDiscovered(any(), eq(BluetoothGatt.GATT_SUCCESS));
249 
250             BluetoothGattCharacteristic characteristic =
251                     gatt.getService(TEST_SERVICE_UUID).getCharacteristic(TEST_CHARACTERISTIC_UUID);
252 
253             BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CCCD_UUID);
254             descriptor.setValue(
255                     isIndicate
256                             ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
257                             : BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
258             assertThat(gatt.writeDescriptor(descriptor)).isTrue();
259 
260             verify(gattCallback, timeout(5000))
261                     .onDescriptorWrite(any(), eq(descriptor), eq(BluetoothGatt.GATT_SUCCESS));
262 
263             gatt.setCharacteristicNotification(characteristic, true);
264 
265             if (isIndicate) {
266                 Log.i(TAG, "Triggering characteristic indication");
267                 triggerCharacteristicIndication(characteristic.getInstanceId());
268             } else {
269                 Log.i(TAG, "Triggering characteristic notification");
270                 triggerCharacteristicNotification(characteristic.getInstanceId());
271             }
272 
273             verify(gattCallback, timeout(5000))
274                     .onCharacteristicChanged(
275                             any(), any(), eq(NOTIFICATION_VALUE.getBytes(StandardCharsets.UTF_8)));
276 
277         } finally {
278             disconnectAndWaitDisconnection(gatt, gattCallback);
279         }
280     }
281 
282     @Test
283     @RequiresFlagsEnabled(Flags.FLAG_ENUMERATE_GATT_ERRORS)
connectTimeout()284     public void connectTimeout() {
285         BluetoothDevice device =
286                 mAdapter.getRemoteLeDevice(
287                         Utils.BUMBLE_RANDOM_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM);
288         BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
289 
290         // Connecting to a device not advertising results in connection timeout after 30 seconds
291         device.connectGatt(mContext, false, gattCallback);
292 
293         verify(gattCallback, timeout(35000))
294                 .onConnectionStateChange(
295                         any(),
296                         eq(BluetoothGatt.GATT_CONNECTION_TIMEOUT),
297                         eq(BluetoothProfile.STATE_DISCONNECTED));
298     }
299 
300     @RequiresFlagsEnabled(Flags.FLAG_GATT_FIX_DEVICE_BUSY)
301     @Test
consecutiveWriteCharacteristicFails_thenSuccess()302     public void consecutiveWriteCharacteristicFails_thenSuccess() throws Exception {
303         Assume.assumeTrue(Flags.gattFixDeviceBusy());
304 
305         registerWritableGattService();
306 
307         BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
308         BluetoothGattCallback gattCallback2 = mock(BluetoothGattCallback.class);
309 
310         BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback);
311         BluetoothGatt gatt2 = connectGattAndWaitConnection(gattCallback2);
312 
313         try {
314             gatt.discoverServices();
315             gatt2.discoverServices();
316             verify(gattCallback, timeout(10000))
317                     .onServicesDiscovered(any(), eq(BluetoothGatt.GATT_SUCCESS));
318             verify(gattCallback2, timeout(10000))
319                     .onServicesDiscovered(any(), eq(BluetoothGatt.GATT_SUCCESS));
320 
321             BluetoothGattCharacteristic characteristic =
322                     gatt.getService(TEST_SERVICE_UUID).getCharacteristic(TEST_CHARACTERISTIC_UUID);
323 
324             BluetoothGattCharacteristic characteristic2 =
325                     gatt2.getService(TEST_SERVICE_UUID).getCharacteristic(TEST_CHARACTERISTIC_UUID);
326 
327             byte[] newValue = new byte[] {13};
328 
329             gatt.writeCharacteristic(
330                     characteristic, newValue, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
331 
332             // TODO: b/324355496 - Make the test consistent when Bumble supports holding a response.
333             // Skip the test if the second write succeeded.
334             Assume.assumeFalse(
335                     gatt2.writeCharacteristic(
336                                     characteristic2,
337                                     newValue,
338                                     BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
339                             == BluetoothStatusCodes.SUCCESS);
340 
341             verify(gattCallback, timeout(5000))
342                     .onCharacteristicWrite(
343                             any(), eq(characteristic), eq(BluetoothGatt.GATT_SUCCESS));
344             verify(gattCallback2, never())
345                     .onCharacteristicWrite(
346                             any(), eq(characteristic), eq(BluetoothGatt.GATT_SUCCESS));
347 
348             assertThat(
349                             gatt2.writeCharacteristic(
350                                     characteristic2,
351                                     newValue,
352                                     BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT))
353                     .isEqualTo(BluetoothStatusCodes.SUCCESS);
354             verify(gattCallback2, timeout(5000))
355                     .onCharacteristicWrite(
356                             any(), eq(characteristic2), eq(BluetoothGatt.GATT_SUCCESS));
357         } finally {
358             disconnectAndWaitDisconnection(gatt, gattCallback);
359             disconnectAndWaitDisconnection(gatt2, gattCallback2);
360         }
361     }
362 
registerWritableGattService()363     private void registerWritableGattService() {
364         GattCharacteristicParams characteristicParams =
365                 GattCharacteristicParams.newBuilder()
366                         .setProperties(BluetoothGattCharacteristic.PROPERTY_WRITE)
367                         .setUuid(TEST_CHARACTERISTIC_UUID.toString())
368                         .build();
369 
370         GattServiceParams serviceParams =
371                 GattServiceParams.newBuilder()
372                         .addCharacteristics(characteristicParams)
373                         .setUuid(TEST_SERVICE_UUID.toString())
374                         .build();
375 
376         RegisterServiceRequest request =
377                 RegisterServiceRequest.newBuilder().setService(serviceParams).build();
378 
379         mBumble.gattBlocking().registerService(request);
380     }
381 
registerNotificationIndicationGattService(boolean isIndicate)382     private void registerNotificationIndicationGattService(boolean isIndicate) {
383         GattCharacteristicParams characteristicParams =
384                 GattCharacteristicParams.newBuilder()
385                         .setProperties(
386                                 isIndicate
387                                         ? BluetoothGattCharacteristic.PROPERTY_INDICATE
388                                         : BluetoothGattCharacteristic.PROPERTY_NOTIFY)
389                         .setUuid(TEST_CHARACTERISTIC_UUID.toString())
390                         .build();
391 
392         GattServiceParams serviceParams =
393                 GattServiceParams.newBuilder()
394                         .addCharacteristics(characteristicParams)
395                         .setUuid(TEST_SERVICE_UUID.toString())
396                         .build();
397 
398         RegisterServiceRequest request =
399                 RegisterServiceRequest.newBuilder().setService(serviceParams).build();
400 
401         mBumble.gattBlocking().registerService(request);
402     }
403 
triggerCharacteristicNotification(int instanceId)404     private void triggerCharacteristicNotification(int instanceId) {
405         NotifyOnCharacteristicRequest req =
406                 NotifyOnCharacteristicRequest.newBuilder()
407                         .setHandle(instanceId)
408                         .setValue(ByteString.copyFromUtf8(NOTIFICATION_VALUE))
409                         .build();
410         NotifyOnCharacteristicResponse resp = mBumble.gattBlocking().notifyOnCharacteristic(req);
411         assertThat(resp.getStatus()).isEqualTo(AttStatusCode.SUCCESS);
412     }
413 
triggerCharacteristicIndication(int instanceId)414     private void triggerCharacteristicIndication(int instanceId) {
415         IndicateOnCharacteristicRequest req =
416                 IndicateOnCharacteristicRequest.newBuilder()
417                         .setHandle(instanceId)
418                         .setValue(ByteString.copyFromUtf8(NOTIFICATION_VALUE))
419                         .build();
420         IndicateOnCharacteristicResponse resp =
421                 mBumble.gattBlocking().indicateOnCharacteristic(req);
422         assertThat(resp.getStatus()).isEqualTo(AttStatusCode.SUCCESS);
423     }
424 
advertiseWithBumble()425     private void advertiseWithBumble() {
426         AdvertiseRequest request =
427                 AdvertiseRequest.newBuilder()
428                         .setLegacy(true)
429                         .setConnectable(true)
430                         .setOwnAddressType(OwnAddressType.RANDOM)
431                         .build();
432 
433         StreamObserverSpliterator<AdvertiseResponse> responseObserver =
434                 new StreamObserverSpliterator<>();
435 
436         mBumble.host().advertise(request, responseObserver);
437     }
438 
connectGattAndWaitConnection(BluetoothGattCallback callback)439     private BluetoothGatt connectGattAndWaitConnection(BluetoothGattCallback callback) {
440         final int status = BluetoothGatt.GATT_SUCCESS;
441         final int state = BluetoothProfile.STATE_CONNECTED;
442 
443         advertiseWithBumble();
444 
445         BluetoothDevice device =
446                 mAdapter.getRemoteLeDevice(
447                         Utils.BUMBLE_RANDOM_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM);
448 
449         BluetoothGatt gatt = device.connectGatt(mContext, false, callback);
450         verify(callback, timeout(1000)).onConnectionStateChange(eq(gatt), eq(status), eq(state));
451 
452         return gatt;
453     }
454 
disconnectAndWaitDisconnection( BluetoothGatt gatt, BluetoothGattCallback callback)455     private void disconnectAndWaitDisconnection(
456             BluetoothGatt gatt, BluetoothGattCallback callback) {
457         final int state = BluetoothProfile.STATE_DISCONNECTED;
458         gatt.disconnect();
459         verify(callback, timeout(1000)).onConnectionStateChange(eq(gatt), anyInt(), eq(state));
460 
461         gatt.close();
462         gatt = null;
463     }
464 
465     @Test
466     @Ignore("b/307981748: requestMTU should return a direct error")
requestMtu_notConnected_isFalse()467     public void requestMtu_notConnected_isFalse() {
468         advertiseWithBumble();
469 
470         BluetoothDevice device =
471                 mAdapter.getRemoteLeDevice(
472                         Utils.BUMBLE_RANDOM_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM);
473         BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
474 
475         BluetoothGatt gatt = device.connectGatt(mContext, false, gattCallback);
476         // Do not wait for connection state change callback and ask MTU directly
477         assertThat(gatt.requestMtu(MTU_REQUESTED)).isFalse();
478     }
479 
480     @Test
481     @Ignore("b/307981748: requestMTU should return a direct error or a error on the callback")
requestMtu_invalidParamer_isFalse()482     public void requestMtu_invalidParamer_isFalse() {
483         BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
484         BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback);
485 
486         try {
487             assertThat(gatt.requestMtu(1024)).isTrue();
488             // verify(gattCallback, timeout(5000).atLeast(1)).onMtuChanged(eq(gatt),
489             // eq(ANDROID_MTU), eq(BluetoothGatt.GATT_FAILURE));
490         } finally {
491             disconnectAndWaitDisconnection(gatt, gattCallback);
492         }
493     }
494 
495     @Test
requestMtu_once_isSuccess()496     public void requestMtu_once_isSuccess() {
497         BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
498         BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback);
499 
500         try {
501             assertThat(gatt.requestMtu(MTU_REQUESTED)).isTrue();
502             // Check that only the ANDROID_MTU is returned, not the MTU_REQUESTED
503             verify(gattCallback, timeout(5000))
504                     .onMtuChanged(eq(gatt), eq(ANDROID_MTU), eq(BluetoothGatt.GATT_SUCCESS));
505         } finally {
506             disconnectAndWaitDisconnection(gatt, gattCallback);
507         }
508     }
509 
510     @Test
requestMtu_multipleTimeFromSameClient_isRejected()511     public void requestMtu_multipleTimeFromSameClient_isRejected() {
512         BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
513         BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback);
514 
515         try {
516             assertThat(gatt.requestMtu(MTU_REQUESTED)).isTrue();
517             // Check that only the ANDROID_MTU is returned, not the MTU_REQUESTED
518             verify(gattCallback, timeout(5000))
519                     .onMtuChanged(eq(gatt), eq(ANDROID_MTU), eq(BluetoothGatt.GATT_SUCCESS));
520 
521             assertThat(gatt.requestMtu(ANOTHER_MTU_REQUESTED)).isTrue();
522             verify(gattCallback, timeout(5000).times(2))
523                     .onMtuChanged(eq(gatt), eq(ANDROID_MTU), eq(BluetoothGatt.GATT_SUCCESS));
524         } finally {
525             disconnectAndWaitDisconnection(gatt, gattCallback);
526         }
527     }
528 
529     @Test
requestMtu_onceFromMultipleClient_secondIsSuccessWithoutUpdate()530     public void requestMtu_onceFromMultipleClient_secondIsSuccessWithoutUpdate() {
531         BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
532         BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback);
533 
534         try {
535             assertThat(gatt.requestMtu(MTU_REQUESTED)).isTrue();
536             verify(gattCallback, timeout(5000))
537                     .onMtuChanged(eq(gatt), eq(ANDROID_MTU), eq(BluetoothGatt.GATT_SUCCESS));
538 
539             BluetoothGattCallback gattCallback2 = mock(BluetoothGattCallback.class);
540             BluetoothGatt gatt2 = connectGattAndWaitConnection(gattCallback2);
541             try {
542                 // first callback because there is already a connected device
543                 verify(gattCallback2, timeout(9000))
544                         .onMtuChanged(eq(gatt2), eq(ANDROID_MTU), eq(BluetoothGatt.GATT_SUCCESS));
545                 assertThat(gatt2.requestMtu(ANOTHER_MTU_REQUESTED)).isTrue();
546                 verify(gattCallback2, timeout(9000).times(2))
547                         .onMtuChanged(eq(gatt2), eq(ANDROID_MTU), eq(BluetoothGatt.GATT_SUCCESS));
548             } finally {
549                 disconnectAndWaitDisconnection(gatt2, gattCallback2);
550             }
551         } finally {
552             disconnectAndWaitDisconnection(gatt, gattCallback);
553         }
554     }
555 }
556