BluetoothGatt.java revision 4b4eebb9a5185a8ea5593a797400960e5258a57e
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
17package android.bluetooth;
18
19import android.bluetooth.BluetoothDevice;
20import android.bluetooth.BluetoothProfile;
21import android.bluetooth.BluetoothProfile.ServiceListener;
22import android.bluetooth.IBluetoothManager;
23import android.bluetooth.IBluetoothStateChangeCallback;
24
25import android.content.ComponentName;
26import android.content.Context;
27import android.content.Intent;
28import android.content.ServiceConnection;
29import android.os.IBinder;
30import android.os.ParcelUuid;
31import android.os.RemoteException;
32import android.os.ServiceManager;
33import android.util.Log;
34
35import java.util.ArrayList;
36import java.util.List;
37import 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 */
50public 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 in callback", 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                             int descrInstId, 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(), descrInstId, 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 in callback", 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 in callback", 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 in callback", 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 in callback", 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                             int descrInstId, ParcelUuid descrUuid,
433                             byte[] value) {
434                if (DBG) Log.d(TAG, "onDescriptorRead() - Device=" + address + " UUID=" + charUuid);
435
436                if (!address.equals(mDevice.getAddress())) {
437                    return;
438                }
439                BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
440                                                          srvcInstId, srvcType);
441                if (service == null) return;
442
443                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
444                        charUuid.getUuid(), charInstId);
445                if (characteristic == null) return;
446
447                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
448                        descrUuid.getUuid(), descrInstId);
449                if (descriptor == null) return;
450
451                if (status == 0) descriptor.setValue(value);
452
453                if ((status == GATT_INSUFFICIENT_AUTHENTICATION
454                  || status == GATT_INSUFFICIENT_ENCRYPTION)
455                  && mAuthRetry == false) {
456                    try {
457                        mAuthRetry = true;
458                        mService.readDescriptor(mClientIf, address,
459                            srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
460                            descrInstId, descrUuid, AUTHENTICATION_MITM);
461                    } catch (RemoteException e) {
462                        Log.e(TAG,"",e);
463                    }
464                }
465
466                mAuthRetry = true;
467
468                try {
469                    mCallback.onDescriptorRead(BluetoothGatt.this, descriptor, status);
470                } catch (Exception ex) {
471                    Log.w(TAG, "Unhandled exception in callback", ex);
472                }
473            }
474
475            /**
476             * Descriptor write operation complete.
477             * @hide
478             */
479            public void onDescriptorWrite(String address, int status, int srvcType,
480                             int srvcInstId, ParcelUuid srvcUuid,
481                             int charInstId, ParcelUuid charUuid,
482                             int descrInstId, ParcelUuid descrUuid) {
483                if (DBG) Log.d(TAG, "onDescriptorWrite() - Device=" + address + " UUID=" + charUuid);
484
485                if (!address.equals(mDevice.getAddress())) {
486                    return;
487                }
488                BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
489                                                          srvcInstId, srvcType);
490                if (service == null) return;
491
492                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
493                        charUuid.getUuid(), charInstId);
494                if (characteristic == null) return;
495
496                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
497                        descrUuid.getUuid(), descrInstId);
498                if (descriptor == null) return;
499
500                if ((status == GATT_INSUFFICIENT_AUTHENTICATION
501                  || status == GATT_INSUFFICIENT_ENCRYPTION)
502                  && mAuthRetry == false) {
503                    try {
504                        mAuthRetry = true;
505                        mService.writeDescriptor(mClientIf, address,
506                            srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
507                            descrInstId, descrUuid, characteristic.getWriteType(),
508                            AUTHENTICATION_MITM, descriptor.getValue());
509                    } catch (RemoteException e) {
510                        Log.e(TAG,"",e);
511                    }
512                }
513
514                mAuthRetry = false;
515
516                try {
517                    mCallback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
518                } catch (Exception ex) {
519                    Log.w(TAG, "Unhandled exception in callback", ex);
520                }
521            }
522
523            /**
524             * Prepared write transaction completed (or aborted)
525             * @hide
526             */
527            public void onExecuteWrite(String address, int status) {
528                if (DBG) Log.d(TAG, "onExecuteWrite() - Device=" + address
529                    + " status=" + status);
530                if (!address.equals(mDevice.getAddress())) {
531                    return;
532                }
533                try {
534                    mCallback.onReliableWriteCompleted(BluetoothGatt.this, status);
535                } catch (Exception ex) {
536                    Log.w(TAG, "Unhandled exception in callback", ex);
537                }
538            }
539
540            /**
541             * Remote device RSSI has been read
542             * @hide
543             */
544            public void onReadRemoteRssi(String address, int rssi, int status) {
545                if (DBG) Log.d(TAG, "onReadRemoteRssi() - Device=" + address +
546                            " rssi=" + rssi + " status=" + status);
547                if (!address.equals(mDevice.getAddress())) {
548                    return;
549                }
550                try {
551                    mCallback.onReadRemoteRssi(BluetoothGatt.this, rssi, status);
552                } catch (Exception ex) {
553                    Log.w(TAG, "Unhandled exception in callback", ex);
554                }
555            }
556
557            /**
558             * Advertise state change callback
559             * @hide
560             */
561            public void onAdvertiseStateChange(int state, int status) {
562                if (DBG) Log.d(TAG, "onAdvertiseStateChange() - state = "
563                        + state + " status=" + status);
564            }
565        };
566
567    /*package*/ BluetoothGatt(Context context, IBluetoothGatt iGatt, BluetoothDevice device) {
568        mContext = context;
569        mService = iGatt;
570        mDevice = device;
571        mServices = new ArrayList<BluetoothGattService>();
572
573        mConnState = CONN_STATE_IDLE;
574    }
575
576    /**
577     * Close this Bluetooth GATT client.
578     *
579     * Application should call this method as early as possible after it is done with
580     * this GATT client.
581     */
582    public void close() {
583        if (DBG) Log.d(TAG, "close()");
584
585        unregisterApp();
586        mConnState = CONN_STATE_CLOSED;
587    }
588
589    /**
590     * Returns a service by UUID, instance and type.
591     * @hide
592     */
593    /*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid,
594                                                int instanceId, int type) {
595        for(BluetoothGattService svc : mServices) {
596            if (svc.getDevice().equals(device) &&
597                svc.getType() == type &&
598                svc.getInstanceId() == instanceId &&
599                svc.getUuid().equals(uuid)) {
600                return svc;
601            }
602        }
603        return null;
604    }
605
606
607    /**
608     * Register an application callback to start using GATT.
609     *
610     * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
611     * is used to notify success or failure if the function returns true.
612     *
613     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
614     *
615     * @param callback GATT callback handler that will receive asynchronous callbacks.
616     * @return If true, the callback will be called to notify success or failure,
617     *         false on immediate error
618     */
619    private boolean registerApp(BluetoothGattCallback callback) {
620        if (DBG) Log.d(TAG, "registerApp()");
621        if (mService == null) return false;
622
623        mCallback = callback;
624        UUID uuid = UUID.randomUUID();
625        if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid);
626
627        try {
628            mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback);
629        } catch (RemoteException e) {
630            Log.e(TAG,"",e);
631            return false;
632        }
633
634        return true;
635    }
636
637    /**
638     * Unregister the current application and callbacks.
639     */
640    private void unregisterApp() {
641        if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf);
642        if (mService == null || mClientIf == 0) return;
643
644        try {
645            mCallback = null;
646            mService.unregisterClient(mClientIf);
647            mClientIf = 0;
648        } catch (RemoteException e) {
649            Log.e(TAG,"",e);
650        }
651    }
652
653    /**
654     * Initiate a connection to a Bluetooth GATT capable device.
655     *
656     * <p>The connection may not be established right away, but will be
657     * completed when the remote device is available. A
658     * {@link BluetoothGattCallback#onConnectionStateChange} callback will be
659     * invoked when the connection state changes as a result of this function.
660     *
661     * <p>The autoConnect paramter determines whether to actively connect to
662     * the remote device, or rather passively scan and finalize the connection
663     * when the remote device is in range/available. Generally, the first ever
664     * connection to a device should be direct (autoConnect set to false) and
665     * subsequent connections to known devices should be invoked with the
666     * autoConnect parameter set to true.
667     *
668     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
669     *
670     * @param device Remote device to connect to
671     * @param autoConnect Whether to directly connect to the remote device (false)
672     *                    or to automatically connect as soon as the remote
673     *                    device becomes available (true).
674     * @return true, if the connection attempt was initiated successfully
675     */
676    /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback) {
677        if (DBG) Log.d(TAG, "connect() - device: " + mDevice.getAddress() + ", auto: " + autoConnect);
678        synchronized(mStateLock) {
679            if (mConnState != CONN_STATE_IDLE) {
680                throw new IllegalStateException("Not idle");
681            }
682            mConnState = CONN_STATE_CONNECTING;
683        }
684        if (!registerApp(callback)) {
685            synchronized(mStateLock) {
686                mConnState = CONN_STATE_IDLE;
687            }
688            Log.e(TAG, "Failed to register callback");
689            return false;
690        }
691
692        // the connection will continue after successful callback registration
693        mAutoConnect = autoConnect;
694        return true;
695    }
696
697    /**
698     * Disconnects an established connection, or cancels a connection attempt
699     * currently in progress.
700     *
701     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
702     */
703    public void disconnect() {
704        if (DBG) Log.d(TAG, "cancelOpen() - device: " + mDevice.getAddress());
705        if (mService == null || mClientIf == 0) return;
706
707        try {
708            mService.clientDisconnect(mClientIf, mDevice.getAddress());
709        } catch (RemoteException e) {
710            Log.e(TAG,"",e);
711        }
712    }
713
714    /**
715     * Connect back to remote device.
716     *
717     * <p>This method is used to re-connect to a remote device after the
718     * connection has been dropped. If the device is not in range, the
719     * re-connection will be triggered once the device is back in range.
720     *
721     * @return true, if the connection attempt was initiated successfully
722     */
723    public boolean connect() {
724        try {
725            mService.clientConnect(mClientIf, mDevice.getAddress(),
726                                   false); // autoConnect is inverse of "isDirect"
727            return true;
728        } catch (RemoteException e) {
729            Log.e(TAG,"",e);
730            return false;
731        }
732    }
733
734    /**
735     * Return the remote bluetooth device this GATT client targets to
736     *
737     * @return remote bluetooth device
738     */
739    public BluetoothDevice getDevice() {
740        return mDevice;
741    }
742
743    /**
744     * Discovers services offered by a remote device as well as their
745     * characteristics and descriptors.
746     *
747     * <p>This is an asynchronous operation. Once service discovery is completed,
748     * the {@link BluetoothGattCallback#onServicesDiscovered} callback is
749     * triggered. If the discovery was successful, the remote services can be
750     * retrieved using the {@link #getServices} function.
751     *
752     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
753     *
754     * @return true, if the remote service discovery has been started
755     */
756    public boolean discoverServices() {
757        if (DBG) Log.d(TAG, "discoverServices() - device: " + mDevice.getAddress());
758        if (mService == null || mClientIf == 0) return false;
759
760        mServices.clear();
761
762        try {
763            mService.discoverServices(mClientIf, mDevice.getAddress());
764        } catch (RemoteException e) {
765            Log.e(TAG,"",e);
766            return false;
767        }
768
769        return true;
770    }
771
772    /**
773     * Returns a list of GATT services offered by the remote device.
774     *
775     * <p>This function requires that service discovery has been completed
776     * for the given device.
777     *
778     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
779     *
780     * @return List of services on the remote device. Returns an empty list
781     *         if service discovery has not yet been performed.
782     */
783    public List<BluetoothGattService> getServices() {
784        List<BluetoothGattService> result =
785                new ArrayList<BluetoothGattService>();
786
787        for (BluetoothGattService service : mServices) {
788            if (service.getDevice().equals(mDevice)) {
789                result.add(service);
790            }
791        }
792
793        return result;
794    }
795
796    /**
797     * Returns a {@link BluetoothGattService}, if the requested UUID is
798     * supported by the remote device.
799     *
800     * <p>This function requires that service discovery has been completed
801     * for the given device.
802     *
803     * <p>If multiple instances of the same service (as identified by UUID)
804     * exist, the first instance of the service is returned.
805     *
806     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
807     *
808     * @param uuid UUID of the requested service
809     * @return BluetoothGattService if supported, or null if the requested
810     *         service is not offered by the remote device.
811     */
812    public BluetoothGattService getService(UUID uuid) {
813        for (BluetoothGattService service : mServices) {
814            if (service.getDevice().equals(mDevice) &&
815                service.getUuid().equals(uuid)) {
816                return service;
817            }
818        }
819
820        return null;
821    }
822
823    /**
824     * Reads the requested characteristic from the associated remote device.
825     *
826     * <p>This is an asynchronous operation. The result of the read operation
827     * is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
828     * callback.
829     *
830     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
831     *
832     * @param characteristic Characteristic to read from the remote device
833     * @return true, if the read operation was initiated successfully
834     */
835    public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
836        if ((characteristic.getProperties() &
837                BluetoothGattCharacteristic.PROPERTY_READ) == 0) return false;
838
839        if (DBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid());
840        if (mService == null || mClientIf == 0) return false;
841
842        BluetoothGattService service = characteristic.getService();
843        if (service == null) return false;
844
845        BluetoothDevice device = service.getDevice();
846        if (device == null) return false;
847
848        try {
849            mService.readCharacteristic(mClientIf, device.getAddress(),
850                service.getType(), service.getInstanceId(),
851                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
852                new ParcelUuid(characteristic.getUuid()), AUTHENTICATION_NONE);
853        } catch (RemoteException e) {
854            Log.e(TAG,"",e);
855            return false;
856        }
857
858        return true;
859    }
860
861    /**
862     * Writes a given characteristic and its values to the associated remote device.
863     *
864     * <p>Once the write operation has been completed, the
865     * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked,
866     * reporting the result of the operation.
867     *
868     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
869     *
870     * @param characteristic Characteristic to write on the remote device
871     * @return true, if the write operation was initiated successfully
872     */
873    public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
874        if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0
875            && (characteristic.getProperties() &
876                BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) return false;
877
878        if (DBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid());
879        if (mService == null || mClientIf == 0) return false;
880
881        BluetoothGattService service = characteristic.getService();
882        if (service == null) return false;
883
884        BluetoothDevice device = service.getDevice();
885        if (device == null) return false;
886
887        try {
888            mService.writeCharacteristic(mClientIf, device.getAddress(),
889                service.getType(), service.getInstanceId(),
890                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
891                new ParcelUuid(characteristic.getUuid()),
892                characteristic.getWriteType(), AUTHENTICATION_NONE,
893                characteristic.getValue());
894        } catch (RemoteException e) {
895            Log.e(TAG,"",e);
896            return false;
897        }
898
899        return true;
900    }
901
902    /**
903     * Reads the value for a given descriptor from the associated remote device.
904     *
905     * <p>Once the read operation has been completed, the
906     * {@link BluetoothGattCallback#onDescriptorRead} callback is
907     * triggered, signaling the result of the operation.
908     *
909     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
910     *
911     * @param descriptor Descriptor value to read from the remote device
912     * @return true, if the read operation was initiated successfully
913     */
914    public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
915        if (DBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid());
916        if (mService == null || mClientIf == 0) return false;
917
918        BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
919        if (characteristic == null) return false;
920
921        BluetoothGattService service = characteristic.getService();
922        if (service == null) return false;
923
924        BluetoothDevice device = service.getDevice();
925        if (device == null) return false;
926
927        try {
928            mService.readDescriptor(mClientIf, device.getAddress(), service.getType(),
929                service.getInstanceId(), new ParcelUuid(service.getUuid()),
930                characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()),
931                descriptor.getInstanceId(), new ParcelUuid(descriptor.getUuid()),
932                AUTHENTICATION_NONE);
933        } catch (RemoteException e) {
934            Log.e(TAG,"",e);
935            return false;
936        }
937
938        return true;
939    }
940
941    /**
942     * Write the value of a given descriptor to the associated remote device.
943     *
944     * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is
945     * triggered to report the result of the write operation.
946     *
947     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
948     *
949     * @param descriptor Descriptor to write to the associated remote device
950     * @return true, if the write operation was initiated successfully
951     */
952    public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
953        if (DBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid());
954        if (mService == null || mClientIf == 0) return false;
955
956        BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
957        if (characteristic == null) return false;
958
959        BluetoothGattService service = characteristic.getService();
960        if (service == null) return false;
961
962        BluetoothDevice device = service.getDevice();
963        if (device == null) return false;
964
965        try {
966            mService.writeDescriptor(mClientIf, device.getAddress(), service.getType(),
967                service.getInstanceId(), new ParcelUuid(service.getUuid()),
968                characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()),
969                descriptor.getInstanceId(), new ParcelUuid(descriptor.getUuid()),
970                characteristic.getWriteType(), AUTHENTICATION_NONE,
971                descriptor.getValue());
972        } catch (RemoteException e) {
973            Log.e(TAG,"",e);
974            return false;
975        }
976
977        return true;
978    }
979
980    /**
981     * Initiates a reliable write transaction for a given remote device.
982     *
983     * <p>Once a reliable write transaction has been initiated, all calls
984     * to {@link #writeCharacteristic} are sent to the remote device for
985     * verification and queued up for atomic execution. The application will
986     * receive an {@link BluetoothGattCallback#onCharacteristicWrite} callback
987     * in response to every {@link #writeCharacteristic} call and is responsible
988     * for verifying if the value has been transmitted accurately.
989     *
990     * <p>After all characteristics have been queued up and verified,
991     * {@link #executeReliableWrite} will execute all writes. If a characteristic
992     * was not written correctly, calling {@link #abortReliableWrite} will
993     * cancel the current transaction without commiting any values on the
994     * remote device.
995     *
996     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
997     *
998     * @return true, if the reliable write transaction has been initiated
999     */
1000    public boolean beginReliableWrite() {
1001        if (DBG) Log.d(TAG, "beginReliableWrite() - device: " + mDevice.getAddress());
1002        if (mService == null || mClientIf == 0) return false;
1003
1004        try {
1005            mService.beginReliableWrite(mClientIf, mDevice.getAddress());
1006        } catch (RemoteException e) {
1007            Log.e(TAG,"",e);
1008            return false;
1009        }
1010
1011        return true;
1012    }
1013
1014    /**
1015     * Executes a reliable write transaction for a given remote device.
1016     *
1017     * <p>This function will commit all queued up characteristic write
1018     * operations for a given remote device.
1019     *
1020     * <p>A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is
1021     * invoked to indicate whether the transaction has been executed correctly.
1022     *
1023     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1024     *
1025     * @return true, if the request to execute the transaction has been sent
1026     */
1027    public boolean executeReliableWrite() {
1028        if (DBG) Log.d(TAG, "executeReliableWrite() - device: " + mDevice.getAddress());
1029        if (mService == null || mClientIf == 0) return false;
1030
1031        try {
1032            mService.endReliableWrite(mClientIf, mDevice.getAddress(), true);
1033        } catch (RemoteException e) {
1034            Log.e(TAG,"",e);
1035            return false;
1036        }
1037
1038        return true;
1039    }
1040
1041    /**
1042     * Cancels a reliable write transaction for a given device.
1043     *
1044     * <p>Calling this function will discard all queued characteristic write
1045     * operations for a given remote device.
1046     *
1047     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1048     */
1049    public void abortReliableWrite() {
1050        if (DBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice.getAddress());
1051        if (mService == null || mClientIf == 0) return;
1052
1053        try {
1054            mService.endReliableWrite(mClientIf, mDevice.getAddress(), false);
1055        } catch (RemoteException e) {
1056            Log.e(TAG,"",e);
1057        }
1058    }
1059
1060    /**
1061     * @deprecated Use {@link #abortReliableWrite()}
1062     */
1063    public void abortReliableWrite(BluetoothDevice mDevice) {
1064        abortReliableWrite();
1065    }
1066
1067    /**
1068     * Enable or disable notifications/indications for a given characteristic.
1069     *
1070     * <p>Once notifications are enabled for a characteristic, a
1071     * {@link BluetoothGattCallback#onCharacteristicChanged} callback will be
1072     * triggered if the remote device indicates that the given characteristic
1073     * has changed.
1074     *
1075     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1076     *
1077     * @param characteristic The characteristic for which to enable notifications
1078     * @param enable Set to true to enable notifications/indications
1079     * @return true, if the requested notification status was set successfully
1080     */
1081    public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
1082                                              boolean enable) {
1083        if (DBG) Log.d(TAG, "setCharacteristicNotification() - uuid: " + characteristic.getUuid()
1084                         + " enable: " + enable);
1085        if (mService == null || mClientIf == 0) return false;
1086
1087        BluetoothGattService service = characteristic.getService();
1088        if (service == null) return false;
1089
1090        BluetoothDevice device = service.getDevice();
1091        if (device == null) return false;
1092
1093        try {
1094            mService.registerForNotification(mClientIf, device.getAddress(),
1095                service.getType(), service.getInstanceId(),
1096                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
1097                new ParcelUuid(characteristic.getUuid()),
1098                enable);
1099        } catch (RemoteException e) {
1100            Log.e(TAG,"",e);
1101            return false;
1102        }
1103
1104        return true;
1105    }
1106
1107    /**
1108     * Clears the internal cache and forces a refresh of the services from the
1109     * remote device.
1110     * @hide
1111     */
1112    public boolean refresh() {
1113        if (DBG) Log.d(TAG, "refresh() - device: " + mDevice.getAddress());
1114        if (mService == null || mClientIf == 0) return false;
1115
1116        try {
1117            mService.refreshDevice(mClientIf, mDevice.getAddress());
1118        } catch (RemoteException e) {
1119            Log.e(TAG,"",e);
1120            return false;
1121        }
1122
1123        return true;
1124    }
1125
1126    /**
1127     * Read the RSSI for a connected remote device.
1128     *
1129     * <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be
1130     * invoked when the RSSI value has been read.
1131     *
1132     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1133     *
1134     * @return true, if the RSSI value has been requested successfully
1135     */
1136    public boolean readRemoteRssi() {
1137        if (DBG) Log.d(TAG, "readRssi() - device: " + mDevice.getAddress());
1138        if (mService == null || mClientIf == 0) return false;
1139
1140        try {
1141            mService.readRemoteRssi(mClientIf, mDevice.getAddress());
1142        } catch (RemoteException e) {
1143            Log.e(TAG,"",e);
1144            return false;
1145        }
1146
1147        return true;
1148    }
1149
1150    /**
1151     * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
1152     * with {@link BluetoothProfile#GATT} as argument
1153     *
1154     * @throws UnsupportedOperationException
1155     */
1156    @Override
1157    public int getConnectionState(BluetoothDevice device) {
1158        throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
1159    }
1160
1161    /**
1162     * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
1163     * with {@link BluetoothProfile#GATT} as argument
1164     *
1165     * @throws UnsupportedOperationException
1166     */
1167    @Override
1168    public List<BluetoothDevice> getConnectedDevices() {
1169        throw new UnsupportedOperationException
1170            ("Use BluetoothManager#getConnectedDevices instead.");
1171    }
1172
1173    /**
1174     * Not supported - please use
1175     * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])}
1176     * with {@link BluetoothProfile#GATT} as first argument
1177     *
1178     * @throws UnsupportedOperationException
1179     */
1180    @Override
1181    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
1182        throw new UnsupportedOperationException
1183            ("Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
1184    }
1185}
1186