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