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