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