BluetoothGatt.java revision f0b56a5a7f9cff4b4ed120188be65a7bc08e2256
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             * A new GATT service has been discovered.
201             * The service is added to the internal list and the search
202             * continues.
203             * @hide
204             */
205            public void onGetService(String address, int srvcType,
206                                     int srvcInstId, ParcelUuid srvcUuid) {
207                if (VDBG) Log.d(TAG, "onGetService() - Device=" + address + " UUID=" + srvcUuid);
208                if (!address.equals(mDevice.getAddress())) {
209                    return;
210                }
211                mServices.add(new BluetoothGattService(mDevice, srvcUuid.getUuid(),
212                                                       srvcInstId, srvcType));
213            }
214
215            /**
216             * An included service has been found durig GATT discovery.
217             * The included service is added to the respective parent.
218             * @hide
219             */
220            public void onGetIncludedService(String address, int srvcType,
221                                             int srvcInstId, ParcelUuid srvcUuid,
222                                             int inclSrvcType, int inclSrvcInstId,
223                                             ParcelUuid inclSrvcUuid) {
224                if (VDBG) Log.d(TAG, "onGetIncludedService() - Device=" + address
225                    + " UUID=" + srvcUuid + " Included=" + inclSrvcUuid);
226
227                if (!address.equals(mDevice.getAddress())) {
228                    return;
229                }
230                BluetoothGattService service = getService(mDevice,
231                        srvcUuid.getUuid(), srvcInstId, srvcType);
232                BluetoothGattService includedService = getService(mDevice,
233                        inclSrvcUuid.getUuid(), inclSrvcInstId, inclSrvcType);
234
235                if (service != null && includedService != null) {
236                    service.addIncludedService(includedService);
237                }
238            }
239
240            /**
241             * A new GATT characteristic has been discovered.
242             * Add the new characteristic to the relevant service and continue
243             * the remote device inspection.
244             * @hide
245             */
246            public void onGetCharacteristic(String address, int srvcType,
247                             int srvcInstId, ParcelUuid srvcUuid,
248                             int charInstId, ParcelUuid charUuid,
249                             int charProps) {
250                if (VDBG) Log.d(TAG, "onGetCharacteristic() - Device=" + address + " UUID=" +
251                               charUuid);
252
253                if (!address.equals(mDevice.getAddress())) {
254                    return;
255                }
256                BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
257                                                          srvcInstId, srvcType);
258                if (service != null) {
259                    service.addCharacteristic(new BluetoothGattCharacteristic(
260                           service, charUuid.getUuid(), charInstId, charProps, 0));
261                }
262            }
263
264            /**
265             * A new GATT descriptor has been discovered.
266             * Finally, add the descriptor to the related characteristic.
267             * This should conclude the remote device update.
268             * @hide
269             */
270            public void onGetDescriptor(String address, int srvcType,
271                             int srvcInstId, ParcelUuid srvcUuid,
272                             int charInstId, ParcelUuid charUuid,
273                             int descrInstId, ParcelUuid descUuid) {
274                if (VDBG) Log.d(TAG, "onGetDescriptor() - Device=" + address + " UUID=" + descUuid);
275
276                if (!address.equals(mDevice.getAddress())) {
277                    return;
278                }
279                BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
280                                                          srvcInstId, srvcType);
281                if (service == null) return;
282
283                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
284                    charUuid.getUuid(), charInstId);
285                if (characteristic == null) return;
286
287                characteristic.addDescriptor(new BluetoothGattDescriptor(
288                    characteristic, descUuid.getUuid(), descrInstId, 0));
289            }
290
291            /**
292             * Remote search has been completed.
293             * The internal object structure should now reflect the state
294             * of the remote device database. Let the application know that
295             * we are done at this point.
296             * @hide
297             */
298            public void onSearchComplete(String address, int status) {
299                if (DBG) Log.d(TAG, "onSearchComplete() = Device=" + address + " Status=" + status);
300                if (!address.equals(mDevice.getAddress())) {
301                    return;
302                }
303                try {
304                    mCallback.onServicesDiscovered(BluetoothGatt.this, status);
305                } catch (Exception ex) {
306                    Log.w(TAG, "Unhandled exception in callback", ex);
307                }
308            }
309
310            /**
311             * Remote characteristic has been read.
312             * Updates the internal value.
313             * @hide
314             */
315            public void onCharacteristicRead(String address, int status, int srvcType,
316                             int srvcInstId, ParcelUuid srvcUuid,
317                             int charInstId, ParcelUuid charUuid, byte[] value) {
318                if (VDBG) Log.d(TAG, "onCharacteristicRead() - Device=" + address
319                            + " UUID=" + charUuid + " Status=" + status);
320
321                if (!address.equals(mDevice.getAddress())) {
322                    return;
323                }
324
325                synchronized(mDeviceBusy) {
326                    mDeviceBusy = false;
327                }
328
329                if ((status == GATT_INSUFFICIENT_AUTHENTICATION
330                  || status == GATT_INSUFFICIENT_ENCRYPTION)
331                  && mAuthRetry == false) {
332                    try {
333                        mAuthRetry = true;
334                        mService.readCharacteristic(mClientIf, address,
335                            srvcType, srvcInstId, srvcUuid,
336                            charInstId, charUuid, AUTHENTICATION_MITM);
337                        return;
338                    } catch (RemoteException e) {
339                        Log.e(TAG,"",e);
340                    }
341                }
342
343                mAuthRetry = false;
344
345                BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
346                                                          srvcInstId, srvcType);
347                if (service == null) return;
348
349                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
350                        charUuid.getUuid(), charInstId);
351                if (characteristic == null) return;
352
353                if (status == 0) characteristic.setValue(value);
354
355                try {
356                    mCallback.onCharacteristicRead(BluetoothGatt.this, characteristic, status);
357                } catch (Exception ex) {
358                    Log.w(TAG, "Unhandled exception in callback", ex);
359                }
360            }
361
362            /**
363             * Characteristic has been written to the remote device.
364             * Let the app know how we did...
365             * @hide
366             */
367            public void onCharacteristicWrite(String address, int status, int srvcType,
368                             int srvcInstId, ParcelUuid srvcUuid,
369                             int charInstId, ParcelUuid charUuid) {
370                if (VDBG) Log.d(TAG, "onCharacteristicWrite() - Device=" + address
371                            + " UUID=" + charUuid + " Status=" + status);
372
373                if (!address.equals(mDevice.getAddress())) {
374                    return;
375                }
376
377                synchronized(mDeviceBusy) {
378                    mDeviceBusy = false;
379                }
380
381                BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
382                                                          srvcInstId, srvcType);
383                if (service == null) return;
384
385                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
386                        charUuid.getUuid(), charInstId);
387                if (characteristic == null) return;
388
389                if ((status == GATT_INSUFFICIENT_AUTHENTICATION
390                  || status == GATT_INSUFFICIENT_ENCRYPTION)
391                  && mAuthRetry == false) {
392                    try {
393                        mAuthRetry = true;
394                        mService.writeCharacteristic(mClientIf, address,
395                            srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
396                            characteristic.getWriteType(), AUTHENTICATION_MITM,
397                            characteristic.getValue());
398                        return;
399                    } catch (RemoteException e) {
400                        Log.e(TAG,"",e);
401                    }
402                }
403
404                mAuthRetry = false;
405
406                try {
407                    mCallback.onCharacteristicWrite(BluetoothGatt.this, characteristic, status);
408                } catch (Exception ex) {
409                    Log.w(TAG, "Unhandled exception in callback", ex);
410                }
411            }
412
413            /**
414             * Remote characteristic has been updated.
415             * Updates the internal value.
416             * @hide
417             */
418            public void onNotify(String address, int srvcType,
419                             int srvcInstId, ParcelUuid srvcUuid,
420                             int charInstId, ParcelUuid charUuid,
421                             byte[] value) {
422                if (VDBG) Log.d(TAG, "onNotify() - Device=" + address + " UUID=" + charUuid);
423
424                if (!address.equals(mDevice.getAddress())) {
425                    return;
426                }
427                BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
428                                                          srvcInstId, srvcType);
429                if (service == null) return;
430
431                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
432                        charUuid.getUuid(), charInstId);
433                if (characteristic == null) return;
434
435                characteristic.setValue(value);
436
437                try {
438                    mCallback.onCharacteristicChanged(BluetoothGatt.this, characteristic);
439                } catch (Exception ex) {
440                    Log.w(TAG, "Unhandled exception in callback", ex);
441                }
442            }
443
444            /**
445             * Descriptor has been read.
446             * @hide
447             */
448            public void onDescriptorRead(String address, int status, int srvcType,
449                             int srvcInstId, ParcelUuid srvcUuid,
450                             int charInstId, ParcelUuid charUuid,
451                             int descrInstId, ParcelUuid descrUuid,
452                             byte[] value) {
453                if (VDBG) Log.d(TAG, "onDescriptorRead() - Device=" + address + " UUID=" + charUuid);
454
455                if (!address.equals(mDevice.getAddress())) {
456                    return;
457                }
458
459                synchronized(mDeviceBusy) {
460                    mDeviceBusy = false;
461                }
462
463                BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
464                                                          srvcInstId, srvcType);
465                if (service == null) return;
466
467                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
468                        charUuid.getUuid(), charInstId);
469                if (characteristic == null) return;
470
471                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
472                        descrUuid.getUuid(), descrInstId);
473                if (descriptor == null) return;
474
475                if (status == 0) descriptor.setValue(value);
476
477                if ((status == GATT_INSUFFICIENT_AUTHENTICATION
478                  || status == GATT_INSUFFICIENT_ENCRYPTION)
479                  && mAuthRetry == false) {
480                    try {
481                        mAuthRetry = true;
482                        mService.readDescriptor(mClientIf, address,
483                            srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
484                            descrInstId, descrUuid, AUTHENTICATION_MITM);
485                        return;
486                    } catch (RemoteException e) {
487                        Log.e(TAG,"",e);
488                    }
489                }
490
491                mAuthRetry = true;
492
493                try {
494                    mCallback.onDescriptorRead(BluetoothGatt.this, descriptor, status);
495                } catch (Exception ex) {
496                    Log.w(TAG, "Unhandled exception in callback", ex);
497                }
498            }
499
500            /**
501             * Descriptor write operation complete.
502             * @hide
503             */
504            public void onDescriptorWrite(String address, int status, int srvcType,
505                             int srvcInstId, ParcelUuid srvcUuid,
506                             int charInstId, ParcelUuid charUuid,
507                             int descrInstId, ParcelUuid descrUuid) {
508                if (VDBG) Log.d(TAG, "onDescriptorWrite() - Device=" + address + " UUID=" + charUuid);
509
510                if (!address.equals(mDevice.getAddress())) {
511                    return;
512                }
513
514                synchronized(mDeviceBusy) {
515                    mDeviceBusy = false;
516                }
517
518                BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
519                                                          srvcInstId, srvcType);
520                if (service == null) return;
521
522                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
523                        charUuid.getUuid(), charInstId);
524                if (characteristic == null) return;
525
526                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
527                        descrUuid.getUuid(), descrInstId);
528                if (descriptor == null) return;
529
530                if ((status == GATT_INSUFFICIENT_AUTHENTICATION
531                  || status == GATT_INSUFFICIENT_ENCRYPTION)
532                  && mAuthRetry == false) {
533                    try {
534                        mAuthRetry = true;
535                        mService.writeDescriptor(mClientIf, address,
536                            srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
537                            descrInstId, descrUuid, characteristic.getWriteType(),
538                            AUTHENTICATION_MITM, descriptor.getValue());
539                        return;
540                    } catch (RemoteException e) {
541                        Log.e(TAG,"",e);
542                    }
543                }
544
545                mAuthRetry = false;
546
547                try {
548                    mCallback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
549                } catch (Exception ex) {
550                    Log.w(TAG, "Unhandled exception in callback", ex);
551                }
552            }
553
554            /**
555             * Prepared write transaction completed (or aborted)
556             * @hide
557             */
558            public void onExecuteWrite(String address, int status) {
559                if (VDBG) Log.d(TAG, "onExecuteWrite() - Device=" + address
560                    + " status=" + status);
561                if (!address.equals(mDevice.getAddress())) {
562                    return;
563                }
564
565                synchronized(mDeviceBusy) {
566                    mDeviceBusy = false;
567                }
568
569                try {
570                    mCallback.onReliableWriteCompleted(BluetoothGatt.this, status);
571                } catch (Exception ex) {
572                    Log.w(TAG, "Unhandled exception in callback", ex);
573                }
574            }
575
576            /**
577             * Remote device RSSI has been read
578             * @hide
579             */
580            public void onReadRemoteRssi(String address, int rssi, int status) {
581                if (VDBG) Log.d(TAG, "onReadRemoteRssi() - Device=" + address +
582                            " rssi=" + rssi + " status=" + status);
583                if (!address.equals(mDevice.getAddress())) {
584                    return;
585                }
586                try {
587                    mCallback.onReadRemoteRssi(BluetoothGatt.this, rssi, status);
588                } catch (Exception ex) {
589                    Log.w(TAG, "Unhandled exception in callback", ex);
590                }
591            }
592
593            /**
594             * Callback invoked when the MTU for a given connection changes
595             * @hide
596             */
597            public void onConfigureMTU(String address, int mtu, int status) {
598                if (DBG) Log.d(TAG, "onConfigureMTU() - Device=" + address +
599                            " mtu=" + mtu + " status=" + status);
600                if (!address.equals(mDevice.getAddress())) {
601                    return;
602                }
603                try {
604                    mCallback.onMtuChanged(BluetoothGatt.this, mtu, status);
605                } catch (Exception ex) {
606                    Log.w(TAG, "Unhandled exception in callback", ex);
607                }
608            }
609        };
610
611    /*package*/ BluetoothGatt(Context context, IBluetoothGatt iGatt, BluetoothDevice device,
612                                int transport) {
613        mContext = context;
614        mService = iGatt;
615        mDevice = device;
616        mTransport = transport;
617        mServices = new ArrayList<BluetoothGattService>();
618
619        mConnState = CONN_STATE_IDLE;
620    }
621
622    /**
623     * Close this Bluetooth GATT client.
624     *
625     * Application should call this method as early as possible after it is done with
626     * this GATT client.
627     */
628    public void close() {
629        if (DBG) Log.d(TAG, "close()");
630
631        unregisterApp();
632        mConnState = CONN_STATE_CLOSED;
633    }
634
635    /**
636     * Returns a service by UUID, instance and type.
637     * @hide
638     */
639    /*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid,
640                                                int instanceId, int type) {
641        for(BluetoothGattService svc : mServices) {
642            if (svc.getDevice().equals(device) &&
643                svc.getType() == type &&
644                svc.getInstanceId() == instanceId &&
645                svc.getUuid().equals(uuid)) {
646                return svc;
647            }
648        }
649        return null;
650    }
651
652
653    /**
654     * Register an application callback to start using GATT.
655     *
656     * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
657     * is used to notify success or failure if the function returns true.
658     *
659     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
660     *
661     * @param callback GATT callback handler that will receive asynchronous callbacks.
662     * @return If true, the callback will be called to notify success or failure,
663     *         false on immediate error
664     */
665    private boolean registerApp(BluetoothGattCallback callback) {
666        if (DBG) Log.d(TAG, "registerApp()");
667        if (mService == null) return false;
668
669        mCallback = callback;
670        UUID uuid = UUID.randomUUID();
671        if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid);
672
673        try {
674            mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback);
675        } catch (RemoteException e) {
676            Log.e(TAG,"",e);
677            return false;
678        }
679
680        return true;
681    }
682
683    /**
684     * Unregister the current application and callbacks.
685     */
686    private void unregisterApp() {
687        if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf);
688        if (mService == null || mClientIf == 0) return;
689
690        try {
691            mCallback = null;
692            mService.unregisterClient(mClientIf);
693            mClientIf = 0;
694        } catch (RemoteException e) {
695            Log.e(TAG,"",e);
696        }
697    }
698
699    /**
700     * Initiate a connection to a Bluetooth GATT capable device.
701     *
702     * <p>The connection may not be established right away, but will be
703     * completed when the remote device is available. A
704     * {@link BluetoothGattCallback#onConnectionStateChange} callback will be
705     * invoked when the connection state changes as a result of this function.
706     *
707     * <p>The autoConnect parameter determines whether to actively connect to
708     * the remote device, or rather passively scan and finalize the connection
709     * when the remote device is in range/available. Generally, the first ever
710     * connection to a device should be direct (autoConnect set to false) and
711     * subsequent connections to known devices should be invoked with the
712     * autoConnect parameter set to true.
713     *
714     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
715     *
716     * @param device Remote device to connect to
717     * @param autoConnect Whether to directly connect to the remote device (false)
718     *                    or to automatically connect as soon as the remote
719     *                    device becomes available (true).
720     * @return true, if the connection attempt was initiated successfully
721     */
722    /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback) {
723        if (DBG) Log.d(TAG, "connect() - device: " + mDevice.getAddress() + ", auto: " + autoConnect);
724        synchronized(mStateLock) {
725            if (mConnState != CONN_STATE_IDLE) {
726                throw new IllegalStateException("Not idle");
727            }
728            mConnState = CONN_STATE_CONNECTING;
729        }
730        if (!registerApp(callback)) {
731            synchronized(mStateLock) {
732                mConnState = CONN_STATE_IDLE;
733            }
734            Log.e(TAG, "Failed to register callback");
735            return false;
736        }
737
738        // the connection will continue after successful callback registration
739        mAutoConnect = autoConnect;
740        return true;
741    }
742
743    /**
744     * Disconnects an established connection, or cancels a connection attempt
745     * currently in progress.
746     *
747     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
748     */
749    public void disconnect() {
750        if (DBG) Log.d(TAG, "cancelOpen() - device: " + mDevice.getAddress());
751        if (mService == null || mClientIf == 0) return;
752
753        try {
754            mService.clientDisconnect(mClientIf, mDevice.getAddress());
755        } catch (RemoteException e) {
756            Log.e(TAG,"",e);
757        }
758    }
759
760    /**
761     * Connect back to remote device.
762     *
763     * <p>This method is used to re-connect to a remote device after the
764     * connection has been dropped. If the device is not in range, the
765     * re-connection will be triggered once the device is back in range.
766     *
767     * @return true, if the connection attempt was initiated successfully
768     */
769    public boolean connect() {
770        try {
771            mService.clientConnect(mClientIf, mDevice.getAddress(),
772                                   false, mTransport); // autoConnect is inverse of "isDirect"
773            return true;
774        } catch (RemoteException e) {
775            Log.e(TAG,"",e);
776            return false;
777        }
778    }
779
780    /**
781     * Return the remote bluetooth device this GATT client targets to
782     *
783     * @return remote bluetooth device
784     */
785    public BluetoothDevice getDevice() {
786        return mDevice;
787    }
788
789    /**
790     * Discovers services offered by a remote device as well as their
791     * characteristics and descriptors.
792     *
793     * <p>This is an asynchronous operation. Once service discovery is completed,
794     * the {@link BluetoothGattCallback#onServicesDiscovered} callback is
795     * triggered. If the discovery was successful, the remote services can be
796     * retrieved using the {@link #getServices} function.
797     *
798     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
799     *
800     * @return true, if the remote service discovery has been started
801     */
802    public boolean discoverServices() {
803        if (DBG) Log.d(TAG, "discoverServices() - device: " + mDevice.getAddress());
804        if (mService == null || mClientIf == 0) return false;
805
806        mServices.clear();
807
808        try {
809            mService.discoverServices(mClientIf, mDevice.getAddress());
810        } catch (RemoteException e) {
811            Log.e(TAG,"",e);
812            return false;
813        }
814
815        return true;
816    }
817
818    /**
819     * Returns a list of GATT services offered by the remote device.
820     *
821     * <p>This function requires that service discovery has been completed
822     * for the given device.
823     *
824     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
825     *
826     * @return List of services on the remote device. Returns an empty list
827     *         if service discovery has not yet been performed.
828     */
829    public List<BluetoothGattService> getServices() {
830        List<BluetoothGattService> result =
831                new ArrayList<BluetoothGattService>();
832
833        for (BluetoothGattService service : mServices) {
834            if (service.getDevice().equals(mDevice)) {
835                result.add(service);
836            }
837        }
838
839        return result;
840    }
841
842    /**
843     * Returns a {@link BluetoothGattService}, if the requested UUID is
844     * supported by the remote device.
845     *
846     * <p>This function requires that service discovery has been completed
847     * for the given device.
848     *
849     * <p>If multiple instances of the same service (as identified by UUID)
850     * exist, the first instance of the service is returned.
851     *
852     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
853     *
854     * @param uuid UUID of the requested service
855     * @return BluetoothGattService if supported, or null if the requested
856     *         service is not offered by the remote device.
857     */
858    public BluetoothGattService getService(UUID uuid) {
859        for (BluetoothGattService service : mServices) {
860            if (service.getDevice().equals(mDevice) &&
861                service.getUuid().equals(uuid)) {
862                return service;
863            }
864        }
865
866        return null;
867    }
868
869    /**
870     * Reads the requested characteristic from the associated remote device.
871     *
872     * <p>This is an asynchronous operation. The result of the read operation
873     * is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
874     * callback.
875     *
876     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
877     *
878     * @param characteristic Characteristic to read from the remote device
879     * @return true, if the read operation was initiated successfully
880     */
881    public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
882        if ((characteristic.getProperties() &
883                BluetoothGattCharacteristic.PROPERTY_READ) == 0) return false;
884
885        if (VDBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid());
886        if (mService == null || mClientIf == 0) return false;
887
888        BluetoothGattService service = characteristic.getService();
889        if (service == null) return false;
890
891        BluetoothDevice device = service.getDevice();
892        if (device == null) return false;
893
894        synchronized(mDeviceBusy) {
895            if (mDeviceBusy) return false;
896            mDeviceBusy = true;
897        }
898
899        try {
900            mService.readCharacteristic(mClientIf, device.getAddress(),
901                service.getType(), service.getInstanceId(),
902                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
903                new ParcelUuid(characteristic.getUuid()), AUTHENTICATION_NONE);
904        } catch (RemoteException e) {
905            Log.e(TAG,"",e);
906            mDeviceBusy = false;
907            return false;
908        }
909
910        return true;
911    }
912
913    /**
914     * Writes a given characteristic and its values to the associated remote device.
915     *
916     * <p>Once the write operation has been completed, the
917     * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked,
918     * reporting the result of the operation.
919     *
920     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
921     *
922     * @param characteristic Characteristic to write on the remote device
923     * @return true, if the write operation was initiated successfully
924     */
925    public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
926        if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0
927            && (characteristic.getProperties() &
928                BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) return false;
929
930        if (VDBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid());
931        if (mService == null || mClientIf == 0) return false;
932
933        BluetoothGattService service = characteristic.getService();
934        if (service == null) return false;
935
936        BluetoothDevice device = service.getDevice();
937        if (device == null) return false;
938
939        synchronized(mDeviceBusy) {
940            if (mDeviceBusy) return false;
941            mDeviceBusy = true;
942        }
943
944        try {
945            mService.writeCharacteristic(mClientIf, device.getAddress(),
946                service.getType(), service.getInstanceId(),
947                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
948                new ParcelUuid(characteristic.getUuid()),
949                characteristic.getWriteType(), AUTHENTICATION_NONE,
950                characteristic.getValue());
951        } catch (RemoteException e) {
952            Log.e(TAG,"",e);
953            mDeviceBusy = false;
954            return false;
955        }
956
957        return true;
958    }
959
960    /**
961     * Reads the value for a given descriptor from the associated remote device.
962     *
963     * <p>Once the read operation has been completed, the
964     * {@link BluetoothGattCallback#onDescriptorRead} callback is
965     * triggered, signaling the result of the operation.
966     *
967     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
968     *
969     * @param descriptor Descriptor value to read from the remote device
970     * @return true, if the read operation was initiated successfully
971     */
972    public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
973        if (VDBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid());
974        if (mService == null || mClientIf == 0) return false;
975
976        BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
977        if (characteristic == null) return false;
978
979        BluetoothGattService service = characteristic.getService();
980        if (service == null) return false;
981
982        BluetoothDevice device = service.getDevice();
983        if (device == null) return false;
984
985        synchronized(mDeviceBusy) {
986            if (mDeviceBusy) return false;
987            mDeviceBusy = true;
988        }
989
990        try {
991            mService.readDescriptor(mClientIf, device.getAddress(), service.getType(),
992                service.getInstanceId(), new ParcelUuid(service.getUuid()),
993                characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()),
994                descriptor.getInstanceId(), new ParcelUuid(descriptor.getUuid()),
995                AUTHENTICATION_NONE);
996        } catch (RemoteException e) {
997            Log.e(TAG,"",e);
998            mDeviceBusy = false;
999            return false;
1000        }
1001
1002        return true;
1003    }
1004
1005    /**
1006     * Write the value of a given descriptor to the associated remote device.
1007     *
1008     * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is
1009     * triggered to report the result of the write operation.
1010     *
1011     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1012     *
1013     * @param descriptor Descriptor to write to the associated remote device
1014     * @return true, if the write operation was initiated successfully
1015     */
1016    public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
1017        if (VDBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid());
1018        if (mService == null || mClientIf == 0) return false;
1019
1020        BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
1021        if (characteristic == null) return false;
1022
1023        BluetoothGattService service = characteristic.getService();
1024        if (service == null) return false;
1025
1026        BluetoothDevice device = service.getDevice();
1027        if (device == null) return false;
1028
1029        synchronized(mDeviceBusy) {
1030            if (mDeviceBusy) return false;
1031            mDeviceBusy = true;
1032        }
1033
1034        try {
1035            mService.writeDescriptor(mClientIf, device.getAddress(), service.getType(),
1036                service.getInstanceId(), new ParcelUuid(service.getUuid()),
1037                characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()),
1038                descriptor.getInstanceId(), new ParcelUuid(descriptor.getUuid()),
1039                characteristic.getWriteType(), AUTHENTICATION_NONE,
1040                descriptor.getValue());
1041        } catch (RemoteException e) {
1042            Log.e(TAG,"",e);
1043            mDeviceBusy = false;
1044            return false;
1045        }
1046
1047        return true;
1048    }
1049
1050    /**
1051     * Initiates a reliable write transaction for a given remote device.
1052     *
1053     * <p>Once a reliable write transaction has been initiated, all calls
1054     * to {@link #writeCharacteristic} are sent to the remote device for
1055     * verification and queued up for atomic execution. The application will
1056     * receive an {@link BluetoothGattCallback#onCharacteristicWrite} callback
1057     * in response to every {@link #writeCharacteristic} call and is responsible
1058     * for verifying if the value has been transmitted accurately.
1059     *
1060     * <p>After all characteristics have been queued up and verified,
1061     * {@link #executeReliableWrite} will execute all writes. If a characteristic
1062     * was not written correctly, calling {@link #abortReliableWrite} will
1063     * cancel the current transaction without commiting any values on the
1064     * remote device.
1065     *
1066     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1067     *
1068     * @return true, if the reliable write transaction has been initiated
1069     */
1070    public boolean beginReliableWrite() {
1071        if (VDBG) Log.d(TAG, "beginReliableWrite() - device: " + mDevice.getAddress());
1072        if (mService == null || mClientIf == 0) return false;
1073
1074        try {
1075            mService.beginReliableWrite(mClientIf, mDevice.getAddress());
1076        } catch (RemoteException e) {
1077            Log.e(TAG,"",e);
1078            return false;
1079        }
1080
1081        return true;
1082    }
1083
1084    /**
1085     * Executes a reliable write transaction for a given remote device.
1086     *
1087     * <p>This function will commit all queued up characteristic write
1088     * operations for a given remote device.
1089     *
1090     * <p>A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is
1091     * invoked to indicate whether the transaction has been executed correctly.
1092     *
1093     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1094     *
1095     * @return true, if the request to execute the transaction has been sent
1096     */
1097    public boolean executeReliableWrite() {
1098        if (VDBG) Log.d(TAG, "executeReliableWrite() - device: " + mDevice.getAddress());
1099        if (mService == null || mClientIf == 0) return false;
1100
1101        synchronized(mDeviceBusy) {
1102            if (mDeviceBusy) return false;
1103            mDeviceBusy = true;
1104        }
1105
1106        try {
1107            mService.endReliableWrite(mClientIf, mDevice.getAddress(), true);
1108        } catch (RemoteException e) {
1109            Log.e(TAG,"",e);
1110            mDeviceBusy = false;
1111            return false;
1112        }
1113
1114        return true;
1115    }
1116
1117    /**
1118     * Cancels a reliable write transaction for a given device.
1119     *
1120     * <p>Calling this function will discard all queued characteristic write
1121     * operations for a given remote device.
1122     *
1123     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1124     */
1125    public void abortReliableWrite() {
1126        if (VDBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice.getAddress());
1127        if (mService == null || mClientIf == 0) return;
1128
1129        try {
1130            mService.endReliableWrite(mClientIf, mDevice.getAddress(), false);
1131        } catch (RemoteException e) {
1132            Log.e(TAG,"",e);
1133        }
1134    }
1135
1136    /**
1137     * @deprecated Use {@link #abortReliableWrite()}
1138     */
1139    public void abortReliableWrite(BluetoothDevice mDevice) {
1140        abortReliableWrite();
1141    }
1142
1143    /**
1144     * Enable or disable notifications/indications for a given characteristic.
1145     *
1146     * <p>Once notifications are enabled for a characteristic, a
1147     * {@link BluetoothGattCallback#onCharacteristicChanged} callback will be
1148     * triggered if the remote device indicates that the given characteristic
1149     * has changed.
1150     *
1151     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1152     *
1153     * @param characteristic The characteristic for which to enable notifications
1154     * @param enable Set to true to enable notifications/indications
1155     * @return true, if the requested notification status was set successfully
1156     */
1157    public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
1158                                              boolean enable) {
1159        if (DBG) Log.d(TAG, "setCharacteristicNotification() - uuid: " + characteristic.getUuid()
1160                         + " enable: " + enable);
1161        if (mService == null || mClientIf == 0) return false;
1162
1163        BluetoothGattService service = characteristic.getService();
1164        if (service == null) return false;
1165
1166        BluetoothDevice device = service.getDevice();
1167        if (device == null) return false;
1168
1169        try {
1170            mService.registerForNotification(mClientIf, device.getAddress(),
1171                service.getType(), service.getInstanceId(),
1172                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
1173                new ParcelUuid(characteristic.getUuid()),
1174                enable);
1175        } catch (RemoteException e) {
1176            Log.e(TAG,"",e);
1177            return false;
1178        }
1179
1180        return true;
1181    }
1182
1183    /**
1184     * Clears the internal cache and forces a refresh of the services from the
1185     * remote device.
1186     * @hide
1187     */
1188    public boolean refresh() {
1189        if (DBG) Log.d(TAG, "refresh() - device: " + mDevice.getAddress());
1190        if (mService == null || mClientIf == 0) return false;
1191
1192        try {
1193            mService.refreshDevice(mClientIf, mDevice.getAddress());
1194        } catch (RemoteException e) {
1195            Log.e(TAG,"",e);
1196            return false;
1197        }
1198
1199        return true;
1200    }
1201
1202    /**
1203     * Read the RSSI for a connected remote device.
1204     *
1205     * <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be
1206     * invoked when the RSSI value has been read.
1207     *
1208     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1209     *
1210     * @return true, if the RSSI value has been requested successfully
1211     */
1212    public boolean readRemoteRssi() {
1213        if (DBG) Log.d(TAG, "readRssi() - device: " + mDevice.getAddress());
1214        if (mService == null || mClientIf == 0) return false;
1215
1216        try {
1217            mService.readRemoteRssi(mClientIf, mDevice.getAddress());
1218        } catch (RemoteException e) {
1219            Log.e(TAG,"",e);
1220            return false;
1221        }
1222
1223        return true;
1224    }
1225
1226    /**
1227     * Request an MTU size used for a given connection.
1228     *
1229     * <p>When performing a write request operation (write without response),
1230     * the data sent is truncated to the MTU size. This function may be used
1231     * to request a larger MTU size to be able to send more data at once.
1232     *
1233     * <p>A {@link BluetoothGattCallback#onMtuChanged} callback will indicate
1234     * whether this operation was successful.
1235     *
1236     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1237     *
1238     * @return true, if the new MTU value has been requested successfully
1239     */
1240    public boolean requestMtu(int mtu) {
1241        if (DBG) Log.d(TAG, "configureMTU() - device: " + mDevice.getAddress()
1242                            + " mtu: " + mtu);
1243        if (mService == null || mClientIf == 0) return false;
1244
1245        try {
1246            mService.configureMTU(mClientIf, mDevice.getAddress(), mtu);
1247        } catch (RemoteException e) {
1248            Log.e(TAG,"",e);
1249            return false;
1250        }
1251
1252        return true;
1253    }
1254
1255    /**
1256     * Request a connection parameter update.
1257     *
1258     * <p>This function will send a connection parameter update request to the
1259     * remote device.
1260     *
1261     * @param connectionPriority Request a specific connection priority. Must be one of
1262     *          {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED},
1263     *          {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH}
1264     *          or {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}.
1265     * @throws IllegalArgumentException If the parameters are outside of their
1266     *                                  specified range.
1267     */
1268    public boolean requestConnectionPriority(int connectionPriority) {
1269        if (connectionPriority < CONNECTION_PRIORITY_BALANCED ||
1270            connectionPriority > CONNECTION_PRIORITY_LOW_POWER) {
1271            throw new IllegalArgumentException("connectionPriority not within valid range");
1272        }
1273
1274        if (DBG) Log.d(TAG, "requestConnectionPriority() - params: " + connectionPriority);
1275        if (mService == null || mClientIf == 0) return false;
1276
1277        try {
1278            mService.connectionParameterUpdate(mClientIf, mDevice.getAddress(), connectionPriority);
1279        } catch (RemoteException e) {
1280            Log.e(TAG,"",e);
1281            return false;
1282        }
1283
1284        return true;
1285    }
1286
1287    /**
1288     * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
1289     * with {@link BluetoothProfile#GATT} as argument
1290     *
1291     * @throws UnsupportedOperationException
1292     */
1293    @Override
1294    public int getConnectionState(BluetoothDevice device) {
1295        throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
1296    }
1297
1298    /**
1299     * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
1300     * with {@link BluetoothProfile#GATT} as argument
1301     *
1302     * @throws UnsupportedOperationException
1303     */
1304    @Override
1305    public List<BluetoothDevice> getConnectedDevices() {
1306        throw new UnsupportedOperationException
1307            ("Use BluetoothManager#getConnectedDevices instead.");
1308    }
1309
1310    /**
1311     * Not supported - please use
1312     * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])}
1313     * with {@link BluetoothProfile#GATT} as first argument
1314     *
1315     * @throws UnsupportedOperationException
1316     */
1317    @Override
1318    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
1319        throw new UnsupportedOperationException
1320            ("Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
1321    }
1322}
1323