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.content.Context;
20 import android.os.ParcelUuid;
21 import android.os.RemoteException;
22 import android.util.Log;
23 
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.UUID;
27 
28 /**
29  * Public API for the Bluetooth GATT Profile.
30  *
31  * <p>This class provides Bluetooth GATT functionality to enable communication
32  * with Bluetooth Smart or Smart Ready devices.
33  *
34  * <p>To connect to a remote peripheral device, create a {@link BluetoothGattCallback}
35  * and call {@link BluetoothDevice#connectGatt} to get a instance of this class.
36  * GATT capable devices can be discovered using the Bluetooth device discovery or BLE
37  * scan process.
38  */
39 public final class BluetoothGatt implements BluetoothProfile {
40     private static final String TAG = "BluetoothGatt";
41     private static final boolean DBG = true;
42     private static final boolean VDBG = false;
43 
44     private final Context mContext;
45     private IBluetoothGatt mService;
46     private BluetoothGattCallback mCallback;
47     private int mClientIf;
48     private boolean mAuthRetry = false;
49     private BluetoothDevice mDevice;
50     private boolean mAutoConnect;
51     private int mConnState;
52     private final Object mStateLock = new Object();
53     private Boolean mDeviceBusy = false;
54     private int mTransport;
55 
56     private static final int CONN_STATE_IDLE = 0;
57     private static final int CONN_STATE_CONNECTING = 1;
58     private static final int CONN_STATE_CONNECTED = 2;
59     private static final int CONN_STATE_DISCONNECTING = 3;
60     private static final int CONN_STATE_CLOSED = 4;
61 
62     private List<BluetoothGattService> mServices;
63 
64     /** A GATT operation completed successfully */
65     public static final int GATT_SUCCESS = 0;
66 
67     /** GATT read operation is not permitted */
68     public static final int GATT_READ_NOT_PERMITTED = 0x2;
69 
70     /** GATT write operation is not permitted */
71     public static final int GATT_WRITE_NOT_PERMITTED = 0x3;
72 
73     /** Insufficient authentication for a given operation */
74     public static final int GATT_INSUFFICIENT_AUTHENTICATION = 0x5;
75 
76     /** The given request is not supported */
77     public static final int GATT_REQUEST_NOT_SUPPORTED = 0x6;
78 
79     /** Insufficient encryption for a given operation */
80     public static final int GATT_INSUFFICIENT_ENCRYPTION = 0xf;
81 
82     /** A read or write operation was requested with an invalid offset */
83     public static final int GATT_INVALID_OFFSET = 0x7;
84 
85     /** A write operation exceeds the maximum length of the attribute */
86     public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 0xd;
87 
88     /** A remote device connection is congested. */
89     public static final int GATT_CONNECTION_CONGESTED = 0x8f;
90 
91     /** A GATT operation failed, errors other than the above */
92     public static final int GATT_FAILURE = 0x101;
93 
94     /**
95      * Connection paramter update - Use the connection paramters recommended by the
96      * Bluetooth SIG. This is the default value if no connection parameter update
97      * is requested.
98      */
99     public static final int CONNECTION_PRIORITY_BALANCED = 0;
100 
101     /**
102      * Connection paramter update - Request a high priority, low latency connection.
103      * An application should only request high priority connection paramters to transfer
104      * large amounts of data over LE quickly. Once the transfer is complete, the application
105      * should request {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED} connectoin parameters
106      * to reduce energy use.
107      */
108     public static final int CONNECTION_PRIORITY_HIGH = 1;
109 
110     /** Connection paramter update - Request low power, reduced data rate connection parameters. */
111     public static final int CONNECTION_PRIORITY_LOW_POWER = 2;
112 
113     /**
114      * No authentication required.
115      * @hide
116      */
117     /*package*/ static final int AUTHENTICATION_NONE = 0;
118 
119     /**
120      * Authentication requested; no man-in-the-middle protection required.
121      * @hide
122      */
123     /*package*/ static final int AUTHENTICATION_NO_MITM = 1;
124 
125     /**
126      * Authentication with man-in-the-middle protection requested.
127      * @hide
128      */
129     /*package*/ static final int AUTHENTICATION_MITM = 2;
130 
131     /**
132      * Bluetooth GATT callbacks. Overrides the default BluetoothGattCallback implementation.
133      */
134     private final IBluetoothGattCallback mBluetoothGattCallback =
135         new BluetoothGattCallbackWrapper() {
136             /**
137              * Application interface registered - app is ready to go
138              * @hide
139              */
140             public void onClientRegistered(int status, int clientIf) {
141                 if (DBG) Log.d(TAG, "onClientRegistered() - status=" + status
142                     + " clientIf=" + clientIf);
143                 if (VDBG) {
144                     synchronized(mStateLock) {
145                         if (mConnState != CONN_STATE_CONNECTING) {
146                             Log.e(TAG, "Bad connection state: " + mConnState);
147                         }
148                     }
149                 }
150                 mClientIf = clientIf;
151                 if (status != GATT_SUCCESS) {
152                     mCallback.onConnectionStateChange(BluetoothGatt.this, GATT_FAILURE,
153                                                       BluetoothProfile.STATE_DISCONNECTED);
154                     synchronized(mStateLock) {
155                         mConnState = CONN_STATE_IDLE;
156                     }
157                     return;
158                 }
159                 try {
160                     mService.clientConnect(mClientIf, mDevice.getAddress(),
161                                            !mAutoConnect, mTransport); // autoConnect is inverse of "isDirect"
162                 } catch (RemoteException e) {
163                     Log.e(TAG,"",e);
164                 }
165             }
166 
167             /**
168              * Client connection state changed
169              * @hide
170              */
171             public void onClientConnectionState(int status, int clientIf,
172                                                 boolean connected, String address) {
173                 if (DBG) Log.d(TAG, "onClientConnectionState() - status=" + status
174                                  + " clientIf=" + clientIf + " device=" + address);
175                 if (!address.equals(mDevice.getAddress())) {
176                     return;
177                 }
178                 int profileState = connected ? BluetoothProfile.STATE_CONNECTED :
179                                                BluetoothProfile.STATE_DISCONNECTED;
180                 try {
181                     mCallback.onConnectionStateChange(BluetoothGatt.this, status, profileState);
182                 } catch (Exception ex) {
183                     Log.w(TAG, "Unhandled exception in callback", ex);
184                 }
185 
186                 synchronized(mStateLock) {
187                     if (connected) {
188                         mConnState = CONN_STATE_CONNECTED;
189                     } else {
190                         mConnState = CONN_STATE_IDLE;
191                     }
192                 }
193 
194                 synchronized(mDeviceBusy) {
195                     mDeviceBusy = false;
196                 }
197             }
198 
199             /**
200              * Remote search has been completed.
201              * The internal object structure should now reflect the state
202              * of the remote device database. Let the application know that
203              * we are done at this point.
204              * @hide
205              */
206             public void onSearchComplete(String address, List<BluetoothGattService> services,
207                                          int status) {
208                 if (DBG) Log.d(TAG, "onSearchComplete() = Device=" + address + " Status=" + status);
209                 if (!address.equals(mDevice.getAddress())) {
210                     return;
211                 }
212 
213                 for (BluetoothGattService s : services) {
214                     //services we receive don't have device set properly.
215                     s.setDevice(mDevice);
216                 }
217 
218                 mServices.addAll(services);
219 
220                 // Fix references to included services, as they doesn't point to right objects.
221                 for (BluetoothGattService fixedService : mServices) {
222                     ArrayList<BluetoothGattService> includedServices =
223                         new ArrayList(fixedService.getIncludedServices());
224                     fixedService.getIncludedServices().clear();
225 
226                     for(BluetoothGattService brokenRef : includedServices) {
227                         BluetoothGattService includedService = getService(mDevice,
228                             brokenRef.getUuid(), brokenRef.getInstanceId(), brokenRef.getType());
229                         if (includedService != null) {
230                             fixedService.addIncludedService(includedService);
231                         } else {
232                             Log.e(TAG, "Broken GATT database: can't find included service.");
233                         }
234                     }
235                 }
236 
237                 try {
238                     mCallback.onServicesDiscovered(BluetoothGatt.this, status);
239                 } catch (Exception ex) {
240                     Log.w(TAG, "Unhandled exception in callback", ex);
241                 }
242             }
243 
244             /**
245              * Remote characteristic has been read.
246              * Updates the internal value.
247              * @hide
248              */
249             public void onCharacteristicRead(String address, int status, int handle, byte[] value) {
250                 if (VDBG) Log.d(TAG, "onCharacteristicRead() - Device=" + address
251                             + " handle=" + handle + " Status=" + status);
252 
253                  Log.w(TAG, "onCharacteristicRead() - Device=" + address
254                             + " handle=" + handle + " Status=" + status);
255 
256                 if (!address.equals(mDevice.getAddress())) {
257                     return;
258                 }
259 
260                 synchronized(mDeviceBusy) {
261                     mDeviceBusy = false;
262                 }
263 
264                 if ((status == GATT_INSUFFICIENT_AUTHENTICATION
265                   || status == GATT_INSUFFICIENT_ENCRYPTION)
266                   && mAuthRetry == false) {
267                     try {
268                         mAuthRetry = true;
269                         mService.readCharacteristic(mClientIf, address, handle, AUTHENTICATION_MITM);
270                         return;
271                     } catch (RemoteException e) {
272                         Log.e(TAG,"",e);
273                     }
274                 }
275 
276                 mAuthRetry = false;
277 
278                 BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice, handle);
279                 if (characteristic == null) {
280                     Log.w(TAG, "onCharacteristicRead() failed to find characteristic!");
281                     return;
282                 }
283 
284                 if (status == 0) characteristic.setValue(value);
285 
286                 try {
287                     mCallback.onCharacteristicRead(BluetoothGatt.this, characteristic, status);
288                 } catch (Exception ex) {
289                     Log.w(TAG, "Unhandled exception in callback", ex);
290                 }
291             }
292 
293             /**
294              * Characteristic has been written to the remote device.
295              * Let the app know how we did...
296              * @hide
297              */
298             public void onCharacteristicWrite(String address, int status, int handle) {
299                 if (VDBG) Log.d(TAG, "onCharacteristicWrite() - Device=" + address
300                             + " handle=" + handle + " Status=" + status);
301 
302                 if (!address.equals(mDevice.getAddress())) {
303                     return;
304                 }
305 
306                 synchronized(mDeviceBusy) {
307                     mDeviceBusy = false;
308                 }
309 
310                 BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice, handle);
311                 if (characteristic == null) return;
312 
313                 if ((status == GATT_INSUFFICIENT_AUTHENTICATION
314                   || status == GATT_INSUFFICIENT_ENCRYPTION)
315                   && mAuthRetry == false) {
316                     try {
317                         mAuthRetry = true;
318                         mService.writeCharacteristic(mClientIf, address, handle,
319                             characteristic.getWriteType(), AUTHENTICATION_MITM,
320                             characteristic.getValue());
321                         return;
322                     } catch (RemoteException e) {
323                         Log.e(TAG,"",e);
324                     }
325                 }
326 
327                 mAuthRetry = false;
328 
329                 try {
330                     mCallback.onCharacteristicWrite(BluetoothGatt.this, characteristic, status);
331                 } catch (Exception ex) {
332                     Log.w(TAG, "Unhandled exception in callback", ex);
333                 }
334             }
335 
336             /**
337              * Remote characteristic has been updated.
338              * Updates the internal value.
339              * @hide
340              */
341             public void onNotify(String address, int handle, byte[] value) {
342                 if (VDBG) Log.d(TAG, "onNotify() - Device=" + address + " handle=" + handle);
343 
344                 if (!address.equals(mDevice.getAddress())) {
345                     return;
346                 }
347 
348                 BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice, handle);
349                 if (characteristic == null) return;
350 
351                 characteristic.setValue(value);
352 
353                 try {
354                     mCallback.onCharacteristicChanged(BluetoothGatt.this, characteristic);
355                 } catch (Exception ex) {
356                     Log.w(TAG, "Unhandled exception in callback", ex);
357                 }
358             }
359 
360             /**
361              * Descriptor has been read.
362              * @hide
363              */
364             public void onDescriptorRead(String address, int status, int handle, byte[] value) {
365                 if (VDBG) Log.d(TAG, "onDescriptorRead() - Device=" + address + " handle=" + handle);
366 
367                 if (!address.equals(mDevice.getAddress())) {
368                     return;
369                 }
370 
371                 synchronized(mDeviceBusy) {
372                     mDeviceBusy = false;
373                 }
374 
375                 BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle);
376                 if (descriptor == null) return;
377 
378                 if (status == 0) descriptor.setValue(value);
379 
380                 if ((status == GATT_INSUFFICIENT_AUTHENTICATION
381                   || status == GATT_INSUFFICIENT_ENCRYPTION)
382                   && mAuthRetry == false) {
383                     try {
384                         mAuthRetry = true;
385                         mService.readDescriptor(mClientIf, address, handle, AUTHENTICATION_MITM);
386                         return;
387                     } catch (RemoteException e) {
388                         Log.e(TAG,"",e);
389                     }
390                 }
391 
392                 mAuthRetry = true;
393 
394                 try {
395                     mCallback.onDescriptorRead(BluetoothGatt.this, descriptor, status);
396                 } catch (Exception ex) {
397                     Log.w(TAG, "Unhandled exception in callback", ex);
398                 }
399             }
400 
401             /**
402              * Descriptor write operation complete.
403              * @hide
404              */
405             public void onDescriptorWrite(String address, int status, int handle) {
406                 if (VDBG) Log.d(TAG, "onDescriptorWrite() - Device=" + address + " handle=" + handle);
407 
408                 if (!address.equals(mDevice.getAddress())) {
409                     return;
410                 }
411 
412                 synchronized(mDeviceBusy) {
413                     mDeviceBusy = false;
414                 }
415 
416                 BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle);
417                 if (descriptor == null) return;
418 
419                 if ((status == GATT_INSUFFICIENT_AUTHENTICATION
420                   || status == GATT_INSUFFICIENT_ENCRYPTION)
421                   && mAuthRetry == false) {
422                     try {
423                         mAuthRetry = true;
424                         mService.writeDescriptor(mClientIf, address, handle,
425                             BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT,
426                             AUTHENTICATION_MITM, descriptor.getValue());
427                         return;
428                     } catch (RemoteException e) {
429                         Log.e(TAG,"",e);
430                     }
431                 }
432 
433                 mAuthRetry = false;
434 
435                 try {
436                     mCallback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
437                 } catch (Exception ex) {
438                     Log.w(TAG, "Unhandled exception in callback", ex);
439                 }
440             }
441 
442             /**
443              * Prepared write transaction completed (or aborted)
444              * @hide
445              */
446             public void onExecuteWrite(String address, int status) {
447                 if (VDBG) Log.d(TAG, "onExecuteWrite() - Device=" + address
448                     + " status=" + status);
449                 if (!address.equals(mDevice.getAddress())) {
450                     return;
451                 }
452 
453                 synchronized(mDeviceBusy) {
454                     mDeviceBusy = false;
455                 }
456 
457                 try {
458                     mCallback.onReliableWriteCompleted(BluetoothGatt.this, status);
459                 } catch (Exception ex) {
460                     Log.w(TAG, "Unhandled exception in callback", ex);
461                 }
462             }
463 
464             /**
465              * Remote device RSSI has been read
466              * @hide
467              */
468             public void onReadRemoteRssi(String address, int rssi, int status) {
469                 if (VDBG) Log.d(TAG, "onReadRemoteRssi() - Device=" + address +
470                             " rssi=" + rssi + " status=" + status);
471                 if (!address.equals(mDevice.getAddress())) {
472                     return;
473                 }
474                 try {
475                     mCallback.onReadRemoteRssi(BluetoothGatt.this, rssi, status);
476                 } catch (Exception ex) {
477                     Log.w(TAG, "Unhandled exception in callback", ex);
478                 }
479             }
480 
481             /**
482              * Callback invoked when the MTU for a given connection changes
483              * @hide
484              */
485             public void onConfigureMTU(String address, int mtu, int status) {
486                 if (DBG) Log.d(TAG, "onConfigureMTU() - Device=" + address +
487                             " mtu=" + mtu + " status=" + status);
488                 if (!address.equals(mDevice.getAddress())) {
489                     return;
490                 }
491                 try {
492                     mCallback.onMtuChanged(BluetoothGatt.this, mtu, status);
493                 } catch (Exception ex) {
494                     Log.w(TAG, "Unhandled exception in callback", ex);
495                 }
496             }
497         };
498 
BluetoothGatt(Context context, IBluetoothGatt iGatt, BluetoothDevice device, int transport)499     /*package*/ BluetoothGatt(Context context, IBluetoothGatt iGatt, BluetoothDevice device,
500                                 int transport) {
501         mContext = context;
502         mService = iGatt;
503         mDevice = device;
504         mTransport = transport;
505         mServices = new ArrayList<BluetoothGattService>();
506 
507         mConnState = CONN_STATE_IDLE;
508     }
509 
510     /**
511      * Close this Bluetooth GATT client.
512      *
513      * Application should call this method as early as possible after it is done with
514      * this GATT client.
515      */
close()516     public void close() {
517         if (DBG) Log.d(TAG, "close()");
518 
519         unregisterApp();
520         mConnState = CONN_STATE_CLOSED;
521     }
522 
523     /**
524      * Returns a service by UUID, instance and type.
525      * @hide
526      */
getService(BluetoothDevice device, UUID uuid, int instanceId, int type)527     /*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid,
528                                                 int instanceId, int type) {
529         for(BluetoothGattService svc : mServices) {
530             if (svc.getDevice().equals(device) &&
531                 svc.getType() == type &&
532                 svc.getInstanceId() == instanceId &&
533                 svc.getUuid().equals(uuid)) {
534                 return svc;
535             }
536         }
537         return null;
538     }
539 
540 
541     /**
542      * Returns a characteristic with id equal to instanceId.
543      * @hide
544      */
getCharacteristicById(BluetoothDevice device, int instanceId)545     /*package*/ BluetoothGattCharacteristic getCharacteristicById(BluetoothDevice device, int instanceId) {
546         for(BluetoothGattService svc : mServices) {
547             for(BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
548                 if (charac.getInstanceId() == instanceId)
549                     return charac;
550             }
551         }
552         return null;
553     }
554 
555     /**
556      * Returns a descriptor with id equal to instanceId.
557      * @hide
558      */
getDescriptorById(BluetoothDevice device, int instanceId)559     /*package*/ BluetoothGattDescriptor getDescriptorById(BluetoothDevice device, int instanceId) {
560         for(BluetoothGattService svc : mServices) {
561             for(BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
562                 for(BluetoothGattDescriptor desc : charac.getDescriptors()) {
563                     if (desc.getInstanceId() == instanceId)
564                         return desc;
565                 }
566             }
567         }
568         return null;
569     }
570 
571     /**
572      * Register an application callback to start using GATT.
573      *
574      * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
575      * is used to notify success or failure if the function returns true.
576      *
577      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
578      *
579      * @param callback GATT callback handler that will receive asynchronous callbacks.
580      * @return If true, the callback will be called to notify success or failure,
581      *         false on immediate error
582      */
registerApp(BluetoothGattCallback callback)583     private boolean registerApp(BluetoothGattCallback callback) {
584         if (DBG) Log.d(TAG, "registerApp()");
585         if (mService == null) return false;
586 
587         mCallback = callback;
588         UUID uuid = UUID.randomUUID();
589         if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid);
590 
591         try {
592             mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback);
593         } catch (RemoteException e) {
594             Log.e(TAG,"",e);
595             return false;
596         }
597 
598         return true;
599     }
600 
601     /**
602      * Unregister the current application and callbacks.
603      */
unregisterApp()604     private void unregisterApp() {
605         if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf);
606         if (mService == null || mClientIf == 0) return;
607 
608         try {
609             mCallback = null;
610             mService.unregisterClient(mClientIf);
611             mClientIf = 0;
612         } catch (RemoteException e) {
613             Log.e(TAG,"",e);
614         }
615     }
616 
617     /**
618      * Initiate a connection to a Bluetooth GATT capable device.
619      *
620      * <p>The connection may not be established right away, but will be
621      * completed when the remote device is available. A
622      * {@link BluetoothGattCallback#onConnectionStateChange} callback will be
623      * invoked when the connection state changes as a result of this function.
624      *
625      * <p>The autoConnect parameter determines whether to actively connect to
626      * the remote device, or rather passively scan and finalize the connection
627      * when the remote device is in range/available. Generally, the first ever
628      * connection to a device should be direct (autoConnect set to false) and
629      * subsequent connections to known devices should be invoked with the
630      * autoConnect parameter set to true.
631      *
632      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
633      *
634      * @param device Remote device to connect to
635      * @param autoConnect Whether to directly connect to the remote device (false)
636      *                    or to automatically connect as soon as the remote
637      *                    device becomes available (true).
638      * @return true, if the connection attempt was initiated successfully
639      */
connect(Boolean autoConnect, BluetoothGattCallback callback)640     /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback) {
641         if (DBG) Log.d(TAG, "connect() - device: " + mDevice.getAddress() + ", auto: " + autoConnect);
642         synchronized(mStateLock) {
643             if (mConnState != CONN_STATE_IDLE) {
644                 throw new IllegalStateException("Not idle");
645             }
646             mConnState = CONN_STATE_CONNECTING;
647         }
648 
649         mAutoConnect = autoConnect;
650 
651         if (!registerApp(callback)) {
652             synchronized(mStateLock) {
653                 mConnState = CONN_STATE_IDLE;
654             }
655             Log.e(TAG, "Failed to register callback");
656             return false;
657         }
658 
659         // The connection will continue in the onClientRegistered callback
660         return true;
661     }
662 
663     /**
664      * Disconnects an established connection, or cancels a connection attempt
665      * currently in progress.
666      *
667      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
668      */
disconnect()669     public void disconnect() {
670         if (DBG) Log.d(TAG, "cancelOpen() - device: " + mDevice.getAddress());
671         if (mService == null || mClientIf == 0) return;
672 
673         try {
674             mService.clientDisconnect(mClientIf, mDevice.getAddress());
675         } catch (RemoteException e) {
676             Log.e(TAG,"",e);
677         }
678     }
679 
680     /**
681      * Connect back to remote device.
682      *
683      * <p>This method is used to re-connect to a remote device after the
684      * connection has been dropped. If the device is not in range, the
685      * re-connection will be triggered once the device is back in range.
686      *
687      * @return true, if the connection attempt was initiated successfully
688      */
connect()689     public boolean connect() {
690         try {
691             mService.clientConnect(mClientIf, mDevice.getAddress(),
692                                    false, mTransport); // autoConnect is inverse of "isDirect"
693             return true;
694         } catch (RemoteException e) {
695             Log.e(TAG,"",e);
696             return false;
697         }
698     }
699 
700     /**
701      * Return the remote bluetooth device this GATT client targets to
702      *
703      * @return remote bluetooth device
704      */
getDevice()705     public BluetoothDevice getDevice() {
706         return mDevice;
707     }
708 
709     /**
710      * Discovers services offered by a remote device as well as their
711      * characteristics and descriptors.
712      *
713      * <p>This is an asynchronous operation. Once service discovery is completed,
714      * the {@link BluetoothGattCallback#onServicesDiscovered} callback is
715      * triggered. If the discovery was successful, the remote services can be
716      * retrieved using the {@link #getServices} function.
717      *
718      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
719      *
720      * @return true, if the remote service discovery has been started
721      */
discoverServices()722     public boolean discoverServices() {
723         if (DBG) Log.d(TAG, "discoverServices() - device: " + mDevice.getAddress());
724         if (mService == null || mClientIf == 0) return false;
725 
726         mServices.clear();
727 
728         try {
729             mService.discoverServices(mClientIf, mDevice.getAddress());
730         } catch (RemoteException e) {
731             Log.e(TAG,"",e);
732             return false;
733         }
734 
735         return true;
736     }
737 
738     /**
739      * Returns a list of GATT services offered by the remote device.
740      *
741      * <p>This function requires that service discovery has been completed
742      * for the given device.
743      *
744      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
745      *
746      * @return List of services on the remote device. Returns an empty list
747      *         if service discovery has not yet been performed.
748      */
getServices()749     public List<BluetoothGattService> getServices() {
750         List<BluetoothGattService> result =
751                 new ArrayList<BluetoothGattService>();
752 
753         for (BluetoothGattService service : mServices) {
754             if (service.getDevice().equals(mDevice)) {
755                 result.add(service);
756             }
757         }
758 
759         return result;
760     }
761 
762     /**
763      * Returns a {@link BluetoothGattService}, if the requested UUID is
764      * supported by the remote device.
765      *
766      * <p>This function requires that service discovery has been completed
767      * for the given device.
768      *
769      * <p>If multiple instances of the same service (as identified by UUID)
770      * exist, the first instance of the service is returned.
771      *
772      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
773      *
774      * @param uuid UUID of the requested service
775      * @return BluetoothGattService if supported, or null if the requested
776      *         service is not offered by the remote device.
777      */
getService(UUID uuid)778     public BluetoothGattService getService(UUID uuid) {
779         for (BluetoothGattService service : mServices) {
780             if (service.getDevice().equals(mDevice) &&
781                 service.getUuid().equals(uuid)) {
782                 return service;
783             }
784         }
785 
786         return null;
787     }
788 
789     /**
790      * Reads the requested characteristic from the associated remote device.
791      *
792      * <p>This is an asynchronous operation. The result of the read operation
793      * is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
794      * callback.
795      *
796      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
797      *
798      * @param characteristic Characteristic to read from the remote device
799      * @return true, if the read operation was initiated successfully
800      */
readCharacteristic(BluetoothGattCharacteristic characteristic)801     public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
802         if ((characteristic.getProperties() &
803                 BluetoothGattCharacteristic.PROPERTY_READ) == 0) return false;
804 
805         if (VDBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid());
806         if (mService == null || mClientIf == 0) return false;
807 
808         BluetoothGattService service = characteristic.getService();
809         if (service == null) return false;
810 
811         BluetoothDevice device = service.getDevice();
812         if (device == null) return false;
813 
814         synchronized(mDeviceBusy) {
815             if (mDeviceBusy) return false;
816             mDeviceBusy = true;
817         }
818 
819         try {
820             mService.readCharacteristic(mClientIf, device.getAddress(),
821                 characteristic.getInstanceId(), AUTHENTICATION_NONE);
822         } catch (RemoteException e) {
823             Log.e(TAG,"",e);
824             mDeviceBusy = false;
825             return false;
826         }
827 
828         return true;
829     }
830 
831     /**
832      * Writes a given characteristic and its values to the associated remote device.
833      *
834      * <p>Once the write operation has been completed, the
835      * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked,
836      * reporting the result of the operation.
837      *
838      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
839      *
840      * @param characteristic Characteristic to write on the remote device
841      * @return true, if the write operation was initiated successfully
842      */
writeCharacteristic(BluetoothGattCharacteristic characteristic)843     public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
844         if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0
845             && (characteristic.getProperties() &
846                 BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) return false;
847 
848         if (VDBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid());
849         if (mService == null || mClientIf == 0 || characteristic.getValue() == null) return false;
850 
851         BluetoothGattService service = characteristic.getService();
852         if (service == null) return false;
853 
854         BluetoothDevice device = service.getDevice();
855         if (device == null) return false;
856 
857         synchronized(mDeviceBusy) {
858             if (mDeviceBusy) return false;
859             mDeviceBusy = true;
860         }
861 
862         try {
863             mService.writeCharacteristic(mClientIf, device.getAddress(),
864                 characteristic.getInstanceId(), characteristic.getWriteType(),
865                 AUTHENTICATION_NONE, characteristic.getValue());
866         } catch (RemoteException e) {
867             Log.e(TAG,"",e);
868             mDeviceBusy = false;
869             return false;
870         }
871 
872         return true;
873     }
874 
875     /**
876      * Reads the value for a given descriptor from the associated remote device.
877      *
878      * <p>Once the read operation has been completed, the
879      * {@link BluetoothGattCallback#onDescriptorRead} callback is
880      * triggered, signaling the result of the operation.
881      *
882      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
883      *
884      * @param descriptor Descriptor value to read from the remote device
885      * @return true, if the read operation was initiated successfully
886      */
readDescriptor(BluetoothGattDescriptor descriptor)887     public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
888         if (VDBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid());
889         if (mService == null || mClientIf == 0) return false;
890 
891         BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
892         if (characteristic == null) return false;
893 
894         BluetoothGattService service = characteristic.getService();
895         if (service == null) return false;
896 
897         BluetoothDevice device = service.getDevice();
898         if (device == null) return false;
899 
900         synchronized(mDeviceBusy) {
901             if (mDeviceBusy) return false;
902             mDeviceBusy = true;
903         }
904 
905         try {
906             mService.readDescriptor(mClientIf, device.getAddress(),
907                 descriptor.getInstanceId(), AUTHENTICATION_NONE);
908         } catch (RemoteException e) {
909             Log.e(TAG,"",e);
910             mDeviceBusy = false;
911             return false;
912         }
913 
914         return true;
915     }
916 
917     /**
918      * Write the value of a given descriptor to the associated remote device.
919      *
920      * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is
921      * triggered to report the result of the write operation.
922      *
923      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
924      *
925      * @param descriptor Descriptor to write to the associated remote device
926      * @return true, if the write operation was initiated successfully
927      */
writeDescriptor(BluetoothGattDescriptor descriptor)928     public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
929         if (VDBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid());
930         if (mService == null || mClientIf == 0 || descriptor.getValue() == null) return false;
931 
932         BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
933         if (characteristic == null) return false;
934 
935         BluetoothGattService service = characteristic.getService();
936         if (service == null) return false;
937 
938         BluetoothDevice device = service.getDevice();
939         if (device == null) return false;
940 
941         synchronized(mDeviceBusy) {
942             if (mDeviceBusy) return false;
943             mDeviceBusy = true;
944         }
945 
946         try {
947             mService.writeDescriptor(mClientIf, device.getAddress(), descriptor.getInstanceId(),
948                 BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT, AUTHENTICATION_NONE,
949                 descriptor.getValue());
950         } catch (RemoteException e) {
951             Log.e(TAG,"",e);
952             mDeviceBusy = false;
953             return false;
954         }
955 
956         return true;
957     }
958 
959     /**
960      * Initiates a reliable write transaction for a given remote device.
961      *
962      * <p>Once a reliable write transaction has been initiated, all calls
963      * to {@link #writeCharacteristic} are sent to the remote device for
964      * verification and queued up for atomic execution. The application will
965      * receive an {@link BluetoothGattCallback#onCharacteristicWrite} callback
966      * in response to every {@link #writeCharacteristic} call and is responsible
967      * for verifying if the value has been transmitted accurately.
968      *
969      * <p>After all characteristics have been queued up and verified,
970      * {@link #executeReliableWrite} will execute all writes. If a characteristic
971      * was not written correctly, calling {@link #abortReliableWrite} will
972      * cancel the current transaction without commiting any values on the
973      * remote device.
974      *
975      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
976      *
977      * @return true, if the reliable write transaction has been initiated
978      */
beginReliableWrite()979     public boolean beginReliableWrite() {
980         if (VDBG) Log.d(TAG, "beginReliableWrite() - device: " + mDevice.getAddress());
981         if (mService == null || mClientIf == 0) return false;
982 
983         try {
984             mService.beginReliableWrite(mClientIf, mDevice.getAddress());
985         } catch (RemoteException e) {
986             Log.e(TAG,"",e);
987             return false;
988         }
989 
990         return true;
991     }
992 
993     /**
994      * Executes a reliable write transaction for a given remote device.
995      *
996      * <p>This function will commit all queued up characteristic write
997      * operations for a given remote device.
998      *
999      * <p>A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is
1000      * invoked to indicate whether the transaction has been executed correctly.
1001      *
1002      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1003      *
1004      * @return true, if the request to execute the transaction has been sent
1005      */
executeReliableWrite()1006     public boolean executeReliableWrite() {
1007         if (VDBG) Log.d(TAG, "executeReliableWrite() - device: " + mDevice.getAddress());
1008         if (mService == null || mClientIf == 0) return false;
1009 
1010         synchronized(mDeviceBusy) {
1011             if (mDeviceBusy) return false;
1012             mDeviceBusy = true;
1013         }
1014 
1015         try {
1016             mService.endReliableWrite(mClientIf, mDevice.getAddress(), true);
1017         } catch (RemoteException e) {
1018             Log.e(TAG,"",e);
1019             mDeviceBusy = false;
1020             return false;
1021         }
1022 
1023         return true;
1024     }
1025 
1026     /**
1027      * Cancels a reliable write transaction for a given device.
1028      *
1029      * <p>Calling this function will discard all queued characteristic write
1030      * operations for a given remote device.
1031      *
1032      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1033      */
abortReliableWrite()1034     public void abortReliableWrite() {
1035         if (VDBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice.getAddress());
1036         if (mService == null || mClientIf == 0) return;
1037 
1038         try {
1039             mService.endReliableWrite(mClientIf, mDevice.getAddress(), false);
1040         } catch (RemoteException e) {
1041             Log.e(TAG,"",e);
1042         }
1043     }
1044 
1045     /**
1046      * @deprecated Use {@link #abortReliableWrite()}
1047      */
abortReliableWrite(BluetoothDevice mDevice)1048     public void abortReliableWrite(BluetoothDevice mDevice) {
1049         abortReliableWrite();
1050     }
1051 
1052     /**
1053      * Enable or disable notifications/indications for a given characteristic.
1054      *
1055      * <p>Once notifications are enabled for a characteristic, a
1056      * {@link BluetoothGattCallback#onCharacteristicChanged} callback will be
1057      * triggered if the remote device indicates that the given characteristic
1058      * has changed.
1059      *
1060      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1061      *
1062      * @param characteristic The characteristic for which to enable notifications
1063      * @param enable Set to true to enable notifications/indications
1064      * @return true, if the requested notification status was set successfully
1065      */
setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enable)1066     public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
1067                                               boolean enable) {
1068         if (DBG) Log.d(TAG, "setCharacteristicNotification() - uuid: " + characteristic.getUuid()
1069                          + " enable: " + enable);
1070         if (mService == null || mClientIf == 0) return false;
1071 
1072         BluetoothGattService service = characteristic.getService();
1073         if (service == null) return false;
1074 
1075         BluetoothDevice device = service.getDevice();
1076         if (device == null) return false;
1077 
1078         try {
1079             mService.registerForNotification(mClientIf, device.getAddress(),
1080                 characteristic.getInstanceId(), enable);
1081         } catch (RemoteException e) {
1082             Log.e(TAG,"",e);
1083             return false;
1084         }
1085 
1086         return true;
1087     }
1088 
1089     /**
1090      * Clears the internal cache and forces a refresh of the services from the
1091      * remote device.
1092      * @hide
1093      */
refresh()1094     public boolean refresh() {
1095         if (DBG) Log.d(TAG, "refresh() - device: " + mDevice.getAddress());
1096         if (mService == null || mClientIf == 0) return false;
1097 
1098         try {
1099             mService.refreshDevice(mClientIf, mDevice.getAddress());
1100         } catch (RemoteException e) {
1101             Log.e(TAG,"",e);
1102             return false;
1103         }
1104 
1105         return true;
1106     }
1107 
1108     /**
1109      * Read the RSSI for a connected remote device.
1110      *
1111      * <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be
1112      * invoked when the RSSI value has been read.
1113      *
1114      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1115      *
1116      * @return true, if the RSSI value has been requested successfully
1117      */
readRemoteRssi()1118     public boolean readRemoteRssi() {
1119         if (DBG) Log.d(TAG, "readRssi() - device: " + mDevice.getAddress());
1120         if (mService == null || mClientIf == 0) return false;
1121 
1122         try {
1123             mService.readRemoteRssi(mClientIf, mDevice.getAddress());
1124         } catch (RemoteException e) {
1125             Log.e(TAG,"",e);
1126             return false;
1127         }
1128 
1129         return true;
1130     }
1131 
1132     /**
1133      * Request an MTU size used for a given connection.
1134      *
1135      * <p>When performing a write request operation (write without response),
1136      * the data sent is truncated to the MTU size. This function may be used
1137      * to request a larger MTU size to be able to send more data at once.
1138      *
1139      * <p>A {@link BluetoothGattCallback#onMtuChanged} callback will indicate
1140      * whether this operation was successful.
1141      *
1142      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1143      *
1144      * @return true, if the new MTU value has been requested successfully
1145      */
requestMtu(int mtu)1146     public boolean requestMtu(int mtu) {
1147         if (DBG) Log.d(TAG, "configureMTU() - device: " + mDevice.getAddress()
1148                             + " mtu: " + mtu);
1149         if (mService == null || mClientIf == 0) return false;
1150 
1151         try {
1152             mService.configureMTU(mClientIf, mDevice.getAddress(), mtu);
1153         } catch (RemoteException e) {
1154             Log.e(TAG,"",e);
1155             return false;
1156         }
1157 
1158         return true;
1159     }
1160 
1161     /**
1162      * Request a connection parameter update.
1163      *
1164      * <p>This function will send a connection parameter update request to the
1165      * remote device.
1166      *
1167      * @param connectionPriority Request a specific connection priority. Must be one of
1168      *          {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED},
1169      *          {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH}
1170      *          or {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}.
1171      * @throws IllegalArgumentException If the parameters are outside of their
1172      *                                  specified range.
1173      */
requestConnectionPriority(int connectionPriority)1174     public boolean requestConnectionPriority(int connectionPriority) {
1175         if (connectionPriority < CONNECTION_PRIORITY_BALANCED ||
1176             connectionPriority > CONNECTION_PRIORITY_LOW_POWER) {
1177             throw new IllegalArgumentException("connectionPriority not within valid range");
1178         }
1179 
1180         if (DBG) Log.d(TAG, "requestConnectionPriority() - params: " + connectionPriority);
1181         if (mService == null || mClientIf == 0) return false;
1182 
1183         try {
1184             mService.connectionParameterUpdate(mClientIf, mDevice.getAddress(), connectionPriority);
1185         } catch (RemoteException e) {
1186             Log.e(TAG,"",e);
1187             return false;
1188         }
1189 
1190         return true;
1191     }
1192 
1193     /**
1194      * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
1195      * with {@link BluetoothProfile#GATT} as argument
1196      *
1197      * @throws UnsupportedOperationException
1198      */
1199     @Override
getConnectionState(BluetoothDevice device)1200     public int getConnectionState(BluetoothDevice device) {
1201         throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
1202     }
1203 
1204     /**
1205      * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
1206      * with {@link BluetoothProfile#GATT} as argument
1207      *
1208      * @throws UnsupportedOperationException
1209      */
1210     @Override
getConnectedDevices()1211     public List<BluetoothDevice> getConnectedDevices() {
1212         throw new UnsupportedOperationException
1213             ("Use BluetoothManager#getConnectedDevices instead.");
1214     }
1215 
1216     /**
1217      * Not supported - please use
1218      * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])}
1219      * with {@link BluetoothProfile#GATT} as first argument
1220      *
1221      * @throws UnsupportedOperationException
1222      */
1223     @Override
getDevicesMatchingConnectionStates(int[] states)1224     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
1225         throw new UnsupportedOperationException
1226             ("Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
1227     }
1228 }
1229