• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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.BluetoothUtils.getSyncTimeout;
20 
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.RequiresNoPermission;
24 import android.annotation.RequiresPermission;
25 import android.annotation.SuppressLint;
26 import android.bluetooth.BluetoothGattCharacteristic.WriteType;
27 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
28 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
29 import android.compat.annotation.UnsupportedAppUsage;
30 import android.content.AttributionSource;
31 import android.os.Build;
32 import android.os.Handler;
33 import android.os.ParcelUuid;
34 import android.os.RemoteException;
35 import android.util.Log;
36 
37 import com.android.modules.utils.SynchronousResultReceiver;
38 
39 import java.lang.annotation.Retention;
40 import java.lang.annotation.RetentionPolicy;
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.UUID;
44 import java.util.concurrent.TimeoutException;
45 
46 /**
47  * Public API for the Bluetooth GATT Profile.
48  *
49  * <p>This class provides Bluetooth GATT functionality to enable communication
50  * with Bluetooth Smart or Smart Ready devices.
51  *
52  * <p>To connect to a remote peripheral device, create a {@link BluetoothGattCallback}
53  * and call {@link BluetoothDevice#connectGatt} to get a instance of this class.
54  * GATT capable devices can be discovered using the Bluetooth device discovery or BLE
55  * scan process.
56  */
57 public final class BluetoothGatt implements BluetoothProfile {
58     private static final String TAG = "BluetoothGatt";
59     private static final boolean DBG = true;
60     private static final boolean VDBG = false;
61 
62     @UnsupportedAppUsage
63     private IBluetoothGatt mService;
64     @UnsupportedAppUsage
65     private volatile BluetoothGattCallback mCallback;
66     private Handler mHandler;
67     @UnsupportedAppUsage
68     private int mClientIf;
69     private BluetoothDevice mDevice;
70     @UnsupportedAppUsage
71     private boolean mAutoConnect;
72     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
73     private int mAuthRetryState;
74     private int mConnState;
75     private final Object mStateLock = new Object();
76     private final Object mDeviceBusyLock = new Object();
77     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
78     private Boolean mDeviceBusy = false;
79     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
80     private int mTransport;
81     private int mPhy;
82     private boolean mOpportunistic;
83     private final AttributionSource mAttributionSource;
84 
85     private static final int AUTH_RETRY_STATE_IDLE = 0;
86     private static final int AUTH_RETRY_STATE_NO_MITM = 1;
87     private static final int AUTH_RETRY_STATE_MITM = 2;
88 
89     private static final int CONN_STATE_IDLE = 0;
90     private static final int CONN_STATE_CONNECTING = 1;
91     private static final int CONN_STATE_CONNECTED = 2;
92     private static final int CONN_STATE_DISCONNECTING = 3;
93     private static final int CONN_STATE_CLOSED = 4;
94 
95     private static final int WRITE_CHARACTERISTIC_MAX_RETRIES = 5;
96     private static final int WRITE_CHARACTERISTIC_TIME_TO_WAIT = 10; // milliseconds
97 
98     private List<BluetoothGattService> mServices;
99 
100     /** A GATT operation completed successfully */
101     public static final int GATT_SUCCESS = 0;
102 
103     /** GATT read operation is not permitted */
104     public static final int GATT_READ_NOT_PERMITTED = 0x2;
105 
106     /** GATT write operation is not permitted */
107     public static final int GATT_WRITE_NOT_PERMITTED = 0x3;
108 
109     /** Insufficient authentication for a given operation */
110     public static final int GATT_INSUFFICIENT_AUTHENTICATION = 0x5;
111 
112     /** The given request is not supported */
113     public static final int GATT_REQUEST_NOT_SUPPORTED = 0x6;
114 
115     /** Insufficient encryption for a given operation */
116     public static final int GATT_INSUFFICIENT_ENCRYPTION = 0xf;
117 
118     /** A read or write operation was requested with an invalid offset */
119     public static final int GATT_INVALID_OFFSET = 0x7;
120 
121     /** Insufficient authorization for a given operation */
122     public static final int GATT_INSUFFICIENT_AUTHORIZATION = 0x8;
123 
124     /** A write operation exceeds the maximum length of the attribute */
125     public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 0xd;
126 
127     /** A remote device connection is congested. */
128     public static final int GATT_CONNECTION_CONGESTED = 0x8f;
129 
130     /** A GATT operation failed, errors other than the above */
131     public static final int GATT_FAILURE = 0x101;
132 
133     /**
134      * Connection parameter update - Use the connection parameters recommended by the
135      * Bluetooth SIG. This is the default value if no connection parameter update
136      * is requested.
137      */
138     public static final int CONNECTION_PRIORITY_BALANCED = 0;
139 
140     /**
141      * Connection parameter update - Request a high priority, low latency connection.
142      * An application should only request high priority connection parameters to transfer large
143      * amounts of data over LE quickly. Once the transfer is complete, the application should
144      * request {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED} connection parameters to reduce
145      * energy use.
146      */
147     public static final int CONNECTION_PRIORITY_HIGH = 1;
148 
149     /** Connection parameter update - Request low power, reduced data rate connection parameters. */
150     public static final int CONNECTION_PRIORITY_LOW_POWER = 2;
151 
152     /**
153      * Connection parameter update - Request the priority preferred for Digital Car Key for a
154      * lower latency connection. This connection parameter will consume more power than
155      * {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED}, so it is recommended that apps do not use
156      * this unless it specifically fits their use case.
157      */
158     public static final int CONNECTION_PRIORITY_DCK = 3;
159 
160     /**
161      * Connection subrate request - Balanced.
162      *
163      * @hide
164      */
165     public static final int SUBRATE_REQUEST_MODE_BALANCED = 0;
166 
167     /**
168      * Connection subrate request - High.
169      *
170      * @hide
171      */
172     public static final int SUBRATE_REQUEST_MODE_HIGH = 1;
173 
174     /**
175      * Connection Subrate Request - Low Power.
176      *
177      * @hide
178      */
179     public static final int SUBRATE_REQUEST_MODE_LOW_POWER = 2;
180 
181     /** @hide */
182     @Retention(RetentionPolicy.SOURCE)
183     @IntDef(prefix = {"SUBRATE_REQUEST_MODE"},
184             value =
185                     {
186                             SUBRATE_REQUEST_MODE_BALANCED,
187                             SUBRATE_REQUEST_MODE_HIGH,
188                             SUBRATE_REQUEST_MODE_LOW_POWER,
189                     })
190     public @interface SubrateRequestMode {}
191 
192     /**
193      * No authentication required.
194      *
195      * @hide
196      */
197     /*package*/ static final int AUTHENTICATION_NONE = 0;
198 
199     /**
200      * Authentication requested; no person-in-the-middle protection required.
201      *
202      * @hide
203      */
204     /*package*/ static final int AUTHENTICATION_NO_MITM = 1;
205 
206     /**
207      * Authentication with person-in-the-middle protection requested.
208      *
209      * @hide
210      */
211     /*package*/ static final int AUTHENTICATION_MITM = 2;
212 
213     /**
214      * Bluetooth GATT callbacks. Overrides the default BluetoothGattCallback implementation.
215      */
216     @SuppressLint("AndroidFrameworkBluetoothPermission")
217     private final IBluetoothGattCallback mBluetoothGattCallback =
218             new IBluetoothGattCallback.Stub() {
219                 /**
220                  * Application interface registered - app is ready to go
221                  * @hide
222                  */
223                 @Override
224                 @SuppressLint("AndroidFrameworkRequiresPermission")
225                 public void onClientRegistered(int status, int clientIf) {
226                     if (DBG) {
227                         Log.d(TAG, "onClientRegistered() - status=" + status
228                                 + " clientIf=" + clientIf);
229                     }
230                     if (VDBG) {
231                         synchronized (mStateLock) {
232                             if (mConnState != CONN_STATE_CONNECTING) {
233                                 Log.e(TAG, "Bad connection state: " + mConnState);
234                             }
235                         }
236                     }
237                     mClientIf = clientIf;
238                     if (status != GATT_SUCCESS) {
239                         runOrQueueCallback(new Runnable() {
240                             @Override
241                             public void run() {
242                                 final BluetoothGattCallback callback = mCallback;
243                                 if (callback != null) {
244                                     callback.onConnectionStateChange(BluetoothGatt.this,
245                                             GATT_FAILURE,
246                                             BluetoothProfile.STATE_DISCONNECTED);
247                                 }
248                             }
249                         });
250 
251                         synchronized (mStateLock) {
252                             mConnState = CONN_STATE_IDLE;
253                         }
254                         return;
255                     }
256                     try {
257                         final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
258                         // autoConnect is inverse of "isDirect"
259                         mService.clientConnect(mClientIf, mDevice.getAddress(),
260                                 mDevice.getAddressType(), !mAutoConnect, mTransport,
261                                 mOpportunistic, mPhy, mAttributionSource, recv);
262                         recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
263                     } catch (RemoteException | TimeoutException e) {
264                         Log.e(TAG, "", e);
265                     }
266                 }
267 
268                 /**
269                  * Phy update callback
270                  * @hide
271                  */
272                 @Override
273                 public void onPhyUpdate(String address, int txPhy, int rxPhy, int status) {
274                     if (DBG) {
275                         Log.d(TAG, "onPhyUpdate() - status=" + status
276                                 + " address=" + address + " txPhy=" + txPhy + " rxPhy=" + rxPhy);
277                     }
278                     if (!address.equals(mDevice.getAddress())) {
279                         return;
280                     }
281 
282                     runOrQueueCallback(new Runnable() {
283                         @Override
284                         public void run() {
285                             final BluetoothGattCallback callback = mCallback;
286                             if (callback != null) {
287                                 callback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status);
288                             }
289                         }
290                     });
291                 }
292 
293                 /**
294                  * Phy read callback
295                  * @hide
296                  */
297                 @Override
298                 public void onPhyRead(String address, int txPhy, int rxPhy, int status) {
299                     if (DBG) {
300                         Log.d(TAG, "onPhyRead() - status=" + status
301                                 + " address=" + address + " txPhy=" + txPhy + " rxPhy=" + rxPhy);
302                     }
303                     if (!address.equals(mDevice.getAddress())) {
304                         return;
305                     }
306 
307                     runOrQueueCallback(new Runnable() {
308                         @Override
309                         public void run() {
310                             final BluetoothGattCallback callback = mCallback;
311                             if (callback != null) {
312                                 callback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status);
313                             }
314                         }
315                     });
316                 }
317 
318                 /**
319                  * Client connection state changed
320                  * @hide
321                  */
322                 @Override
323                 public void onClientConnectionState(int status, int clientIf,
324                         boolean connected, String address) {
325                     if (DBG) {
326                         Log.d(TAG, "onClientConnectionState() - status=" + status
327                                 + " clientIf=" + clientIf + " device=" + address);
328                     }
329                     if (!address.equals(mDevice.getAddress())) {
330                         return;
331                     }
332                     int profileState = connected ? BluetoothProfile.STATE_CONNECTED :
333                             BluetoothProfile.STATE_DISCONNECTED;
334 
335                     runOrQueueCallback(new Runnable() {
336                         @Override
337                         public void run() {
338                             final BluetoothGattCallback callback = mCallback;
339                             if (callback != null) {
340                                 callback.onConnectionStateChange(BluetoothGatt.this, status,
341                                         profileState);
342                             }
343                         }
344                     });
345 
346                     synchronized (mStateLock) {
347                         if (connected) {
348                             mConnState = CONN_STATE_CONNECTED;
349                         } else {
350                             mConnState = CONN_STATE_IDLE;
351                         }
352                     }
353 
354                     synchronized (mDeviceBusyLock) {
355                         mDeviceBusy = false;
356                     }
357                 }
358 
359                 /**
360                  * Remote search has been completed.
361                  * The internal object structure should now reflect the state
362                  * of the remote device database. Let the application know that
363                  * we are done at this point.
364                  * @hide
365                  */
366                 @Override
367                 public void onSearchComplete(String address, List<BluetoothGattService> services,
368                         int status) {
369                     if (DBG) {
370                         Log.d(TAG,
371                                 "onSearchComplete() = Device=" + address + " Status=" + status);
372                     }
373                     if (!address.equals(mDevice.getAddress())) {
374                         return;
375                     }
376 
377                     for (BluetoothGattService s : services) {
378                         //services we receive don't have device set properly.
379                         s.setDevice(mDevice);
380                     }
381 
382                     mServices.addAll(services);
383 
384                     // Fix references to included services, as they doesn't point to right objects.
385                     for (BluetoothGattService fixedService : mServices) {
386                         ArrayList<BluetoothGattService> includedServices =
387                                 new ArrayList(fixedService.getIncludedServices());
388                         fixedService.getIncludedServices().clear();
389 
390                         for (BluetoothGattService brokenRef : includedServices) {
391                             BluetoothGattService includedService = getService(mDevice,
392                                     brokenRef.getUuid(), brokenRef.getInstanceId());
393                             if (includedService != null) {
394                                 fixedService.addIncludedService(includedService);
395                             } else {
396                                 Log.e(TAG, "Broken GATT database: can't find included service.");
397                             }
398                         }
399                     }
400 
401                     runOrQueueCallback(new Runnable() {
402                         @Override
403                         public void run() {
404                             final BluetoothGattCallback callback = mCallback;
405                             if (callback != null) {
406                                 callback.onServicesDiscovered(BluetoothGatt.this, status);
407                             }
408                         }
409                     });
410                 }
411 
412                 /**
413                  * Remote characteristic has been read.
414                  * Updates the internal value.
415                  * @hide
416                  */
417                 @Override
418                 @SuppressLint("AndroidFrameworkRequiresPermission")
419                 public void onCharacteristicRead(String address, int status, int handle,
420                         byte[] value) {
421                     if (VDBG) {
422                         Log.d(TAG, "onCharacteristicRead() - Device=" + address
423                                 + " handle=" + handle + " Status=" + status);
424                     }
425 
426                     if (!address.equals(mDevice.getAddress())) {
427                         return;
428                     }
429 
430                     synchronized (mDeviceBusyLock) {
431                         mDeviceBusy = false;
432                     }
433 
434                     if ((status == GATT_INSUFFICIENT_AUTHENTICATION
435                             || status == GATT_INSUFFICIENT_ENCRYPTION)
436                             && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) {
437                         try {
438                             final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE)
439                                     ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM;
440                             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
441                             mService.readCharacteristic(
442                                     mClientIf, address, handle, authReq, mAttributionSource, recv);
443                             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
444                             mAuthRetryState++;
445                             return;
446                         } catch (RemoteException | TimeoutException e) {
447                             Log.e(TAG, "", e);
448                         }
449                     }
450 
451                     mAuthRetryState = AUTH_RETRY_STATE_IDLE;
452 
453                     BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice,
454                             handle);
455                     if (characteristic == null) {
456                         Log.w(TAG, "onCharacteristicRead() failed to find characteristic!");
457                         return;
458                     }
459 
460                     runOrQueueCallback(new Runnable() {
461                         @Override
462                         public void run() {
463                             final BluetoothGattCallback callback = mCallback;
464                             if (callback != null) {
465                                 if (status == 0) characteristic.setValue(value);
466                                 callback.onCharacteristicRead(BluetoothGatt.this, characteristic,
467                                         value, status);
468                             }
469                         }
470                     });
471                 }
472 
473                 /**
474                  * Characteristic has been written to the remote device.
475                  * Let the app know how we did...
476                  * @hide
477                  */
478                 @Override
479                 @SuppressLint("AndroidFrameworkRequiresPermission")
480                 public void onCharacteristicWrite(String address, int status, int handle,
481                         byte[] value) {
482                     if (VDBG) {
483                         Log.d(TAG, "onCharacteristicWrite() - Device=" + address
484                                 + " handle=" + handle + " Status=" + status);
485                     }
486 
487                     if (!address.equals(mDevice.getAddress())) {
488                         return;
489                     }
490 
491                     synchronized (mDeviceBusyLock) {
492                         mDeviceBusy = false;
493                     }
494 
495                     BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice,
496                             handle);
497                     if (characteristic == null) return;
498 
499                     if ((status == GATT_INSUFFICIENT_AUTHENTICATION
500                             || status == GATT_INSUFFICIENT_ENCRYPTION)
501                             && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) {
502                         try {
503                             final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE)
504                                     ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM;
505                             int requestStatus = BluetoothStatusCodes.ERROR_UNKNOWN;
506                             for (int i = 0; i < WRITE_CHARACTERISTIC_MAX_RETRIES; i++) {
507                                 final SynchronousResultReceiver<Integer> recv =
508                                         SynchronousResultReceiver.get();
509                                 mService.writeCharacteristic(mClientIf, address, handle,
510                                         characteristic.getWriteType(), authReq, value,
511                                         mAttributionSource, recv);
512                                 requestStatus = recv.awaitResultNoInterrupt(getSyncTimeout())
513                                     .getValue(BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND);
514                                 if (requestStatus
515                                         != BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY) {
516                                     break;
517                                 }
518                                 try {
519                                     Thread.sleep(WRITE_CHARACTERISTIC_TIME_TO_WAIT);
520                                 } catch (InterruptedException e) {
521                                 }
522                             }
523                             mAuthRetryState++;
524                             return;
525                         } catch (RemoteException | TimeoutException e) {
526                             Log.e(TAG, "", e);
527                         }
528                     }
529 
530                     mAuthRetryState = AUTH_RETRY_STATE_IDLE;
531                     runOrQueueCallback(new Runnable() {
532                         @Override
533                         public void run() {
534                             final BluetoothGattCallback callback = mCallback;
535                             if (callback != null) {
536                                 callback.onCharacteristicWrite(BluetoothGatt.this, characteristic,
537                                         status);
538                             }
539                         }
540                     });
541                 }
542 
543                 /**
544                  * Remote characteristic has been updated.
545                  * Updates the internal value.
546                  * @hide
547                  */
548                 @Override
549                 public void onNotify(String address, int handle, byte[] value) {
550                     if (VDBG) Log.d(TAG, "onNotify() - Device=" + address + " handle=" + handle);
551 
552                     if (!address.equals(mDevice.getAddress())) {
553                         return;
554                     }
555 
556                     BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice,
557                             handle);
558                     if (characteristic == null) return;
559 
560                     runOrQueueCallback(new Runnable() {
561                         @Override
562                         public void run() {
563                             final BluetoothGattCallback callback = mCallback;
564                             if (callback != null) {
565                                 characteristic.setValue(value);
566                                 callback.onCharacteristicChanged(BluetoothGatt.this,
567                                         characteristic, value);
568                             }
569                         }
570                     });
571                 }
572 
573                 /**
574                  * Descriptor has been read.
575                  * @hide
576                  */
577                 @Override
578                 @SuppressLint("AndroidFrameworkRequiresPermission")
579                 public void onDescriptorRead(String address, int status, int handle, byte[] value) {
580                     if (VDBG) {
581                         Log.d(TAG,
582                                 "onDescriptorRead() - Device=" + address + " handle=" + handle);
583                     }
584 
585                     if (!address.equals(mDevice.getAddress())) {
586                         return;
587                     }
588 
589                     synchronized (mDeviceBusyLock) {
590                         mDeviceBusy = false;
591                     }
592 
593                     BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle);
594                     if (descriptor == null) return;
595 
596 
597                     if ((status == GATT_INSUFFICIENT_AUTHENTICATION
598                             || status == GATT_INSUFFICIENT_ENCRYPTION)
599                             && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) {
600                         try {
601                             final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE)
602                                     ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM;
603                             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
604                             mService.readDescriptor(mClientIf, address, handle, authReq,
605                                     mAttributionSource, recv);
606                             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
607                             mAuthRetryState++;
608                             return;
609                         } catch (RemoteException | TimeoutException e) {
610                             Log.e(TAG, "", e);
611                         }
612                     }
613 
614                     mAuthRetryState = AUTH_RETRY_STATE_IDLE;
615 
616                     runOrQueueCallback(new Runnable() {
617                         @Override
618                         public void run() {
619                             final BluetoothGattCallback callback = mCallback;
620                             if (callback != null) {
621                                 if (status == 0) descriptor.setValue(value);
622                                 callback.onDescriptorRead(BluetoothGatt.this, descriptor, status,
623                                         value);
624                             }
625                         }
626                     });
627                 }
628 
629                 /**
630                  * Descriptor write operation complete.
631                  * @hide
632                  */
633                 @Override
634                 @SuppressLint("AndroidFrameworkRequiresPermission")
635                 public void onDescriptorWrite(String address, int status, int handle,
636                         byte[] value) {
637                     if (VDBG) {
638                         Log.d(TAG,
639                                 "onDescriptorWrite() - Device=" + address + " handle=" + handle);
640                     }
641 
642                     if (!address.equals(mDevice.getAddress())) {
643                         return;
644                     }
645 
646                     synchronized (mDeviceBusyLock) {
647                         mDeviceBusy = false;
648                     }
649 
650                     BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle);
651                     if (descriptor == null) return;
652 
653                     if ((status == GATT_INSUFFICIENT_AUTHENTICATION
654                             || status == GATT_INSUFFICIENT_ENCRYPTION)
655                             && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) {
656                         try {
657                             final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE)
658                                     ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM;
659                             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
660                             mService.writeDescriptor(mClientIf, address, handle,
661                                     authReq, value, mAttributionSource, recv);
662                             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
663                             mAuthRetryState++;
664                             return;
665                         } catch (RemoteException | TimeoutException e) {
666                             Log.e(TAG, "", e);
667                         }
668                     }
669 
670                     mAuthRetryState = AUTH_RETRY_STATE_IDLE;
671 
672                     runOrQueueCallback(new Runnable() {
673                         @Override
674                         public void run() {
675                             final BluetoothGattCallback callback = mCallback;
676                             if (callback != null) {
677                                 callback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
678                             }
679                         }
680                     });
681                 }
682 
683                 /**
684                  * Prepared write transaction completed (or aborted)
685                  * @hide
686                  */
687                 @Override
688                 public void onExecuteWrite(String address, int status) {
689                     if (VDBG) {
690                         Log.d(TAG, "onExecuteWrite() - Device=" + address
691                                 + " status=" + status);
692                     }
693                     if (!address.equals(mDevice.getAddress())) {
694                         return;
695                     }
696 
697                     synchronized (mDeviceBusyLock) {
698                         mDeviceBusy = false;
699                     }
700 
701                     runOrQueueCallback(new Runnable() {
702                         @Override
703                         public void run() {
704                             final BluetoothGattCallback callback = mCallback;
705                             if (callback != null) {
706                                 callback.onReliableWriteCompleted(BluetoothGatt.this, status);
707                             }
708                         }
709                     });
710                 }
711 
712                 /**
713                  * Remote device RSSI has been read
714                  * @hide
715                  */
716                 @Override
717                 public void onReadRemoteRssi(String address, int rssi, int status) {
718                     if (VDBG) {
719                         Log.d(TAG, "onReadRemoteRssi() - Device=" + address
720                                 + " rssi=" + rssi + " status=" + status);
721                     }
722                     if (!address.equals(mDevice.getAddress())) {
723                         return;
724                     }
725                     runOrQueueCallback(new Runnable() {
726                         @Override
727                         public void run() {
728                             final BluetoothGattCallback callback = mCallback;
729                             if (callback != null) {
730                                 callback.onReadRemoteRssi(BluetoothGatt.this, rssi, status);
731                             }
732                         }
733                     });
734                 }
735 
736                 /**
737                  * Callback invoked when the MTU for a given connection changes
738                  * @hide
739                  */
740                 @Override
741                 public void onConfigureMTU(String address, int mtu, int status) {
742                     if (DBG) {
743                         Log.d(TAG, "onConfigureMTU() - Device=" + address
744                                 + " mtu=" + mtu + " status=" + status);
745                     }
746                     if (!address.equals(mDevice.getAddress())) {
747                         return;
748                     }
749 
750                     runOrQueueCallback(new Runnable() {
751                         @Override
752                         public void run() {
753                             final BluetoothGattCallback callback = mCallback;
754                             if (callback != null) {
755                                 callback.onMtuChanged(BluetoothGatt.this, mtu, status);
756                             }
757                         }
758                     });
759                 }
760 
761                 /**
762                  * Callback invoked when the given connection is updated
763                  * @hide
764                  */
765                 @Override
766                 public void onConnectionUpdated(String address, int interval, int latency,
767                         int timeout, int status) {
768                     if (DBG) {
769                         Log.d(TAG, "onConnectionUpdated() - Device=" + address
770                                 + " interval=" + interval + " latency=" + latency
771                                 + " timeout=" + timeout + " status=" + status);
772                     }
773                     if (!address.equals(mDevice.getAddress())) {
774                         return;
775                     }
776 
777                     runOrQueueCallback(new Runnable() {
778                         @Override
779                         public void run() {
780                             final BluetoothGattCallback callback = mCallback;
781                             if (callback != null) {
782                                 callback.onConnectionUpdated(BluetoothGatt.this, interval, latency,
783                                         timeout, status);
784                             }
785                         }
786                     });
787                 }
788 
789                 /**
790                  * Callback invoked when service changed event is received
791                  * @hide
792                  */
793                 @Override
794                 public void onServiceChanged(String address) {
795                     if (DBG) {
796                         Log.d(TAG, "onServiceChanged() - Device=" + address);
797                     }
798 
799                     if (!address.equals(mDevice.getAddress())) {
800                         return;
801                     }
802 
803                     runOrQueueCallback(new Runnable() {
804                         @Override
805                         public void run() {
806                             final BluetoothGattCallback callback = mCallback;
807                             if (callback != null) {
808                                 callback.onServiceChanged(BluetoothGatt.this);
809                             }
810                         }
811                     });
812                 }
813 
814                 /**
815                  * Callback invoked when the given connection's subrate is changed
816                  * @hide
817                  */
818                 @Override
819                 public void onSubrateChange(String address, int subrateFactor, int latency,
820                         int contNum, int timeout, int status) {
821                     Log.d(TAG,
822                             "onSubrateChange() - "
823                                     + "Device=" + BluetoothUtils.toAnonymizedAddress(address)
824                                     + ", subrateFactor=" + subrateFactor + ", latency=" + latency
825                                     + ", contNum=" + contNum + ", timeout=" + timeout
826                                     + ", status=" + status);
827 
828                     if (!address.equals(mDevice.getAddress())) {
829                         return;
830                     }
831 
832                     runOrQueueCallback(new Runnable() {
833                         @Override
834                         public void run() {
835                             final BluetoothGattCallback callback = mCallback;
836                             if (callback != null) {
837                                 callback.onSubrateChange(BluetoothGatt.this, subrateFactor, latency,
838                                         contNum, timeout, status);
839                             }
840                         }
841                     });
842                 }
843             };
844 
BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device, int transport, boolean opportunistic, int phy, AttributionSource attributionSource)845     /* package */ BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device, int transport,
846             boolean opportunistic, int phy, AttributionSource attributionSource) {
847         mService = iGatt;
848         mDevice = device;
849         mTransport = transport;
850         mPhy = phy;
851         mOpportunistic = opportunistic;
852         mAttributionSource = attributionSource;
853         mServices = new ArrayList<BluetoothGattService>();
854 
855         mConnState = CONN_STATE_IDLE;
856         mAuthRetryState = AUTH_RETRY_STATE_IDLE;
857     }
858 
859     /**
860      * Close this Bluetooth GATT client.
861      *
862      * <p>Application should call this method as early as possible after it is done with this GATT
863      * client.
864      */
865     @RequiresBluetoothConnectPermission
866     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
867     @Override
close()868     public void close() {
869         if (DBG) Log.d(TAG, "close()");
870 
871         unregisterApp();
872         mConnState = CONN_STATE_CLOSED;
873         mAuthRetryState = AUTH_RETRY_STATE_IDLE;
874     }
875 
876     /**
877      * Returns a service by UUID, instance and type.
878      *
879      * @hide
880      */
getService(BluetoothDevice device, UUID uuid, int instanceId)881     /*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid,
882             int instanceId) {
883         for (BluetoothGattService svc : mServices) {
884             if (svc.getDevice().equals(device)
885                     && svc.getInstanceId() == instanceId
886                     && svc.getUuid().equals(uuid)) {
887                 return svc;
888             }
889         }
890         return null;
891     }
892 
893 
894     /**
895      * Returns a characteristic with id equal to instanceId.
896      *
897      * @hide
898      */
getCharacteristicById(BluetoothDevice device, int instanceId)899     /*package*/ BluetoothGattCharacteristic getCharacteristicById(BluetoothDevice device,
900             int instanceId) {
901         for (BluetoothGattService svc : mServices) {
902             for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
903                 if (charac.getInstanceId() == instanceId) {
904                     return charac;
905                 }
906             }
907         }
908         return null;
909     }
910 
911     /**
912      * Returns a descriptor with id equal to instanceId.
913      *
914      * @hide
915      */
getDescriptorById(BluetoothDevice device, int instanceId)916     /*package*/ BluetoothGattDescriptor getDescriptorById(BluetoothDevice device, int instanceId) {
917         for (BluetoothGattService svc : mServices) {
918             for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
919                 for (BluetoothGattDescriptor desc : charac.getDescriptors()) {
920                     if (desc.getInstanceId() == instanceId) {
921                         return desc;
922                     }
923                 }
924             }
925         }
926         return null;
927     }
928 
929     /**
930      * Queue the runnable on a {@link Handler} provided by the user, or execute the runnable
931      * immediately if no Handler was provided.
932      */
runOrQueueCallback(final Runnable cb)933     private void runOrQueueCallback(final Runnable cb) {
934         if (mHandler == null) {
935             try {
936                 cb.run();
937             } catch (Exception ex) {
938                 Log.w(TAG, "Unhandled exception in callback", ex);
939             }
940         } else {
941             mHandler.post(cb);
942         }
943     }
944 
945     /**
946      * Register an application callback to start using GATT.
947      *
948      * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
949      * is used to notify success or failure if the function returns true.
950      *
951      * @param callback GATT callback handler that will receive asynchronous callbacks.
952      * @return If true, the callback will be called to notify success or failure, false on immediate
953      * error
954      */
955     @RequiresLegacyBluetoothPermission
956     @RequiresBluetoothConnectPermission
957     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
registerApp(BluetoothGattCallback callback, Handler handler)958     private boolean registerApp(BluetoothGattCallback callback, Handler handler) {
959         return registerApp(callback, handler, false);
960     }
961 
962     /**
963      * Register an application callback to start using GATT.
964      *
965      * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
966      * is used to notify success or failure if the function returns true.
967      *
968      * @param callback GATT callback handler that will receive asynchronous callbacks.
969      * @param eattSupport indicate to allow for eatt support
970      * @return If true, the callback will be called to notify success or failure, false on immediate
971      * error
972      * @hide
973      */
974     @RequiresLegacyBluetoothPermission
975     @RequiresBluetoothConnectPermission
976     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
registerApp(BluetoothGattCallback callback, Handler handler, boolean eattSupport)977     private boolean registerApp(BluetoothGattCallback callback, Handler handler,
978                                 boolean eattSupport) {
979         if (DBG) Log.d(TAG, "registerApp()");
980         if (mService == null) return false;
981 
982         mCallback = callback;
983         mHandler = handler;
984         UUID uuid = UUID.randomUUID();
985         if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid);
986 
987         try {
988             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
989             mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback, eattSupport,
990                     mAttributionSource, recv);
991             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
992         } catch (RemoteException | TimeoutException e) {
993             Log.e(TAG, "", e);
994             return false;
995         }
996 
997         return true;
998     }
999 
1000     /**
1001      * Unregister the current application and callbacks.
1002      */
1003     @UnsupportedAppUsage
1004     @RequiresBluetoothConnectPermission
1005     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
unregisterApp()1006     private void unregisterApp() {
1007         if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf);
1008         if (mService == null || mClientIf == 0) return;
1009 
1010         try {
1011             mCallback = null;
1012             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1013             mService.unregisterClient(mClientIf, mAttributionSource, recv);
1014             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1015             mClientIf = 0;
1016         } catch (RemoteException | TimeoutException e) {
1017             Log.e(TAG, "", e);
1018         }
1019     }
1020 
1021     /**
1022      * Initiate a connection to a Bluetooth GATT capable device.
1023      *
1024      * <p>The connection may not be established right away, but will be
1025      * completed when the remote device is available. A
1026      * {@link BluetoothGattCallback#onConnectionStateChange} callback will be
1027      * invoked when the connection state changes as a result of this function.
1028      *
1029      * <p>The autoConnect parameter determines whether to actively connect to
1030      * the remote device, or rather passively scan and finalize the connection
1031      * when the remote device is in range/available. Generally, the first ever
1032      * connection to a device should be direct (autoConnect set to false) and
1033      * subsequent connections to known devices should be invoked with the
1034      * autoConnect parameter set to true.
1035      *
1036      * @param device Remote device to connect to
1037      * @param autoConnect Whether to directly connect to the remote device (false) or to
1038      * automatically connect as soon as the remote device becomes available (true).
1039      * @return true, if the connection attempt was initiated successfully
1040      */
1041     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1042     @RequiresLegacyBluetoothPermission
1043     @RequiresBluetoothConnectPermission
1044     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
connect(Boolean autoConnect, BluetoothGattCallback callback, Handler handler)1045     /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback,
1046             Handler handler) {
1047         if (DBG) {
1048             Log.d(TAG,
1049                     "connect() - device: " + mDevice + ", auto: " + autoConnect);
1050         }
1051         synchronized (mStateLock) {
1052             if (mConnState != CONN_STATE_IDLE) {
1053                 throw new IllegalStateException("Not idle");
1054             }
1055             mConnState = CONN_STATE_CONNECTING;
1056         }
1057 
1058         mAutoConnect = autoConnect;
1059 
1060         if (!registerApp(callback, handler)) {
1061             synchronized (mStateLock) {
1062                 mConnState = CONN_STATE_IDLE;
1063             }
1064             Log.e(TAG, "Failed to register callback");
1065             return false;
1066         }
1067 
1068         // The connection will continue in the onClientRegistered callback
1069         return true;
1070     }
1071 
1072     /**
1073      * Disconnects an established connection, or cancels a connection attempt
1074      * currently in progress.
1075      */
1076     @RequiresLegacyBluetoothPermission
1077     @RequiresBluetoothConnectPermission
1078     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
disconnect()1079     public void disconnect() {
1080         if (DBG) Log.d(TAG, "cancelOpen() - device: " + mDevice);
1081         if (mService == null || mClientIf == 0) return;
1082 
1083         try {
1084             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1085             mService.clientDisconnect(mClientIf, mDevice.getAddress(), mAttributionSource, recv);
1086             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1087         } catch (RemoteException | TimeoutException e) {
1088             Log.e(TAG, "", e);
1089         }
1090     }
1091 
1092     /**
1093      * Connect back to remote device.
1094      *
1095      * <p>This method is used to re-connect to a remote device after the
1096      * connection has been dropped. If the device is not in range, the
1097      * re-connection will be triggered once the device is back in range.
1098      *
1099      * @return true, if the connection attempt was initiated successfully
1100      */
1101     @RequiresBluetoothConnectPermission
1102     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
connect()1103     public boolean connect() {
1104         try {
1105             if (DBG) {
1106                 Log.d(TAG, "connect(void) - device: " + mDevice
1107                         + ", auto=" + mAutoConnect);
1108             }
1109 
1110             // autoConnect is inverse of "isDirect"
1111             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1112             mService.clientConnect(mClientIf, mDevice.getAddress(), mDevice.getAddressType(),
1113                     !mAutoConnect, mTransport, mOpportunistic, mPhy, mAttributionSource, recv);
1114             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1115             return true;
1116         } catch (RemoteException | TimeoutException e) {
1117             Log.e(TAG, "", e);
1118             return false;
1119         }
1120     }
1121 
1122     /**
1123      * Set the preferred connection PHY for this app. Please note that this is just a
1124      * recommendation, whether the PHY change will happen depends on other applications preferences,
1125      * local and remote controller capabilities. Controller can override these settings.
1126      * <p>
1127      * {@link BluetoothGattCallback#onPhyUpdate} will be triggered as a result of this call, even
1128      * if no PHY change happens. It is also triggered when remote device updates the PHY.
1129      *
1130      * @param txPhy preferred transmitter PHY. Bitwise OR of any of {@link
1131      * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link
1132      * BluetoothDevice#PHY_LE_CODED_MASK}.
1133      * @param rxPhy preferred receiver PHY. Bitwise OR of any of {@link
1134      * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link
1135      * BluetoothDevice#PHY_LE_CODED_MASK}.
1136      * @param phyOptions preferred coding to use when transmitting on the LE Coded PHY. Can be one
1137      * of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2} or
1138      * {@link BluetoothDevice#PHY_OPTION_S8}
1139      */
1140     @RequiresBluetoothConnectPermission
1141     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
setPreferredPhy(int txPhy, int rxPhy, int phyOptions)1142     public void setPreferredPhy(int txPhy, int rxPhy, int phyOptions) {
1143         try {
1144             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1145             mService.clientSetPreferredPhy(mClientIf, mDevice.getAddress(), txPhy, rxPhy,
1146                     phyOptions, mAttributionSource, recv);
1147             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1148         } catch (RemoteException | TimeoutException e) {
1149             Log.e(TAG, "", e);
1150         }
1151     }
1152 
1153     /**
1154      * Read the current transmitter PHY and receiver PHY of the connection. The values are returned
1155      * in {@link BluetoothGattCallback#onPhyRead}
1156      */
1157     @RequiresBluetoothConnectPermission
1158     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
readPhy()1159     public void readPhy() {
1160         try {
1161             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1162             mService.clientReadPhy(mClientIf, mDevice.getAddress(), mAttributionSource, recv);
1163             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1164         } catch (RemoteException | TimeoutException e) {
1165             Log.e(TAG, "", e);
1166         }
1167     }
1168 
1169     /**
1170      * Return the remote bluetooth device this GATT client targets to
1171      *
1172      * @return remote bluetooth device
1173      */
1174     @RequiresNoPermission
getDevice()1175     public BluetoothDevice getDevice() {
1176         return mDevice;
1177     }
1178 
1179     /**
1180      * Discovers services offered by a remote device as well as their
1181      * characteristics and descriptors.
1182      *
1183      * <p>This is an asynchronous operation. Once service discovery is completed,
1184      * the {@link BluetoothGattCallback#onServicesDiscovered} callback is
1185      * triggered. If the discovery was successful, the remote services can be
1186      * retrieved using the {@link #getServices} function.
1187      *
1188      * @return true, if the remote service discovery has been started
1189      */
1190     @RequiresLegacyBluetoothPermission
1191     @RequiresBluetoothConnectPermission
1192     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
discoverServices()1193     public boolean discoverServices() {
1194         if (DBG) Log.d(TAG, "discoverServices() - device: " + mDevice);
1195         if (mService == null || mClientIf == 0) return false;
1196 
1197         mServices.clear();
1198 
1199         try {
1200             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1201             mService.discoverServices(mClientIf, mDevice.getAddress(), mAttributionSource, recv);
1202             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1203         } catch (RemoteException | TimeoutException e) {
1204             Log.e(TAG, "", e);
1205             return false;
1206         }
1207 
1208         return true;
1209     }
1210 
1211     /**
1212      * Discovers a service by UUID. This is exposed only for passing PTS tests.
1213      * It should never be used by real applications. The service is not searched
1214      * for characteristics and descriptors, or returned in any callback.
1215      *
1216      * @return true, if the remote service discovery has been started
1217      * @hide
1218      */
1219     @RequiresLegacyBluetoothPermission
1220     @RequiresBluetoothConnectPermission
1221     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
discoverServiceByUuid(UUID uuid)1222     public boolean discoverServiceByUuid(UUID uuid) {
1223         if (DBG) Log.d(TAG, "discoverServiceByUuid() - device: " + mDevice);
1224         if (mService == null || mClientIf == 0) return false;
1225 
1226         mServices.clear();
1227 
1228         try {
1229             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1230             mService.discoverServiceByUuid(mClientIf, mDevice.getAddress(), new ParcelUuid(uuid),
1231                     mAttributionSource, recv);
1232             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1233         } catch (RemoteException | TimeoutException e) {
1234             Log.e(TAG, "", e);
1235             return false;
1236         }
1237         return true;
1238     }
1239 
1240     /**
1241      * Returns a list of GATT services offered by the remote device.
1242      *
1243      * <p>This function requires that service discovery has been completed
1244      * for the given device.
1245      *
1246      * @return List of services on the remote device. Returns an empty list if service discovery has
1247      * not yet been performed.
1248      */
1249     @RequiresLegacyBluetoothPermission
1250     @RequiresNoPermission
getServices()1251     public List<BluetoothGattService> getServices() {
1252         List<BluetoothGattService> result =
1253                 new ArrayList<BluetoothGattService>();
1254 
1255         for (BluetoothGattService service : mServices) {
1256             if (service.getDevice().equals(mDevice)) {
1257                 result.add(service);
1258             }
1259         }
1260 
1261         return result;
1262     }
1263 
1264     /**
1265      * Returns a {@link BluetoothGattService}, if the requested UUID is
1266      * supported by the remote device.
1267      *
1268      * <p>This function requires that service discovery has been completed
1269      * for the given device.
1270      *
1271      * <p>If multiple instances of the same service (as identified by UUID)
1272      * exist, the first instance of the service is returned.
1273      *
1274      * @param uuid UUID of the requested service
1275      * @return BluetoothGattService if supported, or null if the requested service is not offered by
1276      * the remote device.
1277      */
1278     @RequiresLegacyBluetoothPermission
1279     @RequiresNoPermission
getService(UUID uuid)1280     public BluetoothGattService getService(UUID uuid) {
1281         for (BluetoothGattService service : mServices) {
1282             if (service.getDevice().equals(mDevice) && service.getUuid().equals(uuid)) {
1283                 return service;
1284             }
1285         }
1286 
1287         return null;
1288     }
1289 
1290     /**
1291      * Reads the requested characteristic from the associated remote device.
1292      *
1293      * <p>This is an asynchronous operation. The result of the read operation
1294      * is reported by the {@link BluetoothGattCallback#onCharacteristicRead(BluetoothGatt,
1295      * BluetoothGattCharacteristic, byte[], int)} callback.
1296      *
1297      * @param characteristic Characteristic to read from the remote device
1298      * @return true, if the read operation was initiated successfully
1299      */
1300     @RequiresLegacyBluetoothPermission
1301     @RequiresBluetoothConnectPermission
1302     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
readCharacteristic(BluetoothGattCharacteristic characteristic)1303     public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
1304         if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) == 0) {
1305             return false;
1306         }
1307 
1308         if (VDBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid());
1309         if (mService == null || mClientIf == 0) return false;
1310 
1311         BluetoothGattService service = characteristic.getService();
1312         if (service == null) return false;
1313 
1314         BluetoothDevice device = service.getDevice();
1315         if (device == null) return false;
1316 
1317         synchronized (mDeviceBusyLock) {
1318             if (mDeviceBusy) return false;
1319             mDeviceBusy = true;
1320         }
1321 
1322         try {
1323             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1324             mService.readCharacteristic(mClientIf, device.getAddress(),
1325                     characteristic.getInstanceId(), AUTHENTICATION_NONE, mAttributionSource, recv);
1326             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1327         } catch (RemoteException | TimeoutException e) {
1328             Log.e(TAG, "", e);
1329             synchronized (mDeviceBusyLock) {
1330                 mDeviceBusy = false;
1331             }
1332             return false;
1333         }
1334 
1335         return true;
1336     }
1337 
1338     /**
1339      * Reads the characteristic using its UUID from the associated remote device.
1340      *
1341      * <p>This is an asynchronous operation. The result of the read operation
1342      * is reported by the {@link BluetoothGattCallback#onCharacteristicRead(BluetoothGatt,
1343      * BluetoothGattCharacteristic, byte[], int)} callback.
1344      *
1345      * @param uuid UUID of characteristic to read from the remote device
1346      * @return true, if the read operation was initiated successfully
1347      * @hide
1348      */
1349     @RequiresLegacyBluetoothPermission
1350     @RequiresBluetoothConnectPermission
1351     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
readUsingCharacteristicUuid(UUID uuid, int startHandle, int endHandle)1352     public boolean readUsingCharacteristicUuid(UUID uuid, int startHandle, int endHandle) {
1353         if (VDBG) Log.d(TAG, "readUsingCharacteristicUuid() - uuid: " + uuid);
1354         if (mService == null || mClientIf == 0) return false;
1355 
1356         synchronized (mDeviceBusyLock) {
1357             if (mDeviceBusy) return false;
1358             mDeviceBusy = true;
1359         }
1360 
1361         try {
1362             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1363             mService.readUsingCharacteristicUuid(mClientIf, mDevice.getAddress(),
1364                     new ParcelUuid(uuid), startHandle, endHandle, AUTHENTICATION_NONE,
1365                     mAttributionSource, recv);
1366             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1367         } catch (RemoteException | TimeoutException e) {
1368             Log.e(TAG, "", e);
1369             synchronized (mDeviceBusyLock) {
1370                 mDeviceBusy = false;
1371             }
1372             return false;
1373         }
1374 
1375         return true;
1376     }
1377 
1378     /**
1379      * Writes a given characteristic and its values to the associated remote device.
1380      *
1381      * <p>Once the write operation has been completed, the
1382      * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked,
1383      * reporting the result of the operation.
1384      *
1385      * @param characteristic Characteristic to write on the remote device
1386      * @return true, if the write operation was initiated successfully
1387      * @throws IllegalArgumentException if characteristic or its value are null
1388      *
1389      * @deprecated Use {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[],
1390      * int)} as this is not memory safe because it relies on a {@link BluetoothGattCharacteristic}
1391      * object whose underlying fields are subject to change outside this method.
1392      */
1393     @Deprecated
1394     @RequiresLegacyBluetoothPermission
1395     @RequiresBluetoothConnectPermission
1396     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
writeCharacteristic(BluetoothGattCharacteristic characteristic)1397     public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
1398         try {
1399             return writeCharacteristic(characteristic, characteristic.getValue(),
1400                     characteristic.getWriteType()) == BluetoothStatusCodes.SUCCESS;
1401         } catch (Exception e) {
1402             return false;
1403         }
1404     }
1405 
1406     /** @hide */
1407     @Retention(RetentionPolicy.SOURCE)
1408     @IntDef(value = {
1409             BluetoothStatusCodes.SUCCESS,
1410             BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION,
1411             BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED,
1412             BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
1413             BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED,
1414             BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY,
1415             BluetoothStatusCodes.ERROR_UNKNOWN
1416     })
1417     public @interface WriteOperationReturnValues{}
1418 
1419     /**
1420      * Writes a given characteristic and its values to the associated remote device.
1421      *
1422      * <p>Once the write operation has been completed, the
1423      * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked,
1424      * reporting the result of the operation.
1425      *
1426      * @param characteristic Characteristic to write on the remote device
1427      * @return whether the characteristic was successfully written to
1428      * @throws IllegalArgumentException if characteristic or value are null
1429      */
1430     @RequiresBluetoothConnectPermission
1431     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
1432     @WriteOperationReturnValues
writeCharacteristic(@onNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value, @WriteType int writeType)1433     public int writeCharacteristic(@NonNull BluetoothGattCharacteristic characteristic,
1434             @NonNull byte[] value, @WriteType int writeType) {
1435         if (characteristic == null) {
1436             throw new IllegalArgumentException("characteristic must not be null");
1437         }
1438         if (value == null) {
1439             throw new IllegalArgumentException("value must not be null");
1440         }
1441         if (VDBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid());
1442         if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0
1443                 && (characteristic.getProperties()
1444                 & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) {
1445             return BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED;
1446         }
1447         if (mService == null || mClientIf == 0) {
1448             return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
1449         }
1450 
1451         BluetoothGattService service = characteristic.getService();
1452         if (service == null) {
1453             throw new IllegalArgumentException("Characteristic must have a non-null service");
1454         }
1455 
1456         BluetoothDevice device = service.getDevice();
1457         if (device == null) {
1458             throw new IllegalArgumentException("Service must have a non-null device");
1459         }
1460 
1461         synchronized (mDeviceBusyLock) {
1462             if (mDeviceBusy) {
1463                 return BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY;
1464             }
1465             mDeviceBusy = true;
1466         }
1467 
1468         int requestStatus = BluetoothStatusCodes.ERROR_UNKNOWN;
1469         try {
1470             for (int i = 0; i < WRITE_CHARACTERISTIC_MAX_RETRIES; i++) {
1471                 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
1472                 mService.writeCharacteristic(mClientIf, device.getAddress(),
1473                         characteristic.getInstanceId(), writeType, AUTHENTICATION_NONE, value,
1474                         mAttributionSource, recv);
1475                 requestStatus = recv.awaitResultNoInterrupt(getSyncTimeout())
1476                     .getValue(BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND);
1477                 if (requestStatus != BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY) {
1478                     break;
1479                 }
1480                 try {
1481                     Thread.sleep(WRITE_CHARACTERISTIC_TIME_TO_WAIT);
1482                 } catch (InterruptedException e) {
1483                 }
1484             }
1485         } catch (TimeoutException e) {
1486             Log.e(TAG, "", e);
1487             synchronized (mDeviceBusyLock) {
1488                 mDeviceBusy = false;
1489             }
1490         } catch (RemoteException e) {
1491             Log.e(TAG, "", e);
1492             synchronized (mDeviceBusyLock) {
1493                 mDeviceBusy = false;
1494             }
1495             throw e.rethrowFromSystemServer();
1496         }
1497 
1498         return requestStatus;
1499     }
1500 
1501     /**
1502      * Reads the value for a given descriptor from the associated remote device.
1503      *
1504      * <p>Once the read operation has been completed, the
1505      * {@link BluetoothGattCallback#onDescriptorRead} callback is
1506      * triggered, signaling the result of the operation.
1507      *
1508      * @param descriptor Descriptor value to read from the remote device
1509      * @return true, if the read operation was initiated successfully
1510      */
1511     @RequiresLegacyBluetoothPermission
1512     @RequiresBluetoothConnectPermission
1513     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
readDescriptor(BluetoothGattDescriptor descriptor)1514     public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
1515         if (VDBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid());
1516         if (mService == null || mClientIf == 0) return false;
1517 
1518         BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
1519         if (characteristic == null) return false;
1520 
1521         BluetoothGattService service = characteristic.getService();
1522         if (service == null) return false;
1523 
1524         BluetoothDevice device = service.getDevice();
1525         if (device == null) return false;
1526 
1527         synchronized (mDeviceBusyLock) {
1528             if (mDeviceBusy) return false;
1529             mDeviceBusy = true;
1530         }
1531 
1532         try {
1533             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1534             mService.readDescriptor(mClientIf, device.getAddress(),
1535                     descriptor.getInstanceId(), AUTHENTICATION_NONE, mAttributionSource, recv);
1536             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1537         } catch (RemoteException | TimeoutException e) {
1538             Log.e(TAG, "", e);
1539             synchronized (mDeviceBusyLock) {
1540                 mDeviceBusy = false;
1541             }
1542             return false;
1543         }
1544 
1545         return true;
1546     }
1547 
1548     /**
1549      * Write the value of a given descriptor to the associated remote device.
1550      *
1551      * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is triggered to report the
1552      * result of the write operation.
1553      *
1554      * @param descriptor Descriptor to write to the associated remote device
1555      * @return true, if the write operation was initiated successfully
1556      * @throws IllegalArgumentException if descriptor or its value are null
1557      *
1558      * @deprecated Use {@link BluetoothGatt#writeDescriptor(BluetoothGattDescriptor, byte[])} as
1559      * this is not memory safe because it relies on a {@link BluetoothGattDescriptor} object
1560      * whose underlying fields are subject to change outside this method.
1561      */
1562     @Deprecated
1563     @RequiresLegacyBluetoothPermission
1564     @RequiresBluetoothConnectPermission
1565     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
writeDescriptor(BluetoothGattDescriptor descriptor)1566     public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
1567         try {
1568             return writeDescriptor(descriptor, descriptor.getValue())
1569                     == BluetoothStatusCodes.SUCCESS;
1570         } catch (Exception e) {
1571             return false;
1572         }
1573     }
1574 
1575     /**
1576      * Write the value of a given descriptor to the associated remote device.
1577      *
1578      * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is triggered to report the
1579      * result of the write operation.
1580      *
1581      * @param descriptor Descriptor to write to the associated remote device
1582      * @return true, if the write operation was initiated successfully
1583      * @throws IllegalArgumentException if descriptor or value are null
1584      */
1585     @RequiresBluetoothConnectPermission
1586     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
1587     @WriteOperationReturnValues
writeDescriptor(@onNull BluetoothGattDescriptor descriptor, @NonNull byte[] value)1588     public int writeDescriptor(@NonNull BluetoothGattDescriptor descriptor,
1589             @NonNull byte[] value) {
1590         if (descriptor == null) {
1591             throw new IllegalArgumentException("descriptor must not be null");
1592         }
1593         if (value == null) {
1594             throw new IllegalArgumentException("value must not be null");
1595         }
1596         if (VDBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid());
1597         if (mService == null || mClientIf == 0) {
1598             return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
1599         }
1600 
1601         BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
1602         if (characteristic == null) {
1603             throw new IllegalArgumentException("Descriptor must have a non-null characteristic");
1604         }
1605 
1606         BluetoothGattService service = characteristic.getService();
1607         if (service == null) {
1608             throw new IllegalArgumentException("Characteristic must have a non-null service");
1609         }
1610 
1611         BluetoothDevice device = service.getDevice();
1612         if (device == null) {
1613             throw new IllegalArgumentException("Service must have a non-null device");
1614         }
1615 
1616         synchronized (mDeviceBusyLock) {
1617             if (mDeviceBusy) return BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY;
1618             mDeviceBusy = true;
1619         }
1620 
1621         try {
1622             final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
1623             mService.writeDescriptor(mClientIf, device.getAddress(),
1624                     descriptor.getInstanceId(), AUTHENTICATION_NONE, value, mAttributionSource,
1625                     recv);
1626             return recv.awaitResultNoInterrupt(getSyncTimeout())
1627                 .getValue(BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND);
1628         } catch (TimeoutException e) {
1629             Log.e(TAG, "", e);
1630             synchronized (mDeviceBusyLock) {
1631                 mDeviceBusy = false;
1632             }
1633         } catch (RemoteException e) {
1634             Log.e(TAG, "", e);
1635             synchronized (mDeviceBusyLock) {
1636                 mDeviceBusy = false;
1637             }
1638             e.rethrowFromSystemServer();
1639         }
1640         return BluetoothStatusCodes.ERROR_UNKNOWN;
1641     }
1642 
1643     /**
1644      * Initiates a reliable write transaction for a given remote device.
1645      *
1646      * <p>Once a reliable write transaction has been initiated, all calls
1647      * to {@link #writeCharacteristic} are sent to the remote device for
1648      * verification and queued up for atomic execution. The application will
1649      * receive a {@link BluetoothGattCallback#onCharacteristicWrite} callback in response to every
1650      * {@link #writeCharacteristic(BluetoothGattCharacteristic, byte[], int)} call and is
1651      * responsible for verifying if the value has been transmitted accurately.
1652      *
1653      * <p>After all characteristics have been queued up and verified,
1654      * {@link #executeReliableWrite} will execute all writes. If a characteristic
1655      * was not written correctly, calling {@link #abortReliableWrite} will
1656      * cancel the current transaction without committing any values on the
1657      * remote device.
1658      *
1659      * @return true, if the reliable write transaction has been initiated
1660      */
1661     @RequiresLegacyBluetoothPermission
1662     @RequiresBluetoothConnectPermission
1663     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
beginReliableWrite()1664     public boolean beginReliableWrite() {
1665         if (VDBG) Log.d(TAG, "beginReliableWrite() - device: " + mDevice);
1666         if (mService == null || mClientIf == 0) return false;
1667 
1668         try {
1669             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1670             mService.beginReliableWrite(mClientIf, mDevice.getAddress(), mAttributionSource, recv);
1671             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1672         } catch (RemoteException | TimeoutException e) {
1673             Log.e(TAG, "", e);
1674             return false;
1675         }
1676 
1677         return true;
1678     }
1679 
1680     /**
1681      * Executes a reliable write transaction for a given remote device.
1682      *
1683      * <p>This function will commit all queued up characteristic write
1684      * operations for a given remote device.
1685      *
1686      * <p>A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is
1687      * invoked to indicate whether the transaction has been executed correctly.
1688      *
1689      * @return true, if the request to execute the transaction has been sent
1690      */
1691     @RequiresLegacyBluetoothPermission
1692     @RequiresBluetoothConnectPermission
1693     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
executeReliableWrite()1694     public boolean executeReliableWrite() {
1695         if (VDBG) Log.d(TAG, "executeReliableWrite() - device: " + mDevice);
1696         if (mService == null || mClientIf == 0) return false;
1697 
1698         synchronized (mDeviceBusyLock) {
1699             if (mDeviceBusy) return false;
1700             mDeviceBusy = true;
1701         }
1702 
1703         try {
1704             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1705             mService.endReliableWrite(mClientIf, mDevice.getAddress(), true, mAttributionSource,
1706                     recv);
1707             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1708         } catch (RemoteException | TimeoutException e) {
1709             Log.e(TAG, "", e);
1710             synchronized (mDeviceBusyLock) {
1711                 mDeviceBusy = false;
1712             }
1713             return false;
1714         }
1715 
1716         return true;
1717     }
1718 
1719     /**
1720      * Cancels a reliable write transaction for a given device.
1721      *
1722      * <p>Calling this function will discard all queued characteristic write
1723      * operations for a given remote device.
1724      */
1725     @RequiresLegacyBluetoothPermission
1726     @RequiresBluetoothConnectPermission
1727     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
abortReliableWrite()1728     public void abortReliableWrite() {
1729         if (VDBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice);
1730         if (mService == null || mClientIf == 0) return;
1731 
1732         try {
1733             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1734             mService.endReliableWrite(mClientIf, mDevice.getAddress(), false, mAttributionSource,
1735                     recv);
1736             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1737         } catch (RemoteException | TimeoutException e) {
1738             Log.e(TAG, "", e);
1739         }
1740     }
1741 
1742     /**
1743      * @deprecated Use {@link #abortReliableWrite()}
1744      */
1745     @Deprecated
1746     @RequiresBluetoothConnectPermission
1747     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
abortReliableWrite(BluetoothDevice mDevice)1748     public void abortReliableWrite(BluetoothDevice mDevice) {
1749         abortReliableWrite();
1750     }
1751 
1752     /**
1753      * Enable or disable notifications/indications for a given characteristic.
1754      *
1755      * <p>Once notifications are enabled for a characteristic, a
1756      * {@link BluetoothGattCallback#onCharacteristicChanged(BluetoothGatt,
1757      * BluetoothGattCharacteristic, byte[])} callback will be triggered if the remote device
1758      * indicates that the given characteristic has changed.
1759      *
1760      * @param characteristic The characteristic for which to enable notifications
1761      * @param enable Set to true to enable notifications/indications
1762      * @return true, if the requested notification status was set successfully
1763      */
1764     @RequiresLegacyBluetoothPermission
1765     @RequiresBluetoothConnectPermission
1766     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enable)1767     public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
1768             boolean enable) {
1769         if (DBG) {
1770             Log.d(TAG, "setCharacteristicNotification() - uuid: " + characteristic.getUuid()
1771                     + " enable: " + enable);
1772         }
1773         if (mService == null || mClientIf == 0) return false;
1774 
1775         BluetoothGattService service = characteristic.getService();
1776         if (service == null) return false;
1777 
1778         BluetoothDevice device = service.getDevice();
1779         if (device == null) return false;
1780 
1781         try {
1782             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1783             mService.registerForNotification(mClientIf, device.getAddress(),
1784                     characteristic.getInstanceId(), enable, mAttributionSource, recv);
1785             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1786         } catch (RemoteException | TimeoutException e) {
1787             Log.e(TAG, "", e);
1788             return false;
1789         }
1790 
1791         return true;
1792     }
1793 
1794     /**
1795      * Clears the internal cache and forces a refresh of the services from the
1796      * remote device.
1797      *
1798      * @hide
1799      */
1800     @UnsupportedAppUsage
1801     @RequiresBluetoothConnectPermission
1802     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
refresh()1803     public boolean refresh() {
1804         if (DBG) Log.d(TAG, "refresh() - device: " + mDevice);
1805         if (mService == null || mClientIf == 0) return false;
1806 
1807         try {
1808             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1809             mService.refreshDevice(mClientIf, mDevice.getAddress(), mAttributionSource, recv);
1810             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1811         } catch (RemoteException | TimeoutException e) {
1812             Log.e(TAG, "", e);
1813             return false;
1814         }
1815 
1816         return true;
1817     }
1818 
1819     /**
1820      * Read the RSSI for a connected remote device.
1821      *
1822      * <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be
1823      * invoked when the RSSI value has been read.
1824      *
1825      * @return true, if the RSSI value has been requested successfully
1826      */
1827     @RequiresLegacyBluetoothPermission
1828     @RequiresBluetoothConnectPermission
1829     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
readRemoteRssi()1830     public boolean readRemoteRssi() {
1831         if (DBG) Log.d(TAG, "readRssi() - device: " + mDevice);
1832         if (mService == null || mClientIf == 0) return false;
1833 
1834         try {
1835             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1836             mService.readRemoteRssi(mClientIf, mDevice.getAddress(), mAttributionSource, recv);
1837             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1838         } catch (RemoteException | TimeoutException e) {
1839             Log.e(TAG, "", e);
1840             return false;
1841         }
1842 
1843         return true;
1844     }
1845 
1846     /**
1847      * Request an MTU size used for a given connection.
1848      *
1849      * <p>When performing a write request operation (write without response),
1850      * the data sent is truncated to the MTU size. This function may be used
1851      * to request a larger MTU size to be able to send more data at once.
1852      *
1853      * <p>A {@link BluetoothGattCallback#onMtuChanged} callback will indicate
1854      * whether this operation was successful.
1855      *
1856      * @return true, if the new MTU value has been requested successfully
1857      */
1858     @RequiresLegacyBluetoothPermission
1859     @RequiresBluetoothConnectPermission
1860     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
requestMtu(int mtu)1861     public boolean requestMtu(int mtu) {
1862         if (DBG) {
1863             Log.d(TAG, "configureMTU() - device: " + mDevice
1864                     + " mtu: " + mtu);
1865         }
1866         if (mService == null || mClientIf == 0) return false;
1867 
1868         try {
1869             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1870             mService.configureMTU(mClientIf, mDevice.getAddress(), mtu, mAttributionSource, recv);
1871             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1872         } catch (RemoteException | TimeoutException e) {
1873             Log.e(TAG, "", e);
1874             return false;
1875         }
1876 
1877         return true;
1878     }
1879 
1880     /**
1881      * Request a connection parameter update.
1882      *
1883      * <p>This function will send a connection parameter update request to the
1884      * remote device.
1885      *
1886      * @param connectionPriority Request a specific connection priority. Must be one of {@link
1887      * BluetoothGatt#CONNECTION_PRIORITY_BALANCED}, {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH}
1888      * {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}, or
1889      * {@link BluetoothGatt#CONNECTION_PRIORITY_DCK}.
1890      * @throws IllegalArgumentException If the parameters are outside of their specified range.
1891      */
1892     @RequiresBluetoothConnectPermission
1893     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
requestConnectionPriority(int connectionPriority)1894     public boolean requestConnectionPriority(int connectionPriority) {
1895         if (connectionPriority < CONNECTION_PRIORITY_BALANCED
1896                 || connectionPriority > CONNECTION_PRIORITY_DCK) {
1897             throw new IllegalArgumentException("connectionPriority not within valid range");
1898         }
1899 
1900         if (DBG) Log.d(TAG, "requestConnectionPriority() - params: " + connectionPriority);
1901         if (mService == null || mClientIf == 0) return false;
1902 
1903         try {
1904             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1905             mService.connectionParameterUpdate(mClientIf, mDevice.getAddress(), connectionPriority,
1906                     mAttributionSource, recv);
1907             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1908         } catch (RemoteException | TimeoutException e) {
1909             Log.e(TAG, "", e);
1910             return false;
1911         }
1912 
1913         return true;
1914     }
1915 
1916     /**
1917      * Request an LE connection parameter update.
1918      *
1919      * <p>This function will send an LE connection parameters update request to the remote device.
1920      *
1921      * @return true, if the request is send to the Bluetooth stack.
1922      * @hide
1923      */
1924     @RequiresBluetoothConnectPermission
1925     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
requestLeConnectionUpdate(int minConnectionInterval, int maxConnectionInterval, int slaveLatency, int supervisionTimeout, int minConnectionEventLen, int maxConnectionEventLen)1926     public boolean requestLeConnectionUpdate(int minConnectionInterval, int maxConnectionInterval,
1927                                              int slaveLatency, int supervisionTimeout,
1928                                              int minConnectionEventLen, int maxConnectionEventLen) {
1929         if (DBG) {
1930             Log.d(TAG, "requestLeConnectionUpdate() - min=(" + minConnectionInterval
1931                         + ")" + (1.25 * minConnectionInterval)
1932                         + "msec, max=(" + maxConnectionInterval + ")"
1933                         + (1.25 * maxConnectionInterval) + "msec, latency=" + slaveLatency
1934                         + ", timeout=" + supervisionTimeout + "msec" + ", min_ce="
1935                         + minConnectionEventLen + ", max_ce=" + maxConnectionEventLen);
1936         }
1937         if (mService == null || mClientIf == 0) return false;
1938 
1939         try {
1940             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1941             mService.leConnectionUpdate(mClientIf, mDevice.getAddress(),
1942                     minConnectionInterval, maxConnectionInterval,
1943                     slaveLatency, supervisionTimeout,
1944                     minConnectionEventLen, maxConnectionEventLen,
1945                     mAttributionSource, recv);
1946             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1947         } catch (RemoteException | TimeoutException e) {
1948             Log.e(TAG, "", e);
1949             return false;
1950         }
1951 
1952         return true;
1953     }
1954 
1955     /**
1956      * Request LE subrate mode.
1957      *
1958      * <p>This function will send a LE subrate request to the remote device.
1959      *
1960      * @param subrateMode Request a specific subrate mode.
1961      * @throws IllegalArgumentException If the parameters are outside of their specified range.
1962      * @return true, if the request is send to the Bluetooth stack.
1963      * @hide
1964      */
1965     @RequiresBluetoothConnectPermission
1966     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
requestSubrateMode(@ubrateRequestMode int subrateMode)1967     public boolean requestSubrateMode(@SubrateRequestMode int subrateMode) {
1968         if (subrateMode < SUBRATE_REQUEST_MODE_BALANCED
1969                 || subrateMode > SUBRATE_REQUEST_MODE_LOW_POWER) {
1970             throw new IllegalArgumentException("Subrate Mode not within valid range");
1971         }
1972 
1973         if (DBG) {
1974             Log.d(TAG, "requestsubrateMode() - subrateMode: " + subrateMode);
1975         }
1976         if (mService == null || mClientIf == 0) {
1977             return false;
1978         }
1979 
1980         try {
1981             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1982             mService.subrateModeRequest(
1983                     mClientIf, mDevice.getAddress(), subrateMode, mAttributionSource, recv);
1984             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1985         } catch (RemoteException | TimeoutException e) {
1986             Log.e(TAG, "", e);
1987             return false;
1988         }
1989         return true;
1990     }
1991 
1992     /**
1993      * Request a LE subrate request.
1994      *
1995      * <p>This function will send a LE subrate request to the remote device.
1996      *
1997      * @return true, if the request is send to the Bluetooth stack.
1998      * @hide
1999      */
2000     @RequiresBluetoothConnectPermission
2001     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
bleSubrateRequest(int subrateMin, int subrateMax, int maxLatency, int contNumber, int supervisionTimeout)2002     public boolean bleSubrateRequest(int subrateMin, int subrateMax, int maxLatency, int contNumber,
2003             int supervisionTimeout) {
2004         if (DBG) {
2005             Log.d(TAG,
2006                     "bleSubrateRequest() - subrateMin=" + subrateMin + " subrateMax=" + (subrateMax)
2007                             + " maxLatency= " + maxLatency + "contNumber=" + contNumber
2008                             + " supervisionTimeout=" + supervisionTimeout);
2009         }
2010         if (mService == null || mClientIf == 0) {
2011             return false;
2012         }
2013 
2014         try {
2015             final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
2016             mService.leSubrateRequest(mClientIf, mDevice.getAddress(), subrateMin, subrateMax,
2017                     maxLatency, contNumber, supervisionTimeout, mAttributionSource, recv);
2018             recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
2019         } catch (RemoteException | TimeoutException e) {
2020             Log.e(TAG, "", e);
2021             return false;
2022         }
2023         return true;
2024     }
2025 
2026     /**
2027      * @deprecated Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
2028      * with {@link BluetoothProfile#GATT} as argument
2029      * @throws UnsupportedOperationException
2030      */
2031     @Override
2032     @RequiresNoPermission
2033     @Deprecated
getConnectionState(BluetoothDevice device)2034     public int getConnectionState(BluetoothDevice device) {
2035         throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
2036     }
2037 
2038     /**
2039      * @deprecated Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
2040      * with {@link BluetoothProfile#GATT} as argument
2041      *
2042      * @throws UnsupportedOperationException
2043      */
2044     @Override
2045     @RequiresNoPermission
2046     @Deprecated
getConnectedDevices()2047     public List<BluetoothDevice> getConnectedDevices() {
2048         throw new UnsupportedOperationException(
2049                 "Use BluetoothManager#getConnectedDevices instead.");
2050     }
2051 
2052     /**
2053      * @deprecated Not supported - please use
2054      * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])}
2055      * with {@link BluetoothProfile#GATT} as first argument
2056      *
2057      * @throws UnsupportedOperationException
2058      */
2059     @Override
2060     @RequiresNoPermission
2061     @Deprecated
getDevicesMatchingConnectionStates(int[] states)2062     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
2063         throw new UnsupportedOperationException(
2064                 "Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
2065     }
2066 }
2067