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