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