• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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              * The MTU for a connection has changed
290              * @hide
291              */
292             public void onMtuChanged(String address, int mtu) {
293                 if (DBG) Log.d(TAG, "onMtuChanged() - "
294                     + "device=" + address + ", mtu=" + mtu);
295 
296                 BluetoothDevice device = mAdapter.getRemoteDevice(address);
297                 if (device == null) return;
298 
299                 try {
300                     mCallback.onMtuChanged(device, mtu);
301                 } catch (Exception ex) {
302                     Log.w(TAG, "Unhandled exception: " + ex);
303                 }
304             }
305         };
306 
307     /**
308      * Create a BluetoothGattServer proxy object.
309      */
BluetoothGattServer(Context context, IBluetoothGatt iGatt, int transport)310     /*package*/ BluetoothGattServer(Context context, IBluetoothGatt iGatt, int transport) {
311         mContext = context;
312         mService = iGatt;
313         mAdapter = BluetoothAdapter.getDefaultAdapter();
314         mCallback = null;
315         mServerIf = 0;
316         mTransport = transport;
317         mServices = new ArrayList<BluetoothGattService>();
318     }
319 
320     /**
321      * Close this GATT server instance.
322      *
323      * Application should call this method as early as possible after it is done with
324      * this GATT server.
325      */
close()326     public void close() {
327         if (DBG) Log.d(TAG, "close()");
328         unregisterCallback();
329     }
330 
331     /**
332      * Register an application callback to start using GattServer.
333      *
334      * <p>This is an asynchronous call. The callback is used to notify
335      * success or failure if the function returns true.
336      *
337      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
338      *
339      * @param callback GATT callback handler that will receive asynchronous
340      *                 callbacks.
341      * @return true, the callback will be called to notify success or failure,
342      *         false on immediate error
343      */
registerCallback(BluetoothGattServerCallback callback)344     /*package*/ boolean registerCallback(BluetoothGattServerCallback callback) {
345         if (DBG) Log.d(TAG, "registerCallback()");
346         if (mService == null) {
347             Log.e(TAG, "GATT service not available");
348             return false;
349         }
350         UUID uuid = UUID.randomUUID();
351         if (DBG) Log.d(TAG, "registerCallback() - UUID=" + uuid);
352 
353         synchronized(mServerIfLock) {
354             if (mCallback != null) {
355                 Log.e(TAG, "App can register callback only once");
356                 return false;
357             }
358 
359             mCallback = callback;
360             try {
361                 mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback);
362             } catch (RemoteException e) {
363                 Log.e(TAG,"",e);
364                 mCallback = null;
365                 return false;
366             }
367 
368             try {
369                 mServerIfLock.wait(CALLBACK_REG_TIMEOUT);
370             } catch (InterruptedException e) {
371                 Log.e(TAG, "" + e);
372                 mCallback = null;
373             }
374 
375             if (mServerIf == 0) {
376                 mCallback = null;
377                 return false;
378             } else {
379                 return true;
380             }
381         }
382     }
383 
384     /**
385      * Unregister the current application and callbacks.
386      */
unregisterCallback()387     private void unregisterCallback() {
388         if (DBG) Log.d(TAG, "unregisterCallback() - mServerIf=" + mServerIf);
389         if (mService == null || mServerIf == 0) return;
390 
391         try {
392             mCallback = null;
393             mService.unregisterServer(mServerIf);
394             mServerIf = 0;
395         } catch (RemoteException e) {
396             Log.e(TAG,"",e);
397         }
398     }
399 
400     /**
401      * Returns a service by UUID, instance and type.
402      * @hide
403      */
getService(UUID uuid, int instanceId, int type)404     /*package*/ BluetoothGattService getService(UUID uuid, int instanceId, int type) {
405         for(BluetoothGattService svc : mServices) {
406             if (svc.getType() == type &&
407                 svc.getInstanceId() == instanceId &&
408                 svc.getUuid().equals(uuid)) {
409                 return svc;
410             }
411         }
412         return null;
413     }
414 
415     /**
416      * Initiate a connection to a Bluetooth GATT capable device.
417      *
418      * <p>The connection may not be established right away, but will be
419      * completed when the remote device is available. A
420      * {@link BluetoothGattServerCallback#onConnectionStateChange} callback will be
421      * invoked when the connection state changes as a result of this function.
422      *
423      * <p>The autoConnect paramter determines whether to actively connect to
424      * the remote device, or rather passively scan and finalize the connection
425      * when the remote device is in range/available. Generally, the first ever
426      * connection to a device should be direct (autoConnect set to false) and
427      * subsequent connections to known devices should be invoked with the
428      * autoConnect parameter set to true.
429      *
430      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
431      *
432      * @param autoConnect Whether to directly connect to the remote device (false)
433      *                    or to automatically connect as soon as the remote
434      *                    device becomes available (true).
435      * @return true, if the connection attempt was initiated successfully
436      */
connect(BluetoothDevice device, boolean autoConnect)437     public boolean connect(BluetoothDevice device, boolean autoConnect) {
438         if (DBG) Log.d(TAG, "connect() - device: " + device.getAddress() + ", auto: " + autoConnect);
439         if (mService == null || mServerIf == 0) return false;
440 
441         try {
442             mService.serverConnect(mServerIf, device.getAddress(),
443                                autoConnect ? false : true,mTransport); // autoConnect is inverse of "isDirect"
444         } catch (RemoteException e) {
445             Log.e(TAG,"",e);
446             return false;
447         }
448 
449         return true;
450     }
451 
452     /**
453      * Disconnects an established connection, or cancels a connection attempt
454      * currently in progress.
455      *
456      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
457      *
458      * @param device Remote device
459      */
cancelConnection(BluetoothDevice device)460     public void cancelConnection(BluetoothDevice device) {
461         if (DBG) Log.d(TAG, "cancelConnection() - device: " + device.getAddress());
462         if (mService == null || mServerIf == 0) return;
463 
464         try {
465             mService.serverDisconnect(mServerIf, device.getAddress());
466         } catch (RemoteException e) {
467             Log.e(TAG,"",e);
468         }
469     }
470 
471     /**
472      * Send a response to a read or write request to a remote device.
473      *
474      * <p>This function must be invoked in when a remote read/write request
475      * is received by one of these callback methods:
476      *
477      * <ul>
478      *      <li>{@link BluetoothGattServerCallback#onCharacteristicReadRequest}
479      *      <li>{@link BluetoothGattServerCallback#onCharacteristicWriteRequest}
480      *      <li>{@link BluetoothGattServerCallback#onDescriptorReadRequest}
481      *      <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest}
482      * </ul>
483      *
484      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
485      *
486      * @param device The remote device to send this response to
487      * @param requestId The ID of the request that was received with the callback
488      * @param status The status of the request to be sent to the remote devices
489      * @param offset Value offset for partial read/write response
490      * @param value The value of the attribute that was read/written (optional)
491      */
sendResponse(BluetoothDevice device, int requestId, int status, int offset, byte[] value)492     public boolean sendResponse(BluetoothDevice device, int requestId,
493                                 int status, int offset, byte[] value) {
494         if (VDBG) Log.d(TAG, "sendResponse() - device: " + device.getAddress());
495         if (mService == null || mServerIf == 0) return false;
496 
497         try {
498             mService.sendResponse(mServerIf, device.getAddress(), requestId,
499                                   status, offset, value);
500         } catch (RemoteException e) {
501             Log.e(TAG,"",e);
502             return false;
503         }
504         return true;
505     }
506 
507     /**
508      * Send a notification or indication that a local characteristic has been
509      * updated.
510      *
511      * <p>A notification or indication is sent to the remote device to signal
512      * that the characteristic has been updated. This function should be invoked
513      * for every client that requests notifications/indications by writing
514      * to the "Client Configuration" descriptor for the given characteristic.
515      *
516      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
517      *
518      * @param device The remote device to receive the notification/indication
519      * @param characteristic The local characteristic that has been updated
520      * @param confirm true to request confirmation from the client (indication),
521      *                false to send a notification
522      * @throws IllegalArgumentException
523      * @return true, if the notification has been triggered successfully
524      */
notifyCharacteristicChanged(BluetoothDevice device, BluetoothGattCharacteristic characteristic, boolean confirm)525     public boolean notifyCharacteristicChanged(BluetoothDevice device,
526                     BluetoothGattCharacteristic characteristic, boolean confirm) {
527         if (VDBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress());
528         if (mService == null || mServerIf == 0) return false;
529 
530         BluetoothGattService service = characteristic.getService();
531         if (service == null) return false;
532 
533         if (characteristic.getValue() == null) {
534             throw new IllegalArgumentException("Chracteristic value is empty. Use "
535                     + "BluetoothGattCharacteristic#setvalue to update");
536         }
537 
538         try {
539             mService.sendNotification(mServerIf, device.getAddress(),
540                     service.getType(), service.getInstanceId(),
541                     new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
542                     new ParcelUuid(characteristic.getUuid()), confirm,
543                     characteristic.getValue());
544         } catch (RemoteException e) {
545             Log.e(TAG,"",e);
546             return false;
547         }
548 
549         return true;
550     }
551 
552     /**
553      * Add a service to the list of services to be hosted.
554      *
555      * <p>Once a service has been addded to the the list, the service and its
556      * included characteristics will be provided by the local device.
557      *
558      * <p>If the local device has already exposed services when this function
559      * is called, a service update notification will be sent to all clients.
560      *
561      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
562      *
563      * @param service Service to be added to the list of services provided
564      *                by this device.
565      * @return true, if the service has been added successfully
566      */
addService(BluetoothGattService service)567     public boolean addService(BluetoothGattService service) {
568         if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid());
569         if (mService == null || mServerIf == 0) return false;
570 
571         mServices.add(service);
572 
573         try {
574             mService.beginServiceDeclaration(mServerIf, service.getType(),
575                 service.getInstanceId(), service.getHandles(),
576                 new ParcelUuid(service.getUuid()), service.isAdvertisePreferred());
577 
578             List<BluetoothGattService> includedServices = service.getIncludedServices();
579             for (BluetoothGattService includedService : includedServices) {
580                 mService.addIncludedService(mServerIf,
581                     includedService.getType(),
582                     includedService.getInstanceId(),
583                     new ParcelUuid(includedService.getUuid()));
584             }
585 
586             List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
587             for (BluetoothGattCharacteristic characteristic : characteristics) {
588                 int permission = ((characteristic.getKeySize() - 7) << 12)
589                                     + characteristic.getPermissions();
590                 mService.addCharacteristic(mServerIf,
591                     new ParcelUuid(characteristic.getUuid()),
592                     characteristic.getProperties(), permission);
593 
594                 List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors();
595                 for (BluetoothGattDescriptor descriptor: descriptors) {
596                     permission = ((characteristic.getKeySize() - 7) << 12)
597                                         + descriptor.getPermissions();
598                     mService.addDescriptor(mServerIf,
599                         new ParcelUuid(descriptor.getUuid()), permission);
600                 }
601             }
602 
603             mService.endServiceDeclaration(mServerIf);
604         } catch (RemoteException e) {
605             Log.e(TAG,"",e);
606             return false;
607         }
608 
609         return true;
610     }
611 
612     /**
613      * Removes a service from the list of services to be provided.
614      *
615      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
616      *
617      * @param service Service to be removed.
618      * @return true, if the service has been removed
619      */
removeService(BluetoothGattService service)620     public boolean removeService(BluetoothGattService service) {
621         if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid());
622         if (mService == null || mServerIf == 0) return false;
623 
624         BluetoothGattService intService = getService(service.getUuid(),
625                                 service.getInstanceId(), service.getType());
626         if (intService == null) return false;
627 
628         try {
629             mService.removeService(mServerIf, service.getType(),
630                 service.getInstanceId(), new ParcelUuid(service.getUuid()));
631             mServices.remove(intService);
632         } catch (RemoteException e) {
633             Log.e(TAG,"",e);
634             return false;
635         }
636 
637         return true;
638     }
639 
640     /**
641      * Remove all services from the list of provided services.
642      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
643      */
clearServices()644     public void clearServices() {
645         if (DBG) Log.d(TAG, "clearServices()");
646         if (mService == null || mServerIf == 0) return;
647 
648         try {
649             mService.clearServices(mServerIf);
650             mServices.clear();
651         } catch (RemoteException e) {
652             Log.e(TAG,"",e);
653         }
654     }
655 
656     /**
657      * Returns a list of GATT services offered by this device.
658      *
659      * <p>An application must call {@link #addService} to add a serice to the
660      * list of services offered by this device.
661      *
662      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
663      *
664      * @return List of services. Returns an empty list
665      *         if no services have been added yet.
666      */
getServices()667     public List<BluetoothGattService> getServices() {
668         return mServices;
669     }
670 
671     /**
672      * Returns a {@link BluetoothGattService} from the list of services offered
673      * by this device.
674      *
675      * <p>If multiple instances of the same service (as identified by UUID)
676      * exist, the first instance of the service is returned.
677      *
678      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
679      *
680      * @param uuid UUID of the requested service
681      * @return BluetoothGattService if supported, or null if the requested
682      *         service is not offered by this device.
683      */
getService(UUID uuid)684     public BluetoothGattService getService(UUID uuid) {
685         for (BluetoothGattService service : mServices) {
686             if (service.getUuid().equals(uuid)) {
687                 return service;
688             }
689         }
690 
691         return null;
692     }
693 
694 
695     /**
696      * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
697      * with {@link BluetoothProfile#GATT} as argument
698      *
699      * @throws UnsupportedOperationException
700      */
701     @Override
getConnectionState(BluetoothDevice device)702     public int getConnectionState(BluetoothDevice device) {
703         throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
704     }
705 
706     /**
707      * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
708      * with {@link BluetoothProfile#GATT} as argument
709      *
710      * @throws UnsupportedOperationException
711      */
712     @Override
getConnectedDevices()713     public List<BluetoothDevice> getConnectedDevices() {
714         throw new UnsupportedOperationException
715             ("Use BluetoothManager#getConnectedDevices instead.");
716     }
717 
718     /**
719      * Not supported - please use
720      * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])}
721      * with {@link BluetoothProfile#GATT} as first argument
722      *
723      * @throws UnsupportedOperationException
724      */
725     @Override
getDevicesMatchingConnectionStates(int[] states)726     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
727         throw new UnsupportedOperationException
728             ("Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
729     }
730 }
731