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