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