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