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