1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.bluetooth;
18 
19 import android.os.ParcelUuid;
20 import android.os.RemoteException;
21 import android.util.Log;
22 
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.UUID;
26 
27 /**
28  * Public API for the Bluetooth GATT Profile server role.
29  *
30  * <p>This class provides Bluetooth GATT server role functionality,
31  * allowing applications to create Bluetooth Smart services and
32  * characteristics.
33  *
34  * <p>BluetoothGattServer is a proxy object for controlling the Bluetooth Service
35  * via IPC.  Use {@link BluetoothManager#openGattServer} to get an instance
36  * of this class.
37  */
38 public final class BluetoothGattServer implements BluetoothProfile {
39     private static final String TAG = "BluetoothGattServer";
40     private static final boolean DBG = true;
41     private static final boolean VDBG = false;
42 
43     private BluetoothAdapter mAdapter;
44     private IBluetoothGatt mService;
45     private BluetoothGattServerCallback mCallback;
46 
47     private Object mServerIfLock = new Object();
48     private int mServerIf;
49     private int mTransport;
50     private BluetoothGattService mPendingService;
51     private List<BluetoothGattService> mServices;
52 
53     private static final int CALLBACK_REG_TIMEOUT = 10000;
54 
55     /**
56      * Bluetooth GATT interface callbacks
57      */
58     private final IBluetoothGattServerCallback mBluetoothGattServerCallback =
59             new IBluetoothGattServerCallback.Stub() {
60                 /**
61                  * Application interface registered - app is ready to go
62                  * @hide
63                  */
64                 @Override
65                 public void onServerRegistered(int status, int serverIf) {
66                     if (DBG) {
67                         Log.d(TAG, "onServerRegistered() - status=" + status
68                                 + " serverIf=" + serverIf);
69                     }
70                     synchronized (mServerIfLock) {
71                         if (mCallback != null) {
72                             mServerIf = serverIf;
73                             mServerIfLock.notify();
74                         } else {
75                             // registration timeout
76                             Log.e(TAG, "onServerRegistered: mCallback is null");
77                         }
78                     }
79                 }
80 
81                 /**
82                  * Server connection state changed
83                  * @hide
84                  */
85                 @Override
86                 public void onServerConnectionState(int status, int serverIf,
87                         boolean connected, String address) {
88                     if (DBG) {
89                         Log.d(TAG, "onServerConnectionState() - status=" + status
90                                 + " serverIf=" + serverIf + " device=" + address);
91                     }
92                     try {
93                         mCallback.onConnectionStateChange(mAdapter.getRemoteDevice(address), status,
94                                 connected ? BluetoothProfile.STATE_CONNECTED :
95                                         BluetoothProfile.STATE_DISCONNECTED);
96                     } catch (Exception ex) {
97                         Log.w(TAG, "Unhandled exception in callback", ex);
98                     }
99                 }
100 
101                 /**
102                  * Service has been added
103                  * @hide
104                  */
105                 @Override
106                 public void onServiceAdded(int status, BluetoothGattService service) {
107                     if (DBG) {
108                         Log.d(TAG, "onServiceAdded() - handle=" + service.getInstanceId()
109                                 + " uuid=" + service.getUuid() + " status=" + status);
110                     }
111 
112                     if (mPendingService == null) {
113                         return;
114                     }
115 
116                     BluetoothGattService tmp = mPendingService;
117                     mPendingService = null;
118 
119                     // Rewrite newly assigned handles to existing service.
120                     tmp.setInstanceId(service.getInstanceId());
121                     List<BluetoothGattCharacteristic> temp_chars = tmp.getCharacteristics();
122                     List<BluetoothGattCharacteristic> svc_chars = service.getCharacteristics();
123                     for (int i = 0; i < svc_chars.size(); i++) {
124                         BluetoothGattCharacteristic temp_char = temp_chars.get(i);
125                         BluetoothGattCharacteristic svc_char = svc_chars.get(i);
126 
127                         temp_char.setInstanceId(svc_char.getInstanceId());
128 
129                         List<BluetoothGattDescriptor> temp_descs = temp_char.getDescriptors();
130                         List<BluetoothGattDescriptor> svc_descs = svc_char.getDescriptors();
131                         for (int j = 0; j < svc_descs.size(); j++) {
132                             temp_descs.get(j).setInstanceId(svc_descs.get(j).getInstanceId());
133                         }
134                     }
135 
136                     mServices.add(tmp);
137 
138                     try {
139                         mCallback.onServiceAdded((int) status, tmp);
140                     } catch (Exception ex) {
141                         Log.w(TAG, "Unhandled exception in callback", ex);
142                     }
143                 }
144 
145                 /**
146                  * Remote client characteristic read request.
147                  * @hide
148                  */
149                 @Override
150                 public void onCharacteristicReadRequest(String address, int transId,
151                         int offset, boolean isLong, int handle) {
152                     if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - handle=" + handle);
153 
154                     BluetoothDevice device = mAdapter.getRemoteDevice(address);
155                     BluetoothGattCharacteristic characteristic = getCharacteristicByHandle(handle);
156                     if (characteristic == null) {
157                         Log.w(TAG, "onCharacteristicReadRequest() no char for handle " + handle);
158                         return;
159                     }
160 
161                     try {
162                         mCallback.onCharacteristicReadRequest(device, transId, offset,
163                                 characteristic);
164                     } catch (Exception ex) {
165                         Log.w(TAG, "Unhandled exception in callback", ex);
166                     }
167                 }
168 
169                 /**
170                  * Remote client descriptor read request.
171                  * @hide
172                  */
173                 @Override
174                 public void onDescriptorReadRequest(String address, int transId,
175                         int offset, boolean isLong, int handle) {
176                     if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - handle=" + handle);
177 
178                     BluetoothDevice device = mAdapter.getRemoteDevice(address);
179                     BluetoothGattDescriptor descriptor = getDescriptorByHandle(handle);
180                     if (descriptor == null) {
181                         Log.w(TAG, "onDescriptorReadRequest() no desc for handle " + handle);
182                         return;
183                     }
184 
185                     try {
186                         mCallback.onDescriptorReadRequest(device, transId, offset, descriptor);
187                     } catch (Exception ex) {
188                         Log.w(TAG, "Unhandled exception in callback", ex);
189                     }
190                 }
191 
192                 /**
193                  * Remote client characteristic write request.
194                  * @hide
195                  */
196                 @Override
197                 public void onCharacteristicWriteRequest(String address, int transId,
198                         int offset, int length, boolean isPrep, boolean needRsp,
199                         int handle, byte[] value) {
200                     if (VDBG) Log.d(TAG, "onCharacteristicWriteRequest() - handle=" + handle);
201 
202                     BluetoothDevice device = mAdapter.getRemoteDevice(address);
203                     BluetoothGattCharacteristic characteristic = getCharacteristicByHandle(handle);
204                     if (characteristic == null) {
205                         Log.w(TAG, "onCharacteristicWriteRequest() no char for handle " + handle);
206                         return;
207                     }
208 
209                     try {
210                         mCallback.onCharacteristicWriteRequest(device, transId, characteristic,
211                                 isPrep, needRsp, offset, value);
212                     } catch (Exception ex) {
213                         Log.w(TAG, "Unhandled exception in callback", ex);
214                     }
215 
216                 }
217 
218                 /**
219                  * Remote client descriptor write request.
220                  * @hide
221                  */
222                 @Override
223                 public void onDescriptorWriteRequest(String address, int transId, int offset,
224                         int length, boolean isPrep, boolean needRsp, int handle, byte[] value) {
225                     if (VDBG) Log.d(TAG, "onDescriptorWriteRequest() - handle=" + handle);
226 
227                     BluetoothDevice device = mAdapter.getRemoteDevice(address);
228                     BluetoothGattDescriptor descriptor = getDescriptorByHandle(handle);
229                     if (descriptor == null) {
230                         Log.w(TAG, "onDescriptorWriteRequest() no desc for handle " + handle);
231                         return;
232                     }
233 
234                     try {
235                         mCallback.onDescriptorWriteRequest(device, transId, descriptor,
236                                 isPrep, needRsp, offset, value);
237                     } catch (Exception ex) {
238                         Log.w(TAG, "Unhandled exception in callback", ex);
239                     }
240                 }
241 
242                 /**
243                  * Execute pending writes.
244                  * @hide
245                  */
246                 @Override
247                 public void onExecuteWrite(String address, int transId,
248                         boolean execWrite) {
249                     if (DBG) {
250                         Log.d(TAG, "onExecuteWrite() - "
251                                 + "device=" + address + ", transId=" + transId
252                                 + "execWrite=" + execWrite);
253                     }
254 
255                     BluetoothDevice device = mAdapter.getRemoteDevice(address);
256                     if (device == null) return;
257 
258                     try {
259                         mCallback.onExecuteWrite(device, transId, execWrite);
260                     } catch (Exception ex) {
261                         Log.w(TAG, "Unhandled exception in callback", ex);
262                     }
263                 }
264 
265                 /**
266                  * A notification/indication has been sent.
267                  * @hide
268                  */
269                 @Override
270                 public void onNotificationSent(String address, int status) {
271                     if (VDBG) {
272                         Log.d(TAG, "onNotificationSent() - "
273                                 + "device=" + address + ", status=" + status);
274                     }
275 
276                     BluetoothDevice device = mAdapter.getRemoteDevice(address);
277                     if (device == null) return;
278 
279                     try {
280                         mCallback.onNotificationSent(device, status);
281                     } catch (Exception ex) {
282                         Log.w(TAG, "Unhandled exception: " + ex);
283                     }
284                 }
285 
286                 /**
287                  * The MTU for a connection has changed
288                  * @hide
289                  */
290                 @Override
291                 public void onMtuChanged(String address, int mtu) {
292                     if (DBG) {
293                         Log.d(TAG, "onMtuChanged() - "
294                                 + "device=" + address + ", mtu=" + mtu);
295                     }
296 
297                     BluetoothDevice device = mAdapter.getRemoteDevice(address);
298                     if (device == null) return;
299 
300                     try {
301                         mCallback.onMtuChanged(device, mtu);
302                     } catch (Exception ex) {
303                         Log.w(TAG, "Unhandled exception: " + ex);
304                     }
305                 }
306 
307                 /**
308                  * The PHY for a connection was updated
309                  * @hide
310                  */
311                 @Override
312                 public void onPhyUpdate(String address, int txPhy, int rxPhy, int status) {
313                     if (DBG) {
314                         Log.d(TAG,
315                                 "onPhyUpdate() - " + "device=" + address + ", txPHy=" + txPhy
316                                         + ", rxPHy=" + rxPhy);
317                     }
318 
319                     BluetoothDevice device = mAdapter.getRemoteDevice(address);
320                     if (device == null) return;
321 
322                     try {
323                         mCallback.onPhyUpdate(device, txPhy, rxPhy, status);
324                     } catch (Exception ex) {
325                         Log.w(TAG, "Unhandled exception: " + ex);
326                     }
327                 }
328 
329                 /**
330                  * The PHY for a connection was read
331                  * @hide
332                  */
333                 @Override
334                 public void onPhyRead(String address, int txPhy, int rxPhy, int status) {
335                     if (DBG) {
336                         Log.d(TAG,
337                                 "onPhyUpdate() - " + "device=" + address + ", txPHy=" + txPhy
338                                         + ", rxPHy=" + rxPhy);
339                     }
340 
341                     BluetoothDevice device = mAdapter.getRemoteDevice(address);
342                     if (device == null) return;
343 
344                     try {
345                         mCallback.onPhyRead(device, txPhy, rxPhy, status);
346                     } catch (Exception ex) {
347                         Log.w(TAG, "Unhandled exception: " + ex);
348                     }
349                 }
350 
351                 /**
352                  * Callback invoked when the given connection is updated
353                  * @hide
354                  */
355                 @Override
356                 public void onConnectionUpdated(String address, int interval, int latency,
357                         int timeout, int status) {
358                     if (DBG) {
359                         Log.d(TAG, "onConnectionUpdated() - Device=" + address
360                                 + " interval=" + interval + " latency=" + latency
361                                 + " timeout=" + timeout + " status=" + status);
362                     }
363                     BluetoothDevice device = mAdapter.getRemoteDevice(address);
364                     if (device == null) return;
365 
366                     try {
367                         mCallback.onConnectionUpdated(device, interval, latency,
368                                 timeout, status);
369                     } catch (Exception ex) {
370                         Log.w(TAG, "Unhandled exception: " + ex);
371                     }
372                 }
373 
374             };
375 
376     /**
377      * Create a BluetoothGattServer proxy object.
378      */
BluetoothGattServer(IBluetoothGatt iGatt, int transport)379     /*package*/ BluetoothGattServer(IBluetoothGatt iGatt, int transport) {
380         mService = iGatt;
381         mAdapter = BluetoothAdapter.getDefaultAdapter();
382         mCallback = null;
383         mServerIf = 0;
384         mTransport = transport;
385         mServices = new ArrayList<BluetoothGattService>();
386     }
387 
388     /**
389      * Returns a characteristic with given handle.
390      *
391      * @hide
392      */
getCharacteristicByHandle(int handle)393     /*package*/ BluetoothGattCharacteristic getCharacteristicByHandle(int handle) {
394         for (BluetoothGattService svc : mServices) {
395             for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
396                 if (charac.getInstanceId() == handle) {
397                     return charac;
398                 }
399             }
400         }
401         return null;
402     }
403 
404     /**
405      * Returns a descriptor with given handle.
406      *
407      * @hide
408      */
getDescriptorByHandle(int handle)409     /*package*/ BluetoothGattDescriptor getDescriptorByHandle(int handle) {
410         for (BluetoothGattService svc : mServices) {
411             for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
412                 for (BluetoothGattDescriptor desc : charac.getDescriptors()) {
413                     if (desc.getInstanceId() == handle) {
414                         return desc;
415                     }
416                 }
417             }
418         }
419         return null;
420     }
421 
422     /**
423      * Close this GATT server instance.
424      *
425      * Application should call this method as early as possible after it is done with
426      * this GATT server.
427      */
close()428     public void close() {
429         if (DBG) Log.d(TAG, "close()");
430         unregisterCallback();
431     }
432 
433     /**
434      * Register an application callback to start using GattServer.
435      *
436      * <p>This is an asynchronous call. The callback is used to notify
437      * success or failure if the function returns true.
438      *
439      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
440      *
441      * @param callback GATT callback handler that will receive asynchronous callbacks.
442      * @return true, the callback will be called to notify success or failure, false on immediate
443      * error
444      */
registerCallback(BluetoothGattServerCallback callback)445     /*package*/ boolean registerCallback(BluetoothGattServerCallback callback) {
446         if (DBG) Log.d(TAG, "registerCallback()");
447         if (mService == null) {
448             Log.e(TAG, "GATT service not available");
449             return false;
450         }
451         UUID uuid = UUID.randomUUID();
452         if (DBG) Log.d(TAG, "registerCallback() - UUID=" + uuid);
453 
454         synchronized (mServerIfLock) {
455             if (mCallback != null) {
456                 Log.e(TAG, "App can register callback only once");
457                 return false;
458             }
459 
460             mCallback = callback;
461             try {
462                 mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback);
463             } catch (RemoteException e) {
464                 Log.e(TAG, "", e);
465                 mCallback = null;
466                 return false;
467             }
468 
469             try {
470                 mServerIfLock.wait(CALLBACK_REG_TIMEOUT);
471             } catch (InterruptedException e) {
472                 Log.e(TAG, "" + e);
473                 mCallback = null;
474             }
475 
476             if (mServerIf == 0) {
477                 mCallback = null;
478                 return false;
479             } else {
480                 return true;
481             }
482         }
483     }
484 
485     /**
486      * Unregister the current application and callbacks.
487      */
unregisterCallback()488     private void unregisterCallback() {
489         if (DBG) Log.d(TAG, "unregisterCallback() - mServerIf=" + mServerIf);
490         if (mService == null || mServerIf == 0) return;
491 
492         try {
493             mCallback = null;
494             mService.unregisterServer(mServerIf);
495             mServerIf = 0;
496         } catch (RemoteException e) {
497             Log.e(TAG, "", e);
498         }
499     }
500 
501     /**
502      * Returns a service by UUID, instance and type.
503      *
504      * @hide
505      */
getService(UUID uuid, int instanceId, int type)506     /*package*/ BluetoothGattService getService(UUID uuid, int instanceId, int type) {
507         for (BluetoothGattService svc : mServices) {
508             if (svc.getType() == type
509                     && svc.getInstanceId() == instanceId
510                     && svc.getUuid().equals(uuid)) {
511                 return svc;
512             }
513         }
514         return null;
515     }
516 
517     /**
518      * Initiate a connection to a Bluetooth GATT capable device.
519      *
520      * <p>The connection may not be established right away, but will be
521      * completed when the remote device is available. A
522      * {@link BluetoothGattServerCallback#onConnectionStateChange} callback will be
523      * invoked when the connection state changes as a result of this function.
524      *
525      * <p>The autoConnect parameter determines whether to actively connect to
526      * the remote device, or rather passively scan and finalize the connection
527      * when the remote device is in range/available. Generally, the first ever
528      * connection to a device should be direct (autoConnect set to false) and
529      * subsequent connections to known devices should be invoked with the
530      * autoConnect parameter set to true.
531      *
532      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
533      *
534      * @param autoConnect Whether to directly connect to the remote device (false) or to
535      * automatically connect as soon as the remote device becomes available (true).
536      * @return true, if the connection attempt was initiated successfully
537      */
connect(BluetoothDevice device, boolean autoConnect)538     public boolean connect(BluetoothDevice device, boolean autoConnect) {
539         if (DBG) {
540             Log.d(TAG,
541                     "connect() - device: " + device.getAddress() + ", auto: " + autoConnect);
542         }
543         if (mService == null || mServerIf == 0) return false;
544 
545         try {
546             // autoConnect is inverse of "isDirect"
547             mService.serverConnect(mServerIf, device.getAddress(), !autoConnect, mTransport);
548         } catch (RemoteException e) {
549             Log.e(TAG, "", e);
550             return false;
551         }
552 
553         return true;
554     }
555 
556     /**
557      * Disconnects an established connection, or cancels a connection attempt
558      * currently in progress.
559      *
560      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
561      *
562      * @param device Remote device
563      */
cancelConnection(BluetoothDevice device)564     public void cancelConnection(BluetoothDevice device) {
565         if (DBG) Log.d(TAG, "cancelConnection() - device: " + device.getAddress());
566         if (mService == null || mServerIf == 0) return;
567 
568         try {
569             mService.serverDisconnect(mServerIf, device.getAddress());
570         } catch (RemoteException e) {
571             Log.e(TAG, "", e);
572         }
573     }
574 
575     /**
576      * Set the preferred connection PHY for this app. Please note that this is just a
577      * recommendation, whether the PHY change will happen depends on other applications peferences,
578      * local and remote controller capabilities. Controller can override these settings. <p> {@link
579      * BluetoothGattServerCallback#onPhyUpdate} will be triggered as a result of this call, even if
580      * no PHY change happens. It is also triggered when remote device updates the PHY.
581      *
582      * @param device The remote device to send this response to
583      * @param txPhy preferred transmitter PHY. Bitwise OR of any of {@link
584      * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link
585      * BluetoothDevice#PHY_LE_CODED_MASK}.
586      * @param rxPhy preferred receiver PHY. Bitwise OR of any of {@link
587      * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link
588      * BluetoothDevice#PHY_LE_CODED_MASK}.
589      * @param phyOptions preferred coding to use when transmitting on the LE Coded PHY. Can be one
590      * of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2} or
591      * {@link BluetoothDevice#PHY_OPTION_S8}
592      */
setPreferredPhy(BluetoothDevice device, int txPhy, int rxPhy, int phyOptions)593     public void setPreferredPhy(BluetoothDevice device, int txPhy, int rxPhy, int phyOptions) {
594         try {
595             mService.serverSetPreferredPhy(mServerIf, device.getAddress(), txPhy, rxPhy,
596                     phyOptions);
597         } catch (RemoteException e) {
598             Log.e(TAG, "", e);
599         }
600     }
601 
602     /**
603      * Read the current transmitter PHY and receiver PHY of the connection. The values are returned
604      * in {@link BluetoothGattServerCallback#onPhyRead}
605      *
606      * @param device The remote device to send this response to
607      */
readPhy(BluetoothDevice device)608     public void readPhy(BluetoothDevice device) {
609         try {
610             mService.serverReadPhy(mServerIf, device.getAddress());
611         } catch (RemoteException e) {
612             Log.e(TAG, "", e);
613         }
614     }
615 
616     /**
617      * Send a response to a read or write request to a remote device.
618      *
619      * <p>This function must be invoked in when a remote read/write request
620      * is received by one of these callback methods:
621      *
622      * <ul>
623      * <li>{@link BluetoothGattServerCallback#onCharacteristicReadRequest}
624      * <li>{@link BluetoothGattServerCallback#onCharacteristicWriteRequest}
625      * <li>{@link BluetoothGattServerCallback#onDescriptorReadRequest}
626      * <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest}
627      * </ul>
628      *
629      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
630      *
631      * @param device The remote device to send this response to
632      * @param requestId The ID of the request that was received with the callback
633      * @param status The status of the request to be sent to the remote devices
634      * @param offset Value offset for partial read/write response
635      * @param value The value of the attribute that was read/written (optional)
636      */
sendResponse(BluetoothDevice device, int requestId, int status, int offset, byte[] value)637     public boolean sendResponse(BluetoothDevice device, int requestId,
638             int status, int offset, byte[] value) {
639         if (VDBG) Log.d(TAG, "sendResponse() - device: " + device.getAddress());
640         if (mService == null || mServerIf == 0) return false;
641 
642         try {
643             mService.sendResponse(mServerIf, device.getAddress(), requestId,
644                     status, offset, value);
645         } catch (RemoteException e) {
646             Log.e(TAG, "", e);
647             return false;
648         }
649         return true;
650     }
651 
652     /**
653      * Send a notification or indication that a local characteristic has been
654      * updated.
655      *
656      * <p>A notification or indication is sent to the remote device to signal
657      * that the characteristic has been updated. This function should be invoked
658      * for every client that requests notifications/indications by writing
659      * to the "Client Configuration" descriptor for the given characteristic.
660      *
661      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
662      *
663      * @param device The remote device to receive the notification/indication
664      * @param characteristic The local characteristic that has been updated
665      * @param confirm true to request confirmation from the client (indication), false to send a
666      * notification
667      * @return true, if the notification has been triggered successfully
668      * @throws IllegalArgumentException
669      */
notifyCharacteristicChanged(BluetoothDevice device, BluetoothGattCharacteristic characteristic, boolean confirm)670     public boolean notifyCharacteristicChanged(BluetoothDevice device,
671             BluetoothGattCharacteristic characteristic, boolean confirm) {
672         if (VDBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress());
673         if (mService == null || mServerIf == 0) return false;
674 
675         BluetoothGattService service = characteristic.getService();
676         if (service == null) return false;
677 
678         if (characteristic.getValue() == null) {
679             throw new IllegalArgumentException("Chracteristic value is empty. Use "
680                     + "BluetoothGattCharacteristic#setvalue to update");
681         }
682 
683         try {
684             mService.sendNotification(mServerIf, device.getAddress(),
685                     characteristic.getInstanceId(), confirm,
686                     characteristic.getValue());
687         } catch (RemoteException e) {
688             Log.e(TAG, "", e);
689             return false;
690         }
691 
692         return true;
693     }
694 
695     /**
696      * Add a service to the list of services to be hosted.
697      *
698      * <p>Once a service has been addded to the list, the service and its
699      * included characteristics will be provided by the local device.
700      *
701      * <p>If the local device has already exposed services when this function
702      * is called, a service update notification will be sent to all clients.
703      *
704      * <p>The {@link BluetoothGattServerCallback#onServiceAdded} callback will indicate
705      * whether this service has been added successfully. Do not add another service
706      * before this callback.
707      *
708      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
709      *
710      * @param service Service to be added to the list of services provided by this device.
711      * @return true, if the request to add service has been initiated
712      */
addService(BluetoothGattService service)713     public boolean addService(BluetoothGattService service) {
714         if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid());
715         if (mService == null || mServerIf == 0) return false;
716 
717         mPendingService = service;
718 
719         try {
720             mService.addService(mServerIf, service);
721         } catch (RemoteException e) {
722             Log.e(TAG, "", e);
723             return false;
724         }
725 
726         return true;
727     }
728 
729     /**
730      * Removes a service from the list of services to be provided.
731      *
732      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
733      *
734      * @param service Service to be removed.
735      * @return true, if the service has been removed
736      */
removeService(BluetoothGattService service)737     public boolean removeService(BluetoothGattService service) {
738         if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid());
739         if (mService == null || mServerIf == 0) return false;
740 
741         BluetoothGattService intService = getService(service.getUuid(),
742                 service.getInstanceId(), service.getType());
743         if (intService == null) return false;
744 
745         try {
746             mService.removeService(mServerIf, service.getInstanceId());
747             mServices.remove(intService);
748         } catch (RemoteException e) {
749             Log.e(TAG, "", e);
750             return false;
751         }
752 
753         return true;
754     }
755 
756     /**
757      * Remove all services from the list of provided services.
758      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
759      */
clearServices()760     public void clearServices() {
761         if (DBG) Log.d(TAG, "clearServices()");
762         if (mService == null || mServerIf == 0) return;
763 
764         try {
765             mService.clearServices(mServerIf);
766             mServices.clear();
767         } catch (RemoteException e) {
768             Log.e(TAG, "", e);
769         }
770     }
771 
772     /**
773      * Returns a list of GATT services offered by this device.
774      *
775      * <p>An application must call {@link #addService} to add a serice to the
776      * list of services offered by this device.
777      *
778      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
779      *
780      * @return List of services. Returns an empty list if no services have been added yet.
781      */
getServices()782     public List<BluetoothGattService> getServices() {
783         return mServices;
784     }
785 
786     /**
787      * Returns a {@link BluetoothGattService} from the list of services offered
788      * by this device.
789      *
790      * <p>If multiple instances of the same service (as identified by UUID)
791      * exist, the first instance of the service is returned.
792      *
793      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
794      *
795      * @param uuid UUID of the requested service
796      * @return BluetoothGattService if supported, or null if the requested service is not offered by
797      * this device.
798      */
getService(UUID uuid)799     public BluetoothGattService getService(UUID uuid) {
800         for (BluetoothGattService service : mServices) {
801             if (service.getUuid().equals(uuid)) {
802                 return service;
803             }
804         }
805 
806         return null;
807     }
808 
809 
810     /**
811      * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
812      * with {@link BluetoothProfile#GATT} as argument
813      *
814      * @throws UnsupportedOperationException
815      */
816     @Override
getConnectionState(BluetoothDevice device)817     public int getConnectionState(BluetoothDevice device) {
818         throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
819     }
820 
821     /**
822      * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
823      * with {@link BluetoothProfile#GATT} as argument
824      *
825      * @throws UnsupportedOperationException
826      */
827     @Override
getConnectedDevices()828     public List<BluetoothDevice> getConnectedDevices() {
829         throw new UnsupportedOperationException(
830                 "Use BluetoothManager#getConnectedDevices instead.");
831     }
832 
833     /**
834      * Not supported - please use
835      * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])}
836      * with {@link BluetoothProfile#GATT} as first argument
837      *
838      * @throws UnsupportedOperationException
839      */
840     @Override
getDevicesMatchingConnectionStates(int[] states)841     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
842         throw new UnsupportedOperationException(
843                 "Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
844     }
845 }
846