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.BluetoothAdapter;
     20 import android.bluetooth.BluetoothDevice;
     21 import android.bluetooth.BluetoothProfile;
     22 import android.content.Context;
     23 import android.os.ParcelUuid;
     24 import android.os.RemoteException;
     25 import android.util.Log;
     26 
     27 import java.util.ArrayList;
     28 import java.util.List;
     29 import java.util.UUID;
     30 
     31 /**
     32  * Public API for the Bluetooth GATT Profile server role.
     33  *
     34  * <p>This class provides Bluetooth GATT server role functionality,
     35  * allowing applications to create Bluetooth Smart services and
     36  * characteristics.
     37  *
     38  * <p>BluetoothGattServer is a proxy object for controlling the Bluetooth Service
     39  * via IPC.  Use {@link BluetoothManager#openGattServer} to get an instance
     40  * of this class.
     41  */
     42 public final class BluetoothGattServer implements BluetoothProfile {
     43     private static final String TAG = "BluetoothGattServer";
     44     private static final boolean DBG = true;
     45     private static final boolean VDBG = false;
     46 
     47     private final Context mContext;
     48     private BluetoothAdapter mAdapter;
     49     private IBluetoothGatt mService;
     50     private BluetoothGattServerCallback mCallback;
     51 
     52     private Object mServerIfLock = new Object();
     53     private int mServerIf;
     54     private int mTransport;
     55     private List<BluetoothGattService> mServices;
     56 
     57     private static final int CALLBACK_REG_TIMEOUT = 10000;
     58 
     59     /**
     60      * Bluetooth GATT interface callbacks
     61      */
     62     private final IBluetoothGattServerCallback mBluetoothGattServerCallback =
     63         new IBluetoothGattServerCallback.Stub() {
     64             /**
     65              * Application interface registered - app is ready to go
     66              * @hide
     67              */
     68             public void onServerRegistered(int status, int serverIf) {
     69                 if (DBG) Log.d(TAG, "onServerRegistered() - status=" + status
     70                     + " serverIf=" + serverIf);
     71                 synchronized(mServerIfLock) {
     72                     if (mCallback != null) {
     73                         mServerIf = serverIf;
     74                         mServerIfLock.notify();
     75                     } else {
     76                         // registration timeout
     77                         Log.e(TAG, "onServerRegistered: mCallback is null");
     78                     }
     79                 }
     80             }
     81 
     82             /**
     83              * Callback reporting an LE scan result.
     84              * @hide
     85              */
     86             public void onScanResult(String address, int rssi, byte[] advData) {
     87                 if (VDBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
     88                 // no op
     89             }
     90 
     91             /**
     92              * Server connection state changed
     93              * @hide
     94              */
     95             public void onServerConnectionState(int status, int serverIf,
     96                                                 boolean connected, String address) {
     97                 if (DBG) Log.d(TAG, "onServerConnectionState() - status=" + status
     98                     + " serverIf=" + serverIf + " device=" + address);
     99                 try {
    100                     mCallback.onConnectionStateChange(mAdapter.getRemoteDevice(address), status,
    101                                                       connected ? BluetoothProfile.STATE_CONNECTED :
    102                                                       BluetoothProfile.STATE_DISCONNECTED);
    103                 } catch (Exception ex) {
    104                     Log.w(TAG, "Unhandled exception in callback", ex);
    105                 }
    106             }
    107 
    108             /**
    109              * Service has been added
    110              * @hide
    111              */
    112             public void onServiceAdded(int status, int srvcType,
    113                                        int srvcInstId, ParcelUuid srvcId) {
    114                 UUID srvcUuid = srvcId.getUuid();
    115                 if (DBG) Log.d(TAG, "onServiceAdded() - service=" + srvcUuid
    116                     + "status=" + status);
    117 
    118                 BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
    119                 if (service == null) return;
    120 
    121                 try {
    122                     mCallback.onServiceAdded((int)status, service);
    123                 } catch (Exception ex) {
    124                     Log.w(TAG, "Unhandled exception in callback", ex);
    125                 }
    126             }
    127 
    128             /**
    129              * Remote client characteristic read request.
    130              * @hide
    131              */
    132             public void onCharacteristicReadRequest(String address, int transId,
    133                             int offset, boolean isLong, int srvcType, int srvcInstId,
    134                             ParcelUuid srvcId, int charInstId, ParcelUuid charId) {
    135                 UUID srvcUuid = srvcId.getUuid();
    136                 UUID charUuid = charId.getUuid();
    137                 if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - "
    138                     + "service=" + srvcUuid + ", characteristic=" + charUuid);
    139 
    140                 BluetoothDevice device = mAdapter.getRemoteDevice(address);
    141                 BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
    142                 if (service == null) return;
    143 
    144                 BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
    145                 if (characteristic == null) return;
    146 
    147                 try {
    148                     mCallback.onCharacteristicReadRequest(device, transId, offset, characteristic);
    149                 } catch (Exception ex) {
    150                     Log.w(TAG, "Unhandled exception in callback", ex);
    151                 }
    152             }
    153 
    154             /**
    155              * Remote client descriptor read request.
    156              * @hide
    157              */
    158             public void onDescriptorReadRequest(String address, int transId,
    159                             int offset, boolean isLong, int srvcType, int srvcInstId,
    160                             ParcelUuid srvcId, int charInstId, ParcelUuid charId,
    161                             ParcelUuid descrId) {
    162                 UUID srvcUuid = srvcId.getUuid();
    163                 UUID charUuid = charId.getUuid();
    164                 UUID descrUuid = descrId.getUuid();
    165                 if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - "
    166                     + "service=" + srvcUuid + ", characteristic=" + charUuid
    167                     + "descriptor=" + descrUuid);
    168 
    169                 BluetoothDevice device = mAdapter.getRemoteDevice(address);
    170                 BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
    171                 if (service == null) return;
    172 
    173                 BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
    174                 if (characteristic == null) return;
    175 
    176                 BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descrUuid);
    177                 if (descriptor == null) return;
    178 
    179                 try {
    180                     mCallback.onDescriptorReadRequest(device, transId, offset, descriptor);
    181                 } catch (Exception ex) {
    182                     Log.w(TAG, "Unhandled exception in callback", ex);
    183                 }
    184             }
    185 
    186             /**
    187              * Remote client characteristic write request.
    188              * @hide
    189              */
    190             public void onCharacteristicWriteRequest(String address, int transId,
    191                             int offset, int length, boolean isPrep, boolean needRsp,
    192                             int srvcType, int srvcInstId, ParcelUuid srvcId,
    193                             int charInstId, ParcelUuid charId, byte[] value) {
    194                 UUID srvcUuid = srvcId.getUuid();
    195                 UUID charUuid = charId.getUuid();
    196                 if (VDBG) Log.d(TAG, "onCharacteristicWriteRequest() - "
    197                     + "service=" + srvcUuid + ", characteristic=" + charUuid);
    198 
    199                 BluetoothDevice device = mAdapter.getRemoteDevice(address);
    200                 BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
    201                 if (service == null) return;
    202 
    203                 BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
    204                 if (characteristic == null) return;
    205 
    206                 try {
    207                     mCallback.onCharacteristicWriteRequest(device, transId, characteristic,
    208                                                            isPrep, needRsp, offset, value);
    209                 } catch (Exception ex) {
    210                     Log.w(TAG, "Unhandled exception in callback", ex);
    211                 }
    212 
    213             }
    214 
    215             /**
    216              * Remote client descriptor write request.
    217              * @hide
    218              */
    219             public void onDescriptorWriteRequest(String address, int transId,
    220                             int offset, int length, boolean isPrep, boolean needRsp,
    221                             int srvcType, int srvcInstId, ParcelUuid srvcId,
    222                             int charInstId, ParcelUuid charId, ParcelUuid descrId,
    223                             byte[] value) {
    224                 UUID srvcUuid = srvcId.getUuid();
    225                 UUID charUuid = charId.getUuid();
    226                 UUID descrUuid = descrId.getUuid();
    227                 if (VDBG) Log.d(TAG, "onDescriptorWriteRequest() - "
    228                     + "service=" + srvcUuid + ", characteristic=" + charUuid
    229                     + "descriptor=" + descrUuid);
    230 
    231                 BluetoothDevice device = mAdapter.getRemoteDevice(address);
    232 
    233                 BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
    234                 if (service == null) return;
    235 
    236                 BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
    237                 if (characteristic == null) return;
    238 
    239                 BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descrUuid);
    240                 if (descriptor == null) return;
    241 
    242                 try {
    243                     mCallback.onDescriptorWriteRequest(device, transId, descriptor,
    244                                                        isPrep, needRsp, offset, value);
    245                 } catch (Exception ex) {
    246                     Log.w(TAG, "Unhandled exception in callback", ex);
    247                 }
    248             }
    249 
    250             /**
    251              * Execute pending writes.
    252              * @hide
    253              */
    254             public void onExecuteWrite(String address, int transId,
    255                                        boolean execWrite) {
    256                 if (DBG) Log.d(TAG, "onExecuteWrite() - "
    257                     + "device=" + address + ", transId=" + transId
    258                     + "execWrite=" + execWrite);
    259 
    260                 BluetoothDevice device = mAdapter.getRemoteDevice(address);
    261                 if (device == null) return;
    262 
    263                 try {
    264                     mCallback.onExecuteWrite(device, transId, execWrite);
    265                 } catch (Exception ex) {
    266                     Log.w(TAG, "Unhandled exception in callback", ex);
    267                 }
    268             }
    269 
    270             /**
    271              * A notification/indication has been sent.
    272              * @hide
    273              */
    274             public void onNotificationSent(String address, int status) {
    275                 if (VDBG) Log.d(TAG, "onNotificationSent() - "
    276                     + "device=" + address + ", status=" + status);
    277 
    278                 BluetoothDevice device = mAdapter.getRemoteDevice(address);
    279                 if (device == null) return;
    280 
    281                 try {
    282                     mCallback.onNotificationSent(device, status);
    283                 } catch (Exception ex) {
    284                     Log.w(TAG, "Unhandled exception: " + ex);
    285                 }
    286             }
    287         };
    288 
    289     /**
    290      * Create a BluetoothGattServer proxy object.
    291      */
    292     /*package*/ BluetoothGattServer(Context context, IBluetoothGatt iGatt, int transport) {
    293         mContext = context;
    294         mService = iGatt;
    295         mAdapter = BluetoothAdapter.getDefaultAdapter();
    296         mCallback = null;
    297         mServerIf = 0;
    298         mTransport = transport;
    299         mServices = new ArrayList<BluetoothGattService>();
    300     }
    301 
    302     /**
    303      * Close this GATT server instance.
    304      *
    305      * Application should call this method as early as possible after it is done with
    306      * this GATT server.
    307      */
    308     public void close() {
    309         if (DBG) Log.d(TAG, "close()");
    310         unregisterCallback();
    311     }
    312 
    313     /**
    314      * Register an application callback to start using GattServer.
    315      *
    316      * <p>This is an asynchronous call. The callback is used to notify
    317      * success or failure if the function returns true.
    318      *
    319      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    320      *
    321      * @param callback GATT callback handler that will receive asynchronous
    322      *                 callbacks.
    323      * @return true, the callback will be called to notify success or failure,
    324      *         false on immediate error
    325      */
    326     /*package*/ boolean registerCallback(BluetoothGattServerCallback callback) {
    327         if (DBG) Log.d(TAG, "registerCallback()");
    328         if (mService == null) {
    329             Log.e(TAG, "GATT service not available");
    330             return false;
    331         }
    332         UUID uuid = UUID.randomUUID();
    333         if (DBG) Log.d(TAG, "registerCallback() - UUID=" + uuid);
    334 
    335         synchronized(mServerIfLock) {
    336             if (mCallback != null) {
    337                 Log.e(TAG, "App can register callback only once");
    338                 return false;
    339             }
    340 
    341             mCallback = callback;
    342             try {
    343                 mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback);
    344             } catch (RemoteException e) {
    345                 Log.e(TAG,"",e);
    346                 mCallback = null;
    347                 return false;
    348             }
    349 
    350             try {
    351                 mServerIfLock.wait(CALLBACK_REG_TIMEOUT);
    352             } catch (InterruptedException e) {
    353                 Log.e(TAG, "" + e);
    354                 mCallback = null;
    355             }
    356 
    357             if (mServerIf == 0) {
    358                 mCallback = null;
    359                 return false;
    360             } else {
    361                 return true;
    362             }
    363         }
    364     }
    365 
    366     /**
    367      * Unregister the current application and callbacks.
    368      */
    369     private void unregisterCallback() {
    370         if (DBG) Log.d(TAG, "unregisterCallback() - mServerIf=" + mServerIf);
    371         if (mService == null || mServerIf == 0) return;
    372 
    373         try {
    374             mCallback = null;
    375             mService.unregisterServer(mServerIf);
    376             mServerIf = 0;
    377         } catch (RemoteException e) {
    378             Log.e(TAG,"",e);
    379         }
    380     }
    381 
    382     /**
    383      * Returns a service by UUID, instance and type.
    384      * @hide
    385      */
    386     /*package*/ BluetoothGattService getService(UUID uuid, int instanceId, int type) {
    387         for(BluetoothGattService svc : mServices) {
    388             if (svc.getType() == type &&
    389                 svc.getInstanceId() == instanceId &&
    390                 svc.getUuid().equals(uuid)) {
    391                 return svc;
    392             }
    393         }
    394         return null;
    395     }
    396 
    397     /**
    398      * Initiate a connection to a Bluetooth GATT capable device.
    399      *
    400      * <p>The connection may not be established right away, but will be
    401      * completed when the remote device is available. A
    402      * {@link BluetoothGattServerCallback#onConnectionStateChange} callback will be
    403      * invoked when the connection state changes as a result of this function.
    404      *
    405      * <p>The autoConnect paramter determines whether to actively connect to
    406      * the remote device, or rather passively scan and finalize the connection
    407      * when the remote device is in range/available. Generally, the first ever
    408      * connection to a device should be direct (autoConnect set to false) and
    409      * subsequent connections to known devices should be invoked with the
    410      * autoConnect parameter set to true.
    411      *
    412      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    413      *
    414      * @param autoConnect Whether to directly connect to the remote device (false)
    415      *                    or to automatically connect as soon as the remote
    416      *                    device becomes available (true).
    417      * @return true, if the connection attempt was initiated successfully
    418      */
    419     public boolean connect(BluetoothDevice device, boolean autoConnect) {
    420         if (DBG) Log.d(TAG, "connect() - device: " + device.getAddress() + ", auto: " + autoConnect);
    421         if (mService == null || mServerIf == 0) return false;
    422 
    423         try {
    424             mService.serverConnect(mServerIf, device.getAddress(),
    425                                autoConnect ? false : true,mTransport); // autoConnect is inverse of "isDirect"
    426         } catch (RemoteException e) {
    427             Log.e(TAG,"",e);
    428             return false;
    429         }
    430 
    431         return true;
    432     }
    433 
    434     /**
    435      * Disconnects an established connection, or cancels a connection attempt
    436      * currently in progress.
    437      *
    438      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    439      *
    440      * @param device Remote device
    441      */
    442     public void cancelConnection(BluetoothDevice device) {
    443         if (DBG) Log.d(TAG, "cancelConnection() - device: " + device.getAddress());
    444         if (mService == null || mServerIf == 0) return;
    445 
    446         try {
    447             mService.serverDisconnect(mServerIf, device.getAddress());
    448         } catch (RemoteException e) {
    449             Log.e(TAG,"",e);
    450         }
    451     }
    452 
    453     /**
    454      * Send a response to a read or write request to a remote device.
    455      *
    456      * <p>This function must be invoked in when a remote read/write request
    457      * is received by one of these callback methods:
    458      *
    459      * <ul>
    460      *      <li>{@link BluetoothGattServerCallback#onCharacteristicReadRequest}
    461      *      <li>{@link BluetoothGattServerCallback#onCharacteristicWriteRequest}
    462      *      <li>{@link BluetoothGattServerCallback#onDescriptorReadRequest}
    463      *      <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest}
    464      * </ul>
    465      *
    466      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    467      *
    468      * @param device The remote device to send this response to
    469      * @param requestId The ID of the request that was received with the callback
    470      * @param status The status of the request to be sent to the remote devices
    471      * @param offset Value offset for partial read/write response
    472      * @param value The value of the attribute that was read/written (optional)
    473      */
    474     public boolean sendResponse(BluetoothDevice device, int requestId,
    475                                 int status, int offset, byte[] value) {
    476         if (VDBG) Log.d(TAG, "sendResponse() - device: " + device.getAddress());
    477         if (mService == null || mServerIf == 0) return false;
    478 
    479         try {
    480             mService.sendResponse(mServerIf, device.getAddress(), requestId,
    481                                   status, offset, value);
    482         } catch (RemoteException e) {
    483             Log.e(TAG,"",e);
    484             return false;
    485         }
    486         return true;
    487     }
    488 
    489     /**
    490      * Send a notification or indication that a local characteristic has been
    491      * updated.
    492      *
    493      * <p>A notification or indication is sent to the remote device to signal
    494      * that the characteristic has been updated. This function should be invoked
    495      * for every client that requests notifications/indications by writing
    496      * to the "Client Configuration" descriptor for the given characteristic.
    497      *
    498      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    499      *
    500      * @param device The remote device to receive the notification/indication
    501      * @param characteristic The local characteristic that has been updated
    502      * @param confirm true to request confirmation from the client (indication),
    503      *                false to send a notification
    504      * @throws IllegalArgumentException
    505      * @return true, if the notification has been triggered successfully
    506      */
    507     public boolean notifyCharacteristicChanged(BluetoothDevice device,
    508                     BluetoothGattCharacteristic characteristic, boolean confirm) {
    509         if (VDBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress());
    510         if (mService == null || mServerIf == 0) return false;
    511 
    512         BluetoothGattService service = characteristic.getService();
    513         if (service == null) return false;
    514 
    515         if (characteristic.getValue() == null) {
    516             throw new IllegalArgumentException("Chracteristic value is empty. Use "
    517                     + "BluetoothGattCharacteristic#setvalue to update");
    518         }
    519 
    520         try {
    521             mService.sendNotification(mServerIf, device.getAddress(),
    522                     service.getType(), service.getInstanceId(),
    523                     new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
    524                     new ParcelUuid(characteristic.getUuid()), confirm,
    525                     characteristic.getValue());
    526         } catch (RemoteException e) {
    527             Log.e(TAG,"",e);
    528             return false;
    529         }
    530 
    531         return true;
    532     }
    533 
    534     /**
    535      * Add a service to the list of services to be hosted.
    536      *
    537      * <p>Once a service has been addded to the the list, the service and its
    538      * included characteristics will be provided by the local device.
    539      *
    540      * <p>If the local device has already exposed services when this function
    541      * is called, a service update notification will be sent to all clients.
    542      *
    543      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    544      *
    545      * @param service Service to be added to the list of services provided
    546      *                by this device.
    547      * @return true, if the service has been added successfully
    548      */
    549     public boolean addService(BluetoothGattService service) {
    550         if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid());
    551         if (mService == null || mServerIf == 0) return false;
    552 
    553         mServices.add(service);
    554 
    555         try {
    556             mService.beginServiceDeclaration(mServerIf, service.getType(),
    557                 service.getInstanceId(), service.getHandles(),
    558                 new ParcelUuid(service.getUuid()), service.isAdvertisePreferred());
    559 
    560             List<BluetoothGattService> includedServices = service.getIncludedServices();
    561             for (BluetoothGattService includedService : includedServices) {
    562                 mService.addIncludedService(mServerIf,
    563                     includedService.getType(),
    564                     includedService.getInstanceId(),
    565                     new ParcelUuid(includedService.getUuid()));
    566             }
    567 
    568             List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
    569             for (BluetoothGattCharacteristic characteristic : characteristics) {
    570                 int permission = ((characteristic.getKeySize() - 7) << 12)
    571                                     + characteristic.getPermissions();
    572                 mService.addCharacteristic(mServerIf,
    573                     new ParcelUuid(characteristic.getUuid()),
    574                     characteristic.getProperties(), permission);
    575 
    576                 List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors();
    577                 for (BluetoothGattDescriptor descriptor: descriptors) {
    578                     permission = ((characteristic.getKeySize() - 7) << 12)
    579                                         + descriptor.getPermissions();
    580                     mService.addDescriptor(mServerIf,
    581                         new ParcelUuid(descriptor.getUuid()), permission);
    582                 }
    583             }
    584 
    585             mService.endServiceDeclaration(mServerIf);
    586         } catch (RemoteException e) {
    587             Log.e(TAG,"",e);
    588             return false;
    589         }
    590 
    591         return true;
    592     }
    593 
    594     /**
    595      * Removes a service from the list of services to be provided.
    596      *
    597      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    598      *
    599      * @param service Service to be removed.
    600      * @return true, if the service has been removed
    601      */
    602     public boolean removeService(BluetoothGattService service) {
    603         if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid());
    604         if (mService == null || mServerIf == 0) return false;
    605 
    606         BluetoothGattService intService = getService(service.getUuid(),
    607                                 service.getInstanceId(), service.getType());
    608         if (intService == null) return false;
    609 
    610         try {
    611             mService.removeService(mServerIf, service.getType(),
    612                 service.getInstanceId(), new ParcelUuid(service.getUuid()));
    613             mServices.remove(intService);
    614         } catch (RemoteException e) {
    615             Log.e(TAG,"",e);
    616             return false;
    617         }
    618 
    619         return true;
    620     }
    621 
    622     /**
    623      * Remove all services from the list of provided services.
    624      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    625      */
    626     public void clearServices() {
    627         if (DBG) Log.d(TAG, "clearServices()");
    628         if (mService == null || mServerIf == 0) return;
    629 
    630         try {
    631             mService.clearServices(mServerIf);
    632             mServices.clear();
    633         } catch (RemoteException e) {
    634             Log.e(TAG,"",e);
    635         }
    636     }
    637 
    638     /**
    639      * Returns a list of GATT services offered by this device.
    640      *
    641      * <p>An application must call {@link #addService} to add a serice to the
    642      * list of services offered by this device.
    643      *
    644      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    645      *
    646      * @return List of services. Returns an empty list
    647      *         if no services have been added yet.
    648      */
    649     public List<BluetoothGattService> getServices() {
    650         return mServices;
    651     }
    652 
    653     /**
    654      * Returns a {@link BluetoothGattService} from the list of services offered
    655      * by this device.
    656      *
    657      * <p>If multiple instances of the same service (as identified by UUID)
    658      * exist, the first instance of the service is returned.
    659      *
    660      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    661      *
    662      * @param uuid UUID of the requested service
    663      * @return BluetoothGattService if supported, or null if the requested
    664      *         service is not offered by this device.
    665      */
    666     public BluetoothGattService getService(UUID uuid) {
    667         for (BluetoothGattService service : mServices) {
    668             if (service.getUuid().equals(uuid)) {
    669                 return service;
    670             }
    671         }
    672 
    673         return null;
    674     }
    675 
    676 
    677     /**
    678      * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
    679      * with {@link BluetoothProfile#GATT} as argument
    680      *
    681      * @throws UnsupportedOperationException
    682      */
    683     @Override
    684     public int getConnectionState(BluetoothDevice device) {
    685         throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
    686     }
    687 
    688     /**
    689      * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
    690      * with {@link BluetoothProfile#GATT} as argument
    691      *
    692      * @throws UnsupportedOperationException
    693      */
    694     @Override
    695     public List<BluetoothDevice> getConnectedDevices() {
    696         throw new UnsupportedOperationException
    697             ("Use BluetoothManager#getConnectedDevices instead.");
    698     }
    699 
    700     /**
    701      * Not supported - please use
    702      * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])}
    703      * with {@link BluetoothProfile#GATT} as first argument
    704      *
    705      * @throws UnsupportedOperationException
    706      */
    707     @Override
    708     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    709         throw new UnsupportedOperationException
    710             ("Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
    711     }
    712 }
    713