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