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.os.Handler;
     20 import android.os.ParcelUuid;
     21 import android.os.RemoteException;
     22 import android.util.Log;
     23 
     24 import java.util.ArrayList;
     25 import java.util.List;
     26 import java.util.UUID;
     27 
     28 /**
     29  * Public API for the Bluetooth GATT Profile.
     30  *
     31  * <p>This class provides Bluetooth GATT functionality to enable communication
     32  * with Bluetooth Smart or Smart Ready devices.
     33  *
     34  * <p>To connect to a remote peripheral device, create a {@link BluetoothGattCallback}
     35  * and call {@link BluetoothDevice#connectGatt} to get a instance of this class.
     36  * GATT capable devices can be discovered using the Bluetooth device discovery or BLE
     37  * scan process.
     38  */
     39 public final class BluetoothGatt implements BluetoothProfile {
     40     private static final String TAG = "BluetoothGatt";
     41     private static final boolean DBG = true;
     42     private static final boolean VDBG = false;
     43 
     44     private IBluetoothGatt mService;
     45     private volatile BluetoothGattCallback mCallback;
     46     private Handler mHandler;
     47     private int mClientIf;
     48     private BluetoothDevice mDevice;
     49     private boolean mAutoConnect;
     50     private int mAuthRetryState;
     51     private int mConnState;
     52     private final Object mStateLock = new Object();
     53     private Boolean mDeviceBusy = false;
     54     private int mTransport;
     55     private int mPhy;
     56     private boolean mOpportunistic;
     57 
     58     private static final int AUTH_RETRY_STATE_IDLE = 0;
     59     private static final int AUTH_RETRY_STATE_NO_MITM = 1;
     60     private static final int AUTH_RETRY_STATE_MITM = 2;
     61 
     62     private static final int CONN_STATE_IDLE = 0;
     63     private static final int CONN_STATE_CONNECTING = 1;
     64     private static final int CONN_STATE_CONNECTED = 2;
     65     private static final int CONN_STATE_DISCONNECTING = 3;
     66     private static final int CONN_STATE_CLOSED = 4;
     67 
     68     private List<BluetoothGattService> mServices;
     69 
     70     /** A GATT operation completed successfully */
     71     public static final int GATT_SUCCESS = 0;
     72 
     73     /** GATT read operation is not permitted */
     74     public static final int GATT_READ_NOT_PERMITTED = 0x2;
     75 
     76     /** GATT write operation is not permitted */
     77     public static final int GATT_WRITE_NOT_PERMITTED = 0x3;
     78 
     79     /** Insufficient authentication for a given operation */
     80     public static final int GATT_INSUFFICIENT_AUTHENTICATION = 0x5;
     81 
     82     /** The given request is not supported */
     83     public static final int GATT_REQUEST_NOT_SUPPORTED = 0x6;
     84 
     85     /** Insufficient encryption for a given operation */
     86     public static final int GATT_INSUFFICIENT_ENCRYPTION = 0xf;
     87 
     88     /** A read or write operation was requested with an invalid offset */
     89     public static final int GATT_INVALID_OFFSET = 0x7;
     90 
     91     /** A write operation exceeds the maximum length of the attribute */
     92     public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 0xd;
     93 
     94     /** A remote device connection is congested. */
     95     public static final int GATT_CONNECTION_CONGESTED = 0x8f;
     96 
     97     /** A GATT operation failed, errors other than the above */
     98     public static final int GATT_FAILURE = 0x101;
     99 
    100     /**
    101      * Connection parameter update - Use the connection parameters recommended by the
    102      * Bluetooth SIG. This is the default value if no connection parameter update
    103      * is requested.
    104      */
    105     public static final int CONNECTION_PRIORITY_BALANCED = 0;
    106 
    107     /**
    108      * Connection parameter update - Request a high priority, low latency connection.
    109      * An application should only request high priority connection parameters to transfer large
    110      * amounts of data over LE quickly. Once the transfer is complete, the application should
    111      * request {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED} connection parameters to reduce
    112      * energy use.
    113      */
    114     public static final int CONNECTION_PRIORITY_HIGH = 1;
    115 
    116     /** Connection parameter update - Request low power, reduced data rate connection parameters. */
    117     public static final int CONNECTION_PRIORITY_LOW_POWER = 2;
    118 
    119     /**
    120      * No authentication required.
    121      *
    122      * @hide
    123      */
    124     /*package*/ static final int AUTHENTICATION_NONE = 0;
    125 
    126     /**
    127      * Authentication requested; no man-in-the-middle protection required.
    128      *
    129      * @hide
    130      */
    131     /*package*/ static final int AUTHENTICATION_NO_MITM = 1;
    132 
    133     /**
    134      * Authentication with man-in-the-middle protection requested.
    135      *
    136      * @hide
    137      */
    138     /*package*/ static final int AUTHENTICATION_MITM = 2;
    139 
    140     /**
    141      * Bluetooth GATT callbacks. Overrides the default BluetoothGattCallback implementation.
    142      */
    143     private final IBluetoothGattCallback mBluetoothGattCallback =
    144             new IBluetoothGattCallback.Stub() {
    145                 /**
    146                  * Application interface registered - app is ready to go
    147                  * @hide
    148                  */
    149                 @Override
    150                 public void onClientRegistered(int status, int clientIf) {
    151                     if (DBG) {
    152                         Log.d(TAG, "onClientRegistered() - status=" + status
    153                                 + " clientIf=" + clientIf);
    154                     }
    155                     if (VDBG) {
    156                         synchronized (mStateLock) {
    157                             if (mConnState != CONN_STATE_CONNECTING) {
    158                                 Log.e(TAG, "Bad connection state: " + mConnState);
    159                             }
    160                         }
    161                     }
    162                     mClientIf = clientIf;
    163                     if (status != GATT_SUCCESS) {
    164                         runOrQueueCallback(new Runnable() {
    165                             @Override
    166                             public void run() {
    167                                 final BluetoothGattCallback callback = mCallback;
    168                                 if (callback != null) {
    169                                     callback.onConnectionStateChange(BluetoothGatt.this,
    170                                             GATT_FAILURE,
    171                                             BluetoothProfile.STATE_DISCONNECTED);
    172                                 }
    173                             }
    174                         });
    175 
    176                         synchronized (mStateLock) {
    177                             mConnState = CONN_STATE_IDLE;
    178                         }
    179                         return;
    180                     }
    181                     try {
    182                         mService.clientConnect(mClientIf, mDevice.getAddress(),
    183                                 !mAutoConnect, mTransport, mOpportunistic,
    184                                 mPhy); // autoConnect is inverse of "isDirect"
    185                     } catch (RemoteException e) {
    186                         Log.e(TAG, "", e);
    187                     }
    188                 }
    189 
    190                 /**
    191                  * Phy update callback
    192                  * @hide
    193                  */
    194                 @Override
    195                 public void onPhyUpdate(String address, int txPhy, int rxPhy, int status) {
    196                     if (DBG) {
    197                         Log.d(TAG, "onPhyUpdate() - status=" + status
    198                                 + " address=" + address + " txPhy=" + txPhy + " rxPhy=" + rxPhy);
    199                     }
    200                     if (!address.equals(mDevice.getAddress())) {
    201                         return;
    202                     }
    203 
    204                     runOrQueueCallback(new Runnable() {
    205                         @Override
    206                         public void run() {
    207                             final BluetoothGattCallback callback = mCallback;
    208                             if (callback != null) {
    209                                 callback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status);
    210                             }
    211                         }
    212                     });
    213                 }
    214 
    215                 /**
    216                  * Phy read callback
    217                  * @hide
    218                  */
    219                 @Override
    220                 public void onPhyRead(String address, int txPhy, int rxPhy, int status) {
    221                     if (DBG) {
    222                         Log.d(TAG, "onPhyRead() - status=" + status
    223                                 + " address=" + address + " txPhy=" + txPhy + " rxPhy=" + rxPhy);
    224                     }
    225                     if (!address.equals(mDevice.getAddress())) {
    226                         return;
    227                     }
    228 
    229                     runOrQueueCallback(new Runnable() {
    230                         @Override
    231                         public void run() {
    232                             final BluetoothGattCallback callback = mCallback;
    233                             if (callback != null) {
    234                                 callback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status);
    235                             }
    236                         }
    237                     });
    238                 }
    239 
    240                 /**
    241                  * Client connection state changed
    242                  * @hide
    243                  */
    244                 @Override
    245                 public void onClientConnectionState(int status, int clientIf,
    246                         boolean connected, String address) {
    247                     if (DBG) {
    248                         Log.d(TAG, "onClientConnectionState() - status=" + status
    249                                 + " clientIf=" + clientIf + " device=" + address);
    250                     }
    251                     if (!address.equals(mDevice.getAddress())) {
    252                         return;
    253                     }
    254                     int profileState = connected ? BluetoothProfile.STATE_CONNECTED :
    255                             BluetoothProfile.STATE_DISCONNECTED;
    256 
    257                     runOrQueueCallback(new Runnable() {
    258                         @Override
    259                         public void run() {
    260                             final BluetoothGattCallback callback = mCallback;
    261                             if (callback != null) {
    262                                 callback.onConnectionStateChange(BluetoothGatt.this, status,
    263                                         profileState);
    264                             }
    265                         }
    266                     });
    267 
    268                     synchronized (mStateLock) {
    269                         if (connected) {
    270                             mConnState = CONN_STATE_CONNECTED;
    271                         } else {
    272                             mConnState = CONN_STATE_IDLE;
    273                         }
    274                     }
    275 
    276                     synchronized (mDeviceBusy) {
    277                         mDeviceBusy = false;
    278                     }
    279                 }
    280 
    281                 /**
    282                  * Remote search has been completed.
    283                  * The internal object structure should now reflect the state
    284                  * of the remote device database. Let the application know that
    285                  * we are done at this point.
    286                  * @hide
    287                  */
    288                 @Override
    289                 public void onSearchComplete(String address, List<BluetoothGattService> services,
    290                         int status) {
    291                     if (DBG) {
    292                         Log.d(TAG,
    293                                 "onSearchComplete() = Device=" + address + " Status=" + status);
    294                     }
    295                     if (!address.equals(mDevice.getAddress())) {
    296                         return;
    297                     }
    298 
    299                     for (BluetoothGattService s : services) {
    300                         //services we receive don't have device set properly.
    301                         s.setDevice(mDevice);
    302                     }
    303 
    304                     mServices.addAll(services);
    305 
    306                     // Fix references to included services, as they doesn't point to right objects.
    307                     for (BluetoothGattService fixedService : mServices) {
    308                         ArrayList<BluetoothGattService> includedServices =
    309                                 new ArrayList(fixedService.getIncludedServices());
    310                         fixedService.getIncludedServices().clear();
    311 
    312                         for (BluetoothGattService brokenRef : includedServices) {
    313                             BluetoothGattService includedService = getService(mDevice,
    314                                     brokenRef.getUuid(), brokenRef.getInstanceId());
    315                             if (includedService != null) {
    316                                 fixedService.addIncludedService(includedService);
    317                             } else {
    318                                 Log.e(TAG, "Broken GATT database: can't find included service.");
    319                             }
    320                         }
    321                     }
    322 
    323                     runOrQueueCallback(new Runnable() {
    324                         @Override
    325                         public void run() {
    326                             final BluetoothGattCallback callback = mCallback;
    327                             if (callback != null) {
    328                                 callback.onServicesDiscovered(BluetoothGatt.this, status);
    329                             }
    330                         }
    331                     });
    332                 }
    333 
    334                 /**
    335                  * Remote characteristic has been read.
    336                  * Updates the internal value.
    337                  * @hide
    338                  */
    339                 @Override
    340                 public void onCharacteristicRead(String address, int status, int handle,
    341                         byte[] value) {
    342                     if (VDBG) {
    343                         Log.d(TAG, "onCharacteristicRead() - Device=" + address
    344                                 + " handle=" + handle + " Status=" + status);
    345                     }
    346 
    347                     if (!address.equals(mDevice.getAddress())) {
    348                         return;
    349                     }
    350 
    351                     synchronized (mDeviceBusy) {
    352                         mDeviceBusy = false;
    353                     }
    354 
    355                     if ((status == GATT_INSUFFICIENT_AUTHENTICATION
    356                             || status == GATT_INSUFFICIENT_ENCRYPTION)
    357                             && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) {
    358                         try {
    359                             final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE)
    360                                     ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM;
    361                             mService.readCharacteristic(mClientIf, address, handle, authReq);
    362                             mAuthRetryState++;
    363                             return;
    364                         } catch (RemoteException e) {
    365                             Log.e(TAG, "", e);
    366                         }
    367                     }
    368 
    369                     mAuthRetryState = AUTH_RETRY_STATE_IDLE;
    370 
    371                     BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice,
    372                             handle);
    373                     if (characteristic == null) {
    374                         Log.w(TAG, "onCharacteristicRead() failed to find characteristic!");
    375                         return;
    376                     }
    377 
    378                     runOrQueueCallback(new Runnable() {
    379                         @Override
    380                         public void run() {
    381                             final BluetoothGattCallback callback = mCallback;
    382                             if (callback != null) {
    383                                 if (status == 0) characteristic.setValue(value);
    384                                 callback.onCharacteristicRead(BluetoothGatt.this, characteristic,
    385                                         status);
    386                             }
    387                         }
    388                     });
    389                 }
    390 
    391                 /**
    392                  * Characteristic has been written to the remote device.
    393                  * Let the app know how we did...
    394                  * @hide
    395                  */
    396                 @Override
    397                 public void onCharacteristicWrite(String address, int status, int handle) {
    398                     if (VDBG) {
    399                         Log.d(TAG, "onCharacteristicWrite() - Device=" + address
    400                                 + " handle=" + handle + " Status=" + status);
    401                     }
    402 
    403                     if (!address.equals(mDevice.getAddress())) {
    404                         return;
    405                     }
    406 
    407                     synchronized (mDeviceBusy) {
    408                         mDeviceBusy = false;
    409                     }
    410 
    411                     BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice,
    412                             handle);
    413                     if (characteristic == null) return;
    414 
    415                     if ((status == GATT_INSUFFICIENT_AUTHENTICATION
    416                             || status == GATT_INSUFFICIENT_ENCRYPTION)
    417                             && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) {
    418                         try {
    419                             final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE)
    420                                     ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM;
    421                             mService.writeCharacteristic(mClientIf, address, handle,
    422                                     characteristic.getWriteType(), authReq,
    423                                     characteristic.getValue());
    424                             mAuthRetryState++;
    425                             return;
    426                         } catch (RemoteException e) {
    427                             Log.e(TAG, "", e);
    428                         }
    429                     }
    430 
    431                     mAuthRetryState = AUTH_RETRY_STATE_IDLE;
    432 
    433                     runOrQueueCallback(new Runnable() {
    434                         @Override
    435                         public void run() {
    436                             final BluetoothGattCallback callback = mCallback;
    437                             if (callback != null) {
    438                                 callback.onCharacteristicWrite(BluetoothGatt.this, characteristic,
    439                                         status);
    440                             }
    441                         }
    442                     });
    443                 }
    444 
    445                 /**
    446                  * Remote characteristic has been updated.
    447                  * Updates the internal value.
    448                  * @hide
    449                  */
    450                 @Override
    451                 public void onNotify(String address, int handle, byte[] value) {
    452                     if (VDBG) Log.d(TAG, "onNotify() - Device=" + address + " handle=" + handle);
    453 
    454                     if (!address.equals(mDevice.getAddress())) {
    455                         return;
    456                     }
    457 
    458                     BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice,
    459                             handle);
    460                     if (characteristic == null) return;
    461 
    462                     runOrQueueCallback(new Runnable() {
    463                         @Override
    464                         public void run() {
    465                             final BluetoothGattCallback callback = mCallback;
    466                             if (callback != null) {
    467                                 characteristic.setValue(value);
    468                                 callback.onCharacteristicChanged(BluetoothGatt.this,
    469                                         characteristic);
    470                             }
    471                         }
    472                     });
    473                 }
    474 
    475                 /**
    476                  * Descriptor has been read.
    477                  * @hide
    478                  */
    479                 @Override
    480                 public void onDescriptorRead(String address, int status, int handle, byte[] value) {
    481                     if (VDBG) {
    482                         Log.d(TAG,
    483                                 "onDescriptorRead() - Device=" + address + " handle=" + handle);
    484                     }
    485 
    486                     if (!address.equals(mDevice.getAddress())) {
    487                         return;
    488                     }
    489 
    490                     synchronized (mDeviceBusy) {
    491                         mDeviceBusy = false;
    492                     }
    493 
    494                     BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle);
    495                     if (descriptor == null) return;
    496 
    497 
    498                     if ((status == GATT_INSUFFICIENT_AUTHENTICATION
    499                             || status == GATT_INSUFFICIENT_ENCRYPTION)
    500                             && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) {
    501                         try {
    502                             final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE)
    503                                     ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM;
    504                             mService.readDescriptor(mClientIf, address, handle, authReq);
    505                             mAuthRetryState++;
    506                             return;
    507                         } catch (RemoteException e) {
    508                             Log.e(TAG, "", e);
    509                         }
    510                     }
    511 
    512                     mAuthRetryState = AUTH_RETRY_STATE_IDLE;
    513 
    514                     runOrQueueCallback(new Runnable() {
    515                         @Override
    516                         public void run() {
    517                             final BluetoothGattCallback callback = mCallback;
    518                             if (callback != null) {
    519                                 if (status == 0) descriptor.setValue(value);
    520                                 callback.onDescriptorRead(BluetoothGatt.this, descriptor, status);
    521                             }
    522                         }
    523                     });
    524                 }
    525 
    526                 /**
    527                  * Descriptor write operation complete.
    528                  * @hide
    529                  */
    530                 @Override
    531                 public void onDescriptorWrite(String address, int status, int handle) {
    532                     if (VDBG) {
    533                         Log.d(TAG,
    534                                 "onDescriptorWrite() - Device=" + address + " handle=" + handle);
    535                     }
    536 
    537                     if (!address.equals(mDevice.getAddress())) {
    538                         return;
    539                     }
    540 
    541                     synchronized (mDeviceBusy) {
    542                         mDeviceBusy = false;
    543                     }
    544 
    545                     BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle);
    546                     if (descriptor == null) return;
    547 
    548                     if ((status == GATT_INSUFFICIENT_AUTHENTICATION
    549                             || status == GATT_INSUFFICIENT_ENCRYPTION)
    550                             && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) {
    551                         try {
    552                             final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE)
    553                                     ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM;
    554                             mService.writeDescriptor(mClientIf, address, handle,
    555                                     authReq, descriptor.getValue());
    556                             mAuthRetryState++;
    557                             return;
    558                         } catch (RemoteException e) {
    559                             Log.e(TAG, "", e);
    560                         }
    561                     }
    562 
    563                     mAuthRetryState = AUTH_RETRY_STATE_IDLE;
    564 
    565                     runOrQueueCallback(new Runnable() {
    566                         @Override
    567                         public void run() {
    568                             final BluetoothGattCallback callback = mCallback;
    569                             if (callback != null) {
    570                                 callback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
    571                             }
    572                         }
    573                     });
    574                 }
    575 
    576                 /**
    577                  * Prepared write transaction completed (or aborted)
    578                  * @hide
    579                  */
    580                 @Override
    581                 public void onExecuteWrite(String address, int status) {
    582                     if (VDBG) {
    583                         Log.d(TAG, "onExecuteWrite() - Device=" + address
    584                                 + " status=" + status);
    585                     }
    586                     if (!address.equals(mDevice.getAddress())) {
    587                         return;
    588                     }
    589 
    590                     synchronized (mDeviceBusy) {
    591                         mDeviceBusy = false;
    592                     }
    593 
    594                     runOrQueueCallback(new Runnable() {
    595                         @Override
    596                         public void run() {
    597                             final BluetoothGattCallback callback = mCallback;
    598                             if (callback != null) {
    599                                 callback.onReliableWriteCompleted(BluetoothGatt.this, status);
    600                             }
    601                         }
    602                     });
    603                 }
    604 
    605                 /**
    606                  * Remote device RSSI has been read
    607                  * @hide
    608                  */
    609                 @Override
    610                 public void onReadRemoteRssi(String address, int rssi, int status) {
    611                     if (VDBG) {
    612                         Log.d(TAG, "onReadRemoteRssi() - Device=" + address
    613                                 + " rssi=" + rssi + " status=" + status);
    614                     }
    615                     if (!address.equals(mDevice.getAddress())) {
    616                         return;
    617                     }
    618                     runOrQueueCallback(new Runnable() {
    619                         @Override
    620                         public void run() {
    621                             final BluetoothGattCallback callback = mCallback;
    622                             if (callback != null) {
    623                                 callback.onReadRemoteRssi(BluetoothGatt.this, rssi, status);
    624                             }
    625                         }
    626                     });
    627                 }
    628 
    629                 /**
    630                  * Callback invoked when the MTU for a given connection changes
    631                  * @hide
    632                  */
    633                 @Override
    634                 public void onConfigureMTU(String address, int mtu, int status) {
    635                     if (DBG) {
    636                         Log.d(TAG, "onConfigureMTU() - Device=" + address
    637                                 + " mtu=" + mtu + " status=" + status);
    638                     }
    639                     if (!address.equals(mDevice.getAddress())) {
    640                         return;
    641                     }
    642 
    643                     runOrQueueCallback(new Runnable() {
    644                         @Override
    645                         public void run() {
    646                             final BluetoothGattCallback callback = mCallback;
    647                             if (callback != null) {
    648                                 callback.onMtuChanged(BluetoothGatt.this, mtu, status);
    649                             }
    650                         }
    651                     });
    652                 }
    653 
    654                 /**
    655                  * Callback invoked when the given connection is updated
    656                  * @hide
    657                  */
    658                 @Override
    659                 public void onConnectionUpdated(String address, int interval, int latency,
    660                         int timeout, int status) {
    661                     if (DBG) {
    662                         Log.d(TAG, "onConnectionUpdated() - Device=" + address
    663                                 + " interval=" + interval + " latency=" + latency
    664                                 + " timeout=" + timeout + " status=" + status);
    665                     }
    666                     if (!address.equals(mDevice.getAddress())) {
    667                         return;
    668                     }
    669 
    670                     runOrQueueCallback(new Runnable() {
    671                         @Override
    672                         public void run() {
    673                             final BluetoothGattCallback callback = mCallback;
    674                             if (callback != null) {
    675                                 callback.onConnectionUpdated(BluetoothGatt.this, interval, latency,
    676                                         timeout, status);
    677                             }
    678                         }
    679                     });
    680                 }
    681             };
    682 
    683     /*package*/ BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device,
    684             int transport, boolean opportunistic, int phy) {
    685         mService = iGatt;
    686         mDevice = device;
    687         mTransport = transport;
    688         mPhy = phy;
    689         mOpportunistic = opportunistic;
    690         mServices = new ArrayList<BluetoothGattService>();
    691 
    692         mConnState = CONN_STATE_IDLE;
    693         mAuthRetryState = AUTH_RETRY_STATE_IDLE;
    694     }
    695 
    696     /**
    697      * Close this Bluetooth GATT client.
    698      *
    699      * Application should call this method as early as possible after it is done with
    700      * this GATT client.
    701      */
    702     public void close() {
    703         if (DBG) Log.d(TAG, "close()");
    704 
    705         unregisterApp();
    706         mConnState = CONN_STATE_CLOSED;
    707         mAuthRetryState = AUTH_RETRY_STATE_IDLE;
    708     }
    709 
    710     /**
    711      * Returns a service by UUID, instance and type.
    712      *
    713      * @hide
    714      */
    715     /*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid,
    716             int instanceId) {
    717         for (BluetoothGattService svc : mServices) {
    718             if (svc.getDevice().equals(device)
    719                     && svc.getInstanceId() == instanceId
    720                     && svc.getUuid().equals(uuid)) {
    721                 return svc;
    722             }
    723         }
    724         return null;
    725     }
    726 
    727 
    728     /**
    729      * Returns a characteristic with id equal to instanceId.
    730      *
    731      * @hide
    732      */
    733     /*package*/ BluetoothGattCharacteristic getCharacteristicById(BluetoothDevice device,
    734             int instanceId) {
    735         for (BluetoothGattService svc : mServices) {
    736             for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
    737                 if (charac.getInstanceId() == instanceId) {
    738                     return charac;
    739                 }
    740             }
    741         }
    742         return null;
    743     }
    744 
    745     /**
    746      * Returns a descriptor with id equal to instanceId.
    747      *
    748      * @hide
    749      */
    750     /*package*/ BluetoothGattDescriptor getDescriptorById(BluetoothDevice device, int instanceId) {
    751         for (BluetoothGattService svc : mServices) {
    752             for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
    753                 for (BluetoothGattDescriptor desc : charac.getDescriptors()) {
    754                     if (desc.getInstanceId() == instanceId) {
    755                         return desc;
    756                     }
    757                 }
    758             }
    759         }
    760         return null;
    761     }
    762 
    763     /**
    764      * Queue the runnable on a {@link Handler} provided by the user, or execute the runnable
    765      * immediately if no Handler was provided.
    766      */
    767     private void runOrQueueCallback(final Runnable cb) {
    768         if (mHandler == null) {
    769             try {
    770                 cb.run();
    771             } catch (Exception ex) {
    772                 Log.w(TAG, "Unhandled exception in callback", ex);
    773             }
    774         } else {
    775             mHandler.post(cb);
    776         }
    777     }
    778 
    779     /**
    780      * Register an application callback to start using GATT.
    781      *
    782      * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
    783      * is used to notify success or failure if the function returns true.
    784      *
    785      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    786      *
    787      * @param callback GATT callback handler that will receive asynchronous callbacks.
    788      * @return If true, the callback will be called to notify success or failure, false on immediate
    789      * error
    790      */
    791     private boolean registerApp(BluetoothGattCallback callback, Handler handler) {
    792         if (DBG) Log.d(TAG, "registerApp()");
    793         if (mService == null) return false;
    794 
    795         mCallback = callback;
    796         mHandler = handler;
    797         UUID uuid = UUID.randomUUID();
    798         if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid);
    799 
    800         try {
    801             mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback);
    802         } catch (RemoteException e) {
    803             Log.e(TAG, "", e);
    804             return false;
    805         }
    806 
    807         return true;
    808     }
    809 
    810     /**
    811      * Unregister the current application and callbacks.
    812      */
    813     private void unregisterApp() {
    814         if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf);
    815         if (mService == null || mClientIf == 0) return;
    816 
    817         try {
    818             mCallback = null;
    819             mService.unregisterClient(mClientIf);
    820             mClientIf = 0;
    821         } catch (RemoteException e) {
    822             Log.e(TAG, "", e);
    823         }
    824     }
    825 
    826     /**
    827      * Initiate a connection to a Bluetooth GATT capable device.
    828      *
    829      * <p>The connection may not be established right away, but will be
    830      * completed when the remote device is available. A
    831      * {@link BluetoothGattCallback#onConnectionStateChange} callback will be
    832      * invoked when the connection state changes as a result of this function.
    833      *
    834      * <p>The autoConnect parameter determines whether to actively connect to
    835      * the remote device, or rather passively scan and finalize the connection
    836      * when the remote device is in range/available. Generally, the first ever
    837      * connection to a device should be direct (autoConnect set to false) and
    838      * subsequent connections to known devices should be invoked with the
    839      * autoConnect parameter set to true.
    840      *
    841      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    842      *
    843      * @param device Remote device to connect to
    844      * @param autoConnect Whether to directly connect to the remote device (false) or to
    845      * automatically connect as soon as the remote device becomes available (true).
    846      * @return true, if the connection attempt was initiated successfully
    847      */
    848     /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback,
    849             Handler handler) {
    850         if (DBG) {
    851             Log.d(TAG,
    852                     "connect() - device: " + mDevice.getAddress() + ", auto: " + autoConnect);
    853         }
    854         synchronized (mStateLock) {
    855             if (mConnState != CONN_STATE_IDLE) {
    856                 throw new IllegalStateException("Not idle");
    857             }
    858             mConnState = CONN_STATE_CONNECTING;
    859         }
    860 
    861         mAutoConnect = autoConnect;
    862 
    863         if (!registerApp(callback, handler)) {
    864             synchronized (mStateLock) {
    865                 mConnState = CONN_STATE_IDLE;
    866             }
    867             Log.e(TAG, "Failed to register callback");
    868             return false;
    869         }
    870 
    871         // The connection will continue in the onClientRegistered callback
    872         return true;
    873     }
    874 
    875     /**
    876      * Disconnects an established connection, or cancels a connection attempt
    877      * currently in progress.
    878      *
    879      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    880      */
    881     public void disconnect() {
    882         if (DBG) Log.d(TAG, "cancelOpen() - device: " + mDevice.getAddress());
    883         if (mService == null || mClientIf == 0) return;
    884 
    885         try {
    886             mService.clientDisconnect(mClientIf, mDevice.getAddress());
    887         } catch (RemoteException e) {
    888             Log.e(TAG, "", e);
    889         }
    890     }
    891 
    892     /**
    893      * Connect back to remote device.
    894      *
    895      * <p>This method is used to re-connect to a remote device after the
    896      * connection has been dropped. If the device is not in range, the
    897      * re-connection will be triggered once the device is back in range.
    898      *
    899      * @return true, if the connection attempt was initiated successfully
    900      */
    901     public boolean connect() {
    902         try {
    903             mService.clientConnect(mClientIf, mDevice.getAddress(), false, mTransport,
    904                     mOpportunistic, mPhy); // autoConnect is inverse of "isDirect"
    905             return true;
    906         } catch (RemoteException e) {
    907             Log.e(TAG, "", e);
    908             return false;
    909         }
    910     }
    911 
    912     /**
    913      * Set the preferred connection PHY for this app. Please note that this is just a
    914      * recommendation, whether the PHY change will happen depends on other applications preferences,
    915      * local and remote controller capabilities. Controller can override these settings.
    916      * <p>
    917      * {@link BluetoothGattCallback#onPhyUpdate} will be triggered as a result of this call, even
    918      * if no PHY change happens. It is also triggered when remote device updates the PHY.
    919      *
    920      * @param txPhy preferred transmitter PHY. Bitwise OR of any of {@link
    921      * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link
    922      * BluetoothDevice#PHY_LE_CODED_MASK}.
    923      * @param rxPhy preferred receiver PHY. Bitwise OR of any of {@link
    924      * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link
    925      * BluetoothDevice#PHY_LE_CODED_MASK}.
    926      * @param phyOptions preferred coding to use when transmitting on the LE Coded PHY. Can be one
    927      * of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2} or
    928      * {@link BluetoothDevice#PHY_OPTION_S8}
    929      */
    930     public void setPreferredPhy(int txPhy, int rxPhy, int phyOptions) {
    931         try {
    932             mService.clientSetPreferredPhy(mClientIf, mDevice.getAddress(), txPhy, rxPhy,
    933                     phyOptions);
    934         } catch (RemoteException e) {
    935             Log.e(TAG, "", e);
    936         }
    937     }
    938 
    939     /**
    940      * Read the current transmitter PHY and receiver PHY of the connection. The values are returned
    941      * in {@link BluetoothGattCallback#onPhyRead}
    942      */
    943     public void readPhy() {
    944         try {
    945             mService.clientReadPhy(mClientIf, mDevice.getAddress());
    946         } catch (RemoteException e) {
    947             Log.e(TAG, "", e);
    948         }
    949     }
    950 
    951     /**
    952      * Return the remote bluetooth device this GATT client targets to
    953      *
    954      * @return remote bluetooth device
    955      */
    956     public BluetoothDevice getDevice() {
    957         return mDevice;
    958     }
    959 
    960     /**
    961      * Discovers services offered by a remote device as well as their
    962      * characteristics and descriptors.
    963      *
    964      * <p>This is an asynchronous operation. Once service discovery is completed,
    965      * the {@link BluetoothGattCallback#onServicesDiscovered} callback is
    966      * triggered. If the discovery was successful, the remote services can be
    967      * retrieved using the {@link #getServices} function.
    968      *
    969      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    970      *
    971      * @return true, if the remote service discovery has been started
    972      */
    973     public boolean discoverServices() {
    974         if (DBG) Log.d(TAG, "discoverServices() - device: " + mDevice.getAddress());
    975         if (mService == null || mClientIf == 0) return false;
    976 
    977         mServices.clear();
    978 
    979         try {
    980             mService.discoverServices(mClientIf, mDevice.getAddress());
    981         } catch (RemoteException e) {
    982             Log.e(TAG, "", e);
    983             return false;
    984         }
    985 
    986         return true;
    987     }
    988 
    989     /**
    990      * Discovers a service by UUID. This is exposed only for passing PTS tests.
    991      * It should never be used by real applications. The service is not searched
    992      * for characteristics and descriptors, or returned in any callback.
    993      *
    994      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    995      *
    996      * @return true, if the remote service discovery has been started
    997      * @hide
    998      */
    999     public boolean discoverServiceByUuid(UUID uuid) {
   1000         if (DBG) Log.d(TAG, "discoverServiceByUuid() - device: " + mDevice.getAddress());
   1001         if (mService == null || mClientIf == 0) return false;
   1002 
   1003         mServices.clear();
   1004 
   1005         try {
   1006             mService.discoverServiceByUuid(mClientIf, mDevice.getAddress(), new ParcelUuid(uuid));
   1007         } catch (RemoteException e) {
   1008             Log.e(TAG, "", e);
   1009             return false;
   1010         }
   1011         return true;
   1012     }
   1013 
   1014     /**
   1015      * Returns a list of GATT services offered by the remote device.
   1016      *
   1017      * <p>This function requires that service discovery has been completed
   1018      * for the given device.
   1019      *
   1020      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
   1021      *
   1022      * @return List of services on the remote device. Returns an empty list if service discovery has
   1023      * not yet been performed.
   1024      */
   1025     public List<BluetoothGattService> getServices() {
   1026         List<BluetoothGattService> result =
   1027                 new ArrayList<BluetoothGattService>();
   1028 
   1029         for (BluetoothGattService service : mServices) {
   1030             if (service.getDevice().equals(mDevice)) {
   1031                 result.add(service);
   1032             }
   1033         }
   1034 
   1035         return result;
   1036     }
   1037 
   1038     /**
   1039      * Returns a {@link BluetoothGattService}, if the requested UUID is
   1040      * supported by the remote device.
   1041      *
   1042      * <p>This function requires that service discovery has been completed
   1043      * for the given device.
   1044      *
   1045      * <p>If multiple instances of the same service (as identified by UUID)
   1046      * exist, the first instance of the service is returned.
   1047      *
   1048      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
   1049      *
   1050      * @param uuid UUID of the requested service
   1051      * @return BluetoothGattService if supported, or null if the requested service is not offered by
   1052      * the remote device.
   1053      */
   1054     public BluetoothGattService getService(UUID uuid) {
   1055         for (BluetoothGattService service : mServices) {
   1056             if (service.getDevice().equals(mDevice) && service.getUuid().equals(uuid)) {
   1057                 return service;
   1058             }
   1059         }
   1060 
   1061         return null;
   1062     }
   1063 
   1064     /**
   1065      * Reads the requested characteristic from the associated remote device.
   1066      *
   1067      * <p>This is an asynchronous operation. The result of the read operation
   1068      * is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
   1069      * callback.
   1070      *
   1071      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
   1072      *
   1073      * @param characteristic Characteristic to read from the remote device
   1074      * @return true, if the read operation was initiated successfully
   1075      */
   1076     public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
   1077         if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) == 0) {
   1078             return false;
   1079         }
   1080 
   1081         if (VDBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid());
   1082         if (mService == null || mClientIf == 0) return false;
   1083 
   1084         BluetoothGattService service = characteristic.getService();
   1085         if (service == null) return false;
   1086 
   1087         BluetoothDevice device = service.getDevice();
   1088         if (device == null) return false;
   1089 
   1090         synchronized (mDeviceBusy) {
   1091             if (mDeviceBusy) return false;
   1092             mDeviceBusy = true;
   1093         }
   1094 
   1095         try {
   1096             mService.readCharacteristic(mClientIf, device.getAddress(),
   1097                     characteristic.getInstanceId(), AUTHENTICATION_NONE);
   1098         } catch (RemoteException e) {
   1099             Log.e(TAG, "", e);
   1100             mDeviceBusy = false;
   1101             return false;
   1102         }
   1103 
   1104         return true;
   1105     }
   1106 
   1107     /**
   1108      * Reads the characteristic using its UUID from the associated remote device.
   1109      *
   1110      * <p>This is an asynchronous operation. The result of the read operation
   1111      * is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
   1112      * callback.
   1113      *
   1114      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
   1115      *
   1116      * @param uuid UUID of characteristic to read from the remote device
   1117      * @return true, if the read operation was initiated successfully
   1118      * @hide
   1119      */
   1120     public boolean readUsingCharacteristicUuid(UUID uuid, int startHandle, int endHandle) {
   1121         if (VDBG) Log.d(TAG, "readUsingCharacteristicUuid() - uuid: " + uuid);
   1122         if (mService == null || mClientIf == 0) return false;
   1123 
   1124         synchronized (mDeviceBusy) {
   1125             if (mDeviceBusy) return false;
   1126             mDeviceBusy = true;
   1127         }
   1128 
   1129         try {
   1130             mService.readUsingCharacteristicUuid(mClientIf, mDevice.getAddress(),
   1131                     new ParcelUuid(uuid), startHandle, endHandle, AUTHENTICATION_NONE);
   1132         } catch (RemoteException e) {
   1133             Log.e(TAG, "", e);
   1134             mDeviceBusy = false;
   1135             return false;
   1136         }
   1137 
   1138         return true;
   1139     }
   1140 
   1141 
   1142     /**
   1143      * Writes a given characteristic and its values to the associated remote device.
   1144      *
   1145      * <p>Once the write operation has been completed, the
   1146      * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked,
   1147      * reporting the result of the operation.
   1148      *
   1149      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
   1150      *
   1151      * @param characteristic Characteristic to write on the remote device
   1152      * @return true, if the write operation was initiated successfully
   1153      */
   1154     public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
   1155         if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0
   1156                 && (characteristic.getProperties()
   1157                 & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) {
   1158             return false;
   1159         }
   1160 
   1161         if (VDBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid());
   1162         if (mService == null || mClientIf == 0 || characteristic.getValue() == null) return false;
   1163 
   1164         BluetoothGattService service = characteristic.getService();
   1165         if (service == null) return false;
   1166 
   1167         BluetoothDevice device = service.getDevice();
   1168         if (device == null) return false;
   1169 
   1170         synchronized (mDeviceBusy) {
   1171             if (mDeviceBusy) return false;
   1172             mDeviceBusy = true;
   1173         }
   1174 
   1175         try {
   1176             mService.writeCharacteristic(mClientIf, device.getAddress(),
   1177                     characteristic.getInstanceId(), characteristic.getWriteType(),
   1178                     AUTHENTICATION_NONE, characteristic.getValue());
   1179         } catch (RemoteException e) {
   1180             Log.e(TAG, "", e);
   1181             mDeviceBusy = false;
   1182             return false;
   1183         }
   1184 
   1185         return true;
   1186     }
   1187 
   1188     /**
   1189      * Reads the value for a given descriptor from the associated remote device.
   1190      *
   1191      * <p>Once the read operation has been completed, the
   1192      * {@link BluetoothGattCallback#onDescriptorRead} callback is
   1193      * triggered, signaling the result of the operation.
   1194      *
   1195      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
   1196      *
   1197      * @param descriptor Descriptor value to read from the remote device
   1198      * @return true, if the read operation was initiated successfully
   1199      */
   1200     public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
   1201         if (VDBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid());
   1202         if (mService == null || mClientIf == 0) return false;
   1203 
   1204         BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
   1205         if (characteristic == null) return false;
   1206 
   1207         BluetoothGattService service = characteristic.getService();
   1208         if (service == null) return false;
   1209 
   1210         BluetoothDevice device = service.getDevice();
   1211         if (device == null) return false;
   1212 
   1213         synchronized (mDeviceBusy) {
   1214             if (mDeviceBusy) return false;
   1215             mDeviceBusy = true;
   1216         }
   1217 
   1218         try {
   1219             mService.readDescriptor(mClientIf, device.getAddress(),
   1220                     descriptor.getInstanceId(), AUTHENTICATION_NONE);
   1221         } catch (RemoteException e) {
   1222             Log.e(TAG, "", e);
   1223             mDeviceBusy = false;
   1224             return false;
   1225         }
   1226 
   1227         return true;
   1228     }
   1229 
   1230     /**
   1231      * Write the value of a given descriptor to the associated remote device.
   1232      *
   1233      * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is
   1234      * triggered to report the result of the write operation.
   1235      *
   1236      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
   1237      *
   1238      * @param descriptor Descriptor to write to the associated remote device
   1239      * @return true, if the write operation was initiated successfully
   1240      */
   1241     public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
   1242         if (VDBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid());
   1243         if (mService == null || mClientIf == 0 || descriptor.getValue() == null) return false;
   1244 
   1245         BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
   1246         if (characteristic == null) return false;
   1247 
   1248         BluetoothGattService service = characteristic.getService();
   1249         if (service == null) return false;
   1250 
   1251         BluetoothDevice device = service.getDevice();
   1252         if (device == null) return false;
   1253 
   1254         synchronized (mDeviceBusy) {
   1255             if (mDeviceBusy) return false;
   1256             mDeviceBusy = true;
   1257         }
   1258 
   1259         try {
   1260             mService.writeDescriptor(mClientIf, device.getAddress(), descriptor.getInstanceId(),
   1261                     AUTHENTICATION_NONE, descriptor.getValue());
   1262         } catch (RemoteException e) {
   1263             Log.e(TAG, "", e);
   1264             mDeviceBusy = false;
   1265             return false;
   1266         }
   1267 
   1268         return true;
   1269     }
   1270 
   1271     /**
   1272      * Initiates a reliable write transaction for a given remote device.
   1273      *
   1274      * <p>Once a reliable write transaction has been initiated, all calls
   1275      * to {@link #writeCharacteristic} are sent to the remote device for
   1276      * verification and queued up for atomic execution. The application will
   1277      * receive an {@link BluetoothGattCallback#onCharacteristicWrite} callback
   1278      * in response to every {@link #writeCharacteristic} call and is responsible
   1279      * for verifying if the value has been transmitted accurately.
   1280      *
   1281      * <p>After all characteristics have been queued up and verified,
   1282      * {@link #executeReliableWrite} will execute all writes. If a characteristic
   1283      * was not written correctly, calling {@link #abortReliableWrite} will
   1284      * cancel the current transaction without commiting any values on the
   1285      * remote device.
   1286      *
   1287      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
   1288      *
   1289      * @return true, if the reliable write transaction has been initiated
   1290      */
   1291     public boolean beginReliableWrite() {
   1292         if (VDBG) Log.d(TAG, "beginReliableWrite() - device: " + mDevice.getAddress());
   1293         if (mService == null || mClientIf == 0) return false;
   1294 
   1295         try {
   1296             mService.beginReliableWrite(mClientIf, mDevice.getAddress());
   1297         } catch (RemoteException e) {
   1298             Log.e(TAG, "", e);
   1299             return false;
   1300         }
   1301 
   1302         return true;
   1303     }
   1304 
   1305     /**
   1306      * Executes a reliable write transaction for a given remote device.
   1307      *
   1308      * <p>This function will commit all queued up characteristic write
   1309      * operations for a given remote device.
   1310      *
   1311      * <p>A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is
   1312      * invoked to indicate whether the transaction has been executed correctly.
   1313      *
   1314      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
   1315      *
   1316      * @return true, if the request to execute the transaction has been sent
   1317      */
   1318     public boolean executeReliableWrite() {
   1319         if (VDBG) Log.d(TAG, "executeReliableWrite() - device: " + mDevice.getAddress());
   1320         if (mService == null || mClientIf == 0) return false;
   1321 
   1322         synchronized (mDeviceBusy) {
   1323             if (mDeviceBusy) return false;
   1324             mDeviceBusy = true;
   1325         }
   1326 
   1327         try {
   1328             mService.endReliableWrite(mClientIf, mDevice.getAddress(), true);
   1329         } catch (RemoteException e) {
   1330             Log.e(TAG, "", e);
   1331             mDeviceBusy = false;
   1332             return false;
   1333         }
   1334 
   1335         return true;
   1336     }
   1337 
   1338     /**
   1339      * Cancels a reliable write transaction for a given device.
   1340      *
   1341      * <p>Calling this function will discard all queued characteristic write
   1342      * operations for a given remote device.
   1343      *
   1344      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
   1345      */
   1346     public void abortReliableWrite() {
   1347         if (VDBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice.getAddress());
   1348         if (mService == null || mClientIf == 0) return;
   1349 
   1350         try {
   1351             mService.endReliableWrite(mClientIf, mDevice.getAddress(), false);
   1352         } catch (RemoteException e) {
   1353             Log.e(TAG, "", e);
   1354         }
   1355     }
   1356 
   1357     /**
   1358      * @deprecated Use {@link #abortReliableWrite()}
   1359      */
   1360     @Deprecated
   1361     public void abortReliableWrite(BluetoothDevice mDevice) {
   1362         abortReliableWrite();
   1363     }
   1364 
   1365     /**
   1366      * Enable or disable notifications/indications for a given characteristic.
   1367      *
   1368      * <p>Once notifications are enabled for a characteristic, a
   1369      * {@link BluetoothGattCallback#onCharacteristicChanged} callback will be
   1370      * triggered if the remote device indicates that the given characteristic
   1371      * has changed.
   1372      *
   1373      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
   1374      *
   1375      * @param characteristic The characteristic for which to enable notifications
   1376      * @param enable Set to true to enable notifications/indications
   1377      * @return true, if the requested notification status was set successfully
   1378      */
   1379     public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
   1380             boolean enable) {
   1381         if (DBG) {
   1382             Log.d(TAG, "setCharacteristicNotification() - uuid: " + characteristic.getUuid()
   1383                     + " enable: " + enable);
   1384         }
   1385         if (mService == null || mClientIf == 0) return false;
   1386 
   1387         BluetoothGattService service = characteristic.getService();
   1388         if (service == null) return false;
   1389 
   1390         BluetoothDevice device = service.getDevice();
   1391         if (device == null) return false;
   1392 
   1393         try {
   1394             mService.registerForNotification(mClientIf, device.getAddress(),
   1395                     characteristic.getInstanceId(), enable);
   1396         } catch (RemoteException e) {
   1397             Log.e(TAG, "", e);
   1398             return false;
   1399         }
   1400 
   1401         return true;
   1402     }
   1403 
   1404     /**
   1405      * Clears the internal cache and forces a refresh of the services from the
   1406      * remote device.
   1407      *
   1408      * @hide
   1409      */
   1410     public boolean refresh() {
   1411         if (DBG) Log.d(TAG, "refresh() - device: " + mDevice.getAddress());
   1412         if (mService == null || mClientIf == 0) return false;
   1413 
   1414         try {
   1415             mService.refreshDevice(mClientIf, mDevice.getAddress());
   1416         } catch (RemoteException e) {
   1417             Log.e(TAG, "", e);
   1418             return false;
   1419         }
   1420 
   1421         return true;
   1422     }
   1423 
   1424     /**
   1425      * Read the RSSI for a connected remote device.
   1426      *
   1427      * <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be
   1428      * invoked when the RSSI value has been read.
   1429      *
   1430      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
   1431      *
   1432      * @return true, if the RSSI value has been requested successfully
   1433      */
   1434     public boolean readRemoteRssi() {
   1435         if (DBG) Log.d(TAG, "readRssi() - device: " + mDevice.getAddress());
   1436         if (mService == null || mClientIf == 0) return false;
   1437 
   1438         try {
   1439             mService.readRemoteRssi(mClientIf, mDevice.getAddress());
   1440         } catch (RemoteException e) {
   1441             Log.e(TAG, "", e);
   1442             return false;
   1443         }
   1444 
   1445         return true;
   1446     }
   1447 
   1448     /**
   1449      * Request an MTU size used for a given connection.
   1450      *
   1451      * <p>When performing a write request operation (write without response),
   1452      * the data sent is truncated to the MTU size. This function may be used
   1453      * to request a larger MTU size to be able to send more data at once.
   1454      *
   1455      * <p>A {@link BluetoothGattCallback#onMtuChanged} callback will indicate
   1456      * whether this operation was successful.
   1457      *
   1458      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
   1459      *
   1460      * @return true, if the new MTU value has been requested successfully
   1461      */
   1462     public boolean requestMtu(int mtu) {
   1463         if (DBG) {
   1464             Log.d(TAG, "configureMTU() - device: " + mDevice.getAddress()
   1465                     + " mtu: " + mtu);
   1466         }
   1467         if (mService == null || mClientIf == 0) return false;
   1468 
   1469         try {
   1470             mService.configureMTU(mClientIf, mDevice.getAddress(), mtu);
   1471         } catch (RemoteException e) {
   1472             Log.e(TAG, "", e);
   1473             return false;
   1474         }
   1475 
   1476         return true;
   1477     }
   1478 
   1479     /**
   1480      * Request a connection parameter update.
   1481      *
   1482      * <p>This function will send a connection parameter update request to the
   1483      * remote device.
   1484      *
   1485      * @param connectionPriority Request a specific connection priority. Must be one of {@link
   1486      * BluetoothGatt#CONNECTION_PRIORITY_BALANCED}, {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH}
   1487      * or {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}.
   1488      * @throws IllegalArgumentException If the parameters are outside of their specified range.
   1489      */
   1490     public boolean requestConnectionPriority(int connectionPriority) {
   1491         if (connectionPriority < CONNECTION_PRIORITY_BALANCED
   1492                 || connectionPriority > CONNECTION_PRIORITY_LOW_POWER) {
   1493             throw new IllegalArgumentException("connectionPriority not within valid range");
   1494         }
   1495 
   1496         if (DBG) Log.d(TAG, "requestConnectionPriority() - params: " + connectionPriority);
   1497         if (mService == null || mClientIf == 0) return false;
   1498 
   1499         try {
   1500             mService.connectionParameterUpdate(mClientIf, mDevice.getAddress(), connectionPriority);
   1501         } catch (RemoteException e) {
   1502             Log.e(TAG, "", e);
   1503             return false;
   1504         }
   1505 
   1506         return true;
   1507     }
   1508 
   1509     /**
   1510      * Request an LE connection parameter update.
   1511      *
   1512      * <p>This function will send an LE connection parameters update request to the remote device.
   1513      *
   1514      * @return true, if the request is send to the Bluetooth stack.
   1515      * @hide
   1516      */
   1517     public boolean requestLeConnectionUpdate(int minConnectionInterval, int maxConnectionInterval,
   1518                                              int slaveLatency, int supervisionTimeout,
   1519                                              int minConnectionEventLen, int maxConnectionEventLen) {
   1520         if (DBG) {
   1521             Log.d(TAG, "requestLeConnectionUpdate() - min=(" + minConnectionInterval
   1522                         + ")" + (1.25 * minConnectionInterval)
   1523                         + "msec, max=(" + maxConnectionInterval + ")"
   1524                         + (1.25 * maxConnectionInterval) + "msec, latency=" + slaveLatency
   1525                         + ", timeout=" + supervisionTimeout + "msec" + ", min_ce="
   1526                         + minConnectionEventLen + ", max_ce=" + maxConnectionEventLen);
   1527         }
   1528         if (mService == null || mClientIf == 0) return false;
   1529 
   1530         try {
   1531             mService.leConnectionUpdate(mClientIf, mDevice.getAddress(),
   1532                                         minConnectionInterval, maxConnectionInterval,
   1533                                         slaveLatency, supervisionTimeout,
   1534                                         minConnectionEventLen, maxConnectionEventLen);
   1535         } catch (RemoteException e) {
   1536             Log.e(TAG, "", e);
   1537             return false;
   1538         }
   1539 
   1540         return true;
   1541     }
   1542 
   1543     /**
   1544      * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
   1545      * with {@link BluetoothProfile#GATT} as argument
   1546      *
   1547      * @throws UnsupportedOperationException
   1548      */
   1549     @Override
   1550     public int getConnectionState(BluetoothDevice device) {
   1551         throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
   1552     }
   1553 
   1554     /**
   1555      * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
   1556      * with {@link BluetoothProfile#GATT} as argument
   1557      *
   1558      * @throws UnsupportedOperationException
   1559      */
   1560     @Override
   1561     public List<BluetoothDevice> getConnectedDevices() {
   1562         throw new UnsupportedOperationException(
   1563                 "Use BluetoothManager#getConnectedDevices instead.");
   1564     }
   1565 
   1566     /**
   1567      * Not supported - please use
   1568      * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])}
   1569      * with {@link BluetoothProfile#GATT} as first argument
   1570      *
   1571      * @throws UnsupportedOperationException
   1572      */
   1573     @Override
   1574     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
   1575         throw new UnsupportedOperationException(
   1576                 "Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
   1577     }
   1578 }
   1579