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