• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 HIMSA II K/S - www.himsa.com.
3  * Represented by EHIMA - www.ehima.com
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.bluetooth.tbs;
19 
20 import static android.bluetooth.BluetoothDevice.METADATA_GTBS_CCCD;
21 
22 import android.bluetooth.BluetoothAdapter;
23 import android.bluetooth.BluetoothDevice;
24 import android.bluetooth.BluetoothGatt;
25 import android.bluetooth.BluetoothGattCharacteristic;
26 import android.bluetooth.BluetoothGattDescriptor;
27 import android.bluetooth.BluetoothGattServerCallback;
28 import android.bluetooth.BluetoothGattService;
29 import android.bluetooth.BluetoothProfile;
30 import android.bluetooth.IBluetoothManager;
31 import android.bluetooth.IBluetoothStateChangeCallback;
32 import android.content.Context;
33 import android.os.Handler;
34 import android.os.Looper;
35 import android.os.ParcelUuid;
36 import android.os.RemoteException;
37 import android.util.Log;
38 
39 import com.android.bluetooth.btservice.AdapterService;
40 import com.android.bluetooth.Utils;
41 import com.android.internal.annotations.VisibleForTesting;
42 
43 import java.io.ByteArrayOutputStream;
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.Objects;
49 import java.util.UUID;
50 
51 public class TbsGatt {
52 
53     private static final String TAG = "TbsGatt";
54     private static final boolean DBG = true;
55 
56     private static final String UUID_PREFIX = "0000";
57     private static final String UUID_SUFFIX = "-0000-1000-8000-00805f9b34fb";
58 
59     /* TBS assigned uuid's */
60     @VisibleForTesting
61     static final UUID UUID_TBS = makeUuid("184B");
62     @VisibleForTesting
63     public static final UUID UUID_GTBS = makeUuid("184C");
64     @VisibleForTesting
65     static final UUID UUID_BEARER_PROVIDER_NAME = makeUuid("2BB3");
66     @VisibleForTesting
67     static final UUID UUID_BEARER_UCI = makeUuid("2BB4");
68     @VisibleForTesting
69     static final UUID UUID_BEARER_TECHNOLOGY = makeUuid("2BB5");
70     @VisibleForTesting
71     static final UUID UUID_BEARER_URI_SCHEMES_SUPPORTED_LIST = makeUuid("2BB6");
72     @VisibleForTesting
73     static final UUID UUID_BEARER_LIST_CURRENT_CALLS = makeUuid("2BB9");
74     @VisibleForTesting
75     static final UUID UUID_CONTENT_CONTROL_ID = makeUuid("2BBA");
76     @VisibleForTesting
77     static final UUID UUID_STATUS_FLAGS = makeUuid("2BBB");
78     @VisibleForTesting
79     static final UUID UUID_CALL_STATE = makeUuid("2BBD");
80     @VisibleForTesting
81     static final UUID UUID_CALL_CONTROL_POINT = makeUuid("2BBE");
82     @VisibleForTesting
83     static final UUID UUID_CALL_CONTROL_POINT_OPTIONAL_OPCODES = makeUuid("2BBF");
84     @VisibleForTesting
85     static final UUID UUID_TERMINATION_REASON = makeUuid("2BC0");
86     @VisibleForTesting
87     static final UUID UUID_INCOMING_CALL = makeUuid("2BC1");
88     @VisibleForTesting
89     static final UUID UUID_CALL_FRIENDLY_NAME = makeUuid("2BC2");
90     @VisibleForTesting
91     static final UUID UUID_CLIENT_CHARACTERISTIC_CONFIGURATION = makeUuid("2902");
92 
93     @VisibleForTesting
94     static final int STATUS_FLAG_INBAND_RINGTONE_ENABLED = 0x0001;
95     @VisibleForTesting
96     static final int STATUS_FLAG_SILENT_MODE_ENABLED = 0x0002;
97 
98     @VisibleForTesting
99     static final int CALL_CONTROL_POINT_OPTIONAL_OPCODE_LOCAL_HOLD = 0x0001;
100     @VisibleForTesting
101     static final int CALL_CONTROL_POINT_OPTIONAL_OPCODE_JOIN = 0x0002;
102 
103     @VisibleForTesting
104     public static final int CALL_CONTROL_POINT_OPCODE_ACCEPT = 0x00;
105     @VisibleForTesting
106     public static final int CALL_CONTROL_POINT_OPCODE_TERMINATE = 0x01;
107     @VisibleForTesting
108     public static final int CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD = 0x02;
109     @VisibleForTesting
110     public static final int CALL_CONTROL_POINT_OPCODE_LOCAL_RETRIEVE = 0x03;
111     @VisibleForTesting
112     public static final int CALL_CONTROL_POINT_OPCODE_ORIGINATE = 0x04;
113     @VisibleForTesting
114     public static final int CALL_CONTROL_POINT_OPCODE_JOIN = 0x05;
115 
116     @VisibleForTesting
117     public static final int CALL_CONTROL_POINT_RESULT_SUCCESS = 0x00;
118     @VisibleForTesting
119     public static final int CALL_CONTROL_POINT_RESULT_OPCODE_NOT_SUPPORTED = 0x01;
120     @VisibleForTesting
121     public static final int CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE = 0x02;
122     @VisibleForTesting
123     public static final int CALL_CONTROL_POINT_RESULT_INVALID_CALL_INDEX = 0x03;
124     @VisibleForTesting
125     public static final int CALL_CONTROL_POINT_RESULT_STATE_MISMATCH = 0x04;
126     @VisibleForTesting
127     public static final int CALL_CONTROL_POINT_RESULT_LACK_OF_RESOURCES = 0x05;
128     @VisibleForTesting
129     public static final int CALL_CONTROL_POINT_RESULT_INVALID_OUTGOING_URI = 0x06;
130 
131     private final Context mContext;
132     private final GattCharacteristic mBearerProviderNameCharacteristic;
133     private final GattCharacteristic mBearerUciCharacteristic;
134     private final GattCharacteristic mBearerTechnologyCharacteristic;
135     private final GattCharacteristic mBearerUriSchemesSupportedListCharacteristic;
136     private final GattCharacteristic mBearerListCurrentCallsCharacteristic;
137     private final GattCharacteristic mContentControlIdCharacteristic;
138     private final GattCharacteristic mStatusFlagsCharacteristic;
139     private final GattCharacteristic mCallStateCharacteristic;
140     private final CallControlPointCharacteristic mCallControlPointCharacteristic;
141     private final GattCharacteristic mCallControlPointOptionalOpcodesCharacteristic;
142     private final GattCharacteristic mTerminationReasonCharacteristic;
143     private final GattCharacteristic mIncomingCallCharacteristic;
144     private final GattCharacteristic mCallFriendlyNameCharacteristic;
145     private List<BluetoothDevice> mSubscribers = new ArrayList<>();
146     private BluetoothGattServerProxy mBluetoothGattServer;
147     private Handler mHandler;
148     private Callback mCallback;
149     private AdapterService mAdapterService;
150 
151     public static abstract class Callback {
152 
onServiceAdded(boolean success)153         public abstract void onServiceAdded(boolean success);
154 
onCallControlPointRequest(BluetoothDevice device, int opcode, byte[] args)155         public abstract void onCallControlPointRequest(BluetoothDevice device, int opcode,
156                 byte[] args);
157     }
158 
TbsGatt(Context context)159     TbsGatt(Context context) {
160         mAdapterService =  Objects.requireNonNull(AdapterService.getAdapterService(),
161                 "AdapterService shouldn't be null when creating MediaControlCattService");
162         IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager();
163         if (mgr != null) {
164             try {
165                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
166             } catch (RemoteException e) {
167                 throw e.rethrowFromSystemServer();
168             }
169         }
170 
171         mContext = context;
172         mBearerProviderNameCharacteristic = new GattCharacteristic(UUID_BEARER_PROVIDER_NAME,
173                 BluetoothGattCharacteristic.PROPERTY_READ
174                         | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
175                 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
176         mBearerUciCharacteristic =
177                 new GattCharacteristic(UUID_BEARER_UCI, BluetoothGattCharacteristic.PROPERTY_READ,
178                         BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
179         mBearerTechnologyCharacteristic = new GattCharacteristic(UUID_BEARER_TECHNOLOGY,
180                 BluetoothGattCharacteristic.PROPERTY_READ
181                         | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
182                 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
183         mBearerUriSchemesSupportedListCharacteristic =
184                 new GattCharacteristic(UUID_BEARER_URI_SCHEMES_SUPPORTED_LIST,
185                         BluetoothGattCharacteristic.PROPERTY_READ
186                                 | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
187                         BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
188         mBearerListCurrentCallsCharacteristic =
189                 new GattCharacteristic(UUID_BEARER_LIST_CURRENT_CALLS,
190                         BluetoothGattCharacteristic.PROPERTY_READ
191                                 | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
192                         BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
193         mContentControlIdCharacteristic = new GattCharacteristic(UUID_CONTENT_CONTROL_ID,
194                 BluetoothGattCharacteristic.PROPERTY_READ,
195                 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
196         mStatusFlagsCharacteristic = new GattCharacteristic(UUID_STATUS_FLAGS,
197                 BluetoothGattCharacteristic.PROPERTY_READ
198                         | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
199                 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
200         mCallStateCharacteristic = new GattCharacteristic(UUID_CALL_STATE,
201                 BluetoothGattCharacteristic.PROPERTY_READ
202                         | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
203                 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
204         mCallControlPointCharacteristic = new CallControlPointCharacteristic();
205         mCallControlPointOptionalOpcodesCharacteristic = new GattCharacteristic(
206                 UUID_CALL_CONTROL_POINT_OPTIONAL_OPCODES, BluetoothGattCharacteristic.PROPERTY_READ,
207                 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
208         mTerminationReasonCharacteristic = new GattCharacteristic(UUID_TERMINATION_REASON,
209                 BluetoothGattCharacteristic.PROPERTY_NOTIFY, 0);
210         mIncomingCallCharacteristic = new GattCharacteristic(UUID_INCOMING_CALL,
211                 BluetoothGattCharacteristic.PROPERTY_READ
212                         | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
213                 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
214         mCallFriendlyNameCharacteristic = new GattCharacteristic(UUID_CALL_FRIENDLY_NAME,
215                 BluetoothGattCharacteristic.PROPERTY_READ
216                         | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
217                 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
218         mBluetoothGattServer = null;
219     }
220 
221     @VisibleForTesting
setBluetoothGattServerForTesting(BluetoothGattServerProxy proxy)222     void setBluetoothGattServerForTesting(BluetoothGattServerProxy proxy) {
223         mBluetoothGattServer = proxy;
224     }
225 
init(int ccid, String uci, List<String> uriSchemes, boolean isLocalHoldOpcodeSupported, boolean isJoinOpcodeSupported, String providerName, int technology, Callback callback)226     public boolean init(int ccid, String uci, List<String> uriSchemes,
227             boolean isLocalHoldOpcodeSupported, boolean isJoinOpcodeSupported, String providerName,
228             int technology, Callback callback) {
229         mBearerProviderNameCharacteristic.setValue(providerName);
230         mBearerTechnologyCharacteristic.setValue(new byte[] {(byte) (technology & 0xFF)});
231         mBearerUciCharacteristic.setValue(uci);
232         setBearerUriSchemesSupportedList(uriSchemes);
233         mContentControlIdCharacteristic.setValue(ccid, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
234         setCallControlPointOptionalOpcodes(isLocalHoldOpcodeSupported, isJoinOpcodeSupported);
235         mStatusFlagsCharacteristic.setValue(0, BluetoothGattCharacteristic.FORMAT_UINT16, 0);
236         mCallback = callback;
237         mHandler = new Handler(Looper.getMainLooper());
238 
239         if (mBluetoothGattServer == null) {
240             mBluetoothGattServer = new BluetoothGattServerProxy(mContext);
241         }
242 
243         if (!mBluetoothGattServer.open(mGattServerCallback)) {
244             Log.e(TAG, " Could not open Gatt server");
245             return false;
246         }
247 
248         BluetoothGattService gattService =
249                 new BluetoothGattService(UUID_GTBS, BluetoothGattService.SERVICE_TYPE_PRIMARY);
250         gattService.addCharacteristic(mBearerProviderNameCharacteristic);
251         gattService.addCharacteristic(mBearerUciCharacteristic);
252         gattService.addCharacteristic(mBearerTechnologyCharacteristic);
253         gattService.addCharacteristic(mBearerUriSchemesSupportedListCharacteristic);
254         gattService.addCharacteristic(mBearerListCurrentCallsCharacteristic);
255         gattService.addCharacteristic(mContentControlIdCharacteristic);
256         gattService.addCharacteristic(mStatusFlagsCharacteristic);
257         gattService.addCharacteristic(mCallStateCharacteristic);
258         gattService.addCharacteristic(mCallControlPointCharacteristic);
259         gattService.addCharacteristic(mCallControlPointOptionalOpcodesCharacteristic);
260         gattService.addCharacteristic(mTerminationReasonCharacteristic);
261         gattService.addCharacteristic(mIncomingCallCharacteristic);
262         gattService.addCharacteristic(mCallFriendlyNameCharacteristic);
263 
264         return mBluetoothGattServer.addService(gattService);
265     }
266 
cleanup()267     public void cleanup() {
268         if (mBluetoothGattServer == null) {
269             return;
270         }
271         mBluetoothGattServer.close();
272         mBluetoothGattServer = null;
273     }
274 
getContext()275     public Context getContext() {
276         return mContext;
277     }
278 
removeUuidFromMetadata(ParcelUuid charUuid, BluetoothDevice device)279     private void removeUuidFromMetadata(ParcelUuid charUuid, BluetoothDevice device) {
280         List<ParcelUuid> uuidList;
281         byte[] gtbs_cccd = device.getMetadata(METADATA_GTBS_CCCD);
282 
283         if ((gtbs_cccd == null) || (gtbs_cccd.length == 0)) {
284             uuidList = new ArrayList<ParcelUuid>();
285         } else {
286             uuidList = new ArrayList<>(Arrays.asList(Utils.byteArrayToUuid(gtbs_cccd)));
287 
288             if (!uuidList.contains(charUuid)) {
289                 Log.d(TAG, "Characteristic CCCD can't be removed (not cached): "
290                         + charUuid.toString());
291                 return;
292             }
293         }
294 
295         uuidList.remove(charUuid);
296 
297         if (!device.setMetadata(METADATA_GTBS_CCCD,
298                 Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
299             Log.e(TAG, "Can't set CCCD for GTBS characteristic UUID: " + charUuid + ", (remove)");
300         }
301     }
302 
addUuidToMetadata(ParcelUuid charUuid, BluetoothDevice device)303     private void addUuidToMetadata(ParcelUuid charUuid, BluetoothDevice device) {
304         List<ParcelUuid> uuidList;
305         byte[] gtbs_cccd = device.getMetadata(METADATA_GTBS_CCCD);
306 
307         if ((gtbs_cccd == null) || (gtbs_cccd.length == 0)) {
308             uuidList = new ArrayList<ParcelUuid>();
309         } else {
310             uuidList = new ArrayList<>(Arrays.asList(Utils.byteArrayToUuid(gtbs_cccd)));
311 
312             if (uuidList.contains(charUuid)) {
313                 Log.d(TAG, "Characteristic CCCD already add: " + charUuid.toString());
314                 return;
315             }
316         }
317 
318         uuidList.add(charUuid);
319 
320         if (!device.setMetadata(METADATA_GTBS_CCCD,
321                 Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
322             Log.e(TAG, "Can't set CCCD for GTBS characteristic UUID: " + charUuid + ", (add)");
323         }
324     }
325 
326     /** Class that handles GATT characteristic notifications */
327     private class BluetoothGattCharacteristicNotifier {
setSubscriptionConfiguration(BluetoothDevice device, byte[] configuration)328         public int setSubscriptionConfiguration(BluetoothDevice device, byte[] configuration) {
329             if (Arrays.equals(configuration, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) {
330                 mSubscribers.remove(device);
331             } else if (!isSubscribed(device)) {
332                 mSubscribers.add(device);
333             }
334 
335             return BluetoothGatt.GATT_SUCCESS;
336         }
337 
getSubscriptionConfiguration(BluetoothDevice device)338         public byte[] getSubscriptionConfiguration(BluetoothDevice device) {
339             if (isSubscribed(device)) {
340                 return BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
341             }
342 
343             return BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
344         }
345 
isSubscribed(BluetoothDevice device)346         public boolean isSubscribed(BluetoothDevice device) {
347             return mSubscribers.contains(device);
348         }
349 
notifyCharacteristicChanged(BluetoothDevice device, BluetoothGattCharacteristic characteristic)350         private void notifyCharacteristicChanged(BluetoothDevice device,
351                 BluetoothGattCharacteristic characteristic) {
352             if (mBluetoothGattServer != null) {
353                 mBluetoothGattServer.notifyCharacteristicChanged(device, characteristic, false);
354             }
355         }
356 
notify(BluetoothDevice device, BluetoothGattCharacteristic characteristic)357         public void notify(BluetoothDevice device, BluetoothGattCharacteristic characteristic) {
358             if (isSubscribed(device)) {
359                 notifyCharacteristicChanged(device, characteristic);
360             }
361         }
362 
notifyAll(BluetoothGattCharacteristic characteristic)363         public void notifyAll(BluetoothGattCharacteristic characteristic) {
364             for (BluetoothDevice device : mSubscribers) {
365                 notifyCharacteristicChanged(device, characteristic);
366             }
367         }
368     }
369 
370     /** Wrapper class for BluetoothGattCharacteristic */
371     private class GattCharacteristic extends BluetoothGattCharacteristic {
372 
373         protected BluetoothGattCharacteristicNotifier mNotifier;
374 
GattCharacteristic(UUID uuid, int properties, int permissions)375         public GattCharacteristic(UUID uuid, int properties, int permissions) {
376             super(uuid, properties, permissions);
377             if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
378                 mNotifier = new BluetoothGattCharacteristicNotifier();
379                 addDescriptor(new ClientCharacteristicConfigurationDescriptor());
380             } else {
381                 mNotifier = null;
382             }
383         }
384 
getSubscriptionConfiguration(BluetoothDevice device)385         public byte[] getSubscriptionConfiguration(BluetoothDevice device) {
386             return mNotifier.getSubscriptionConfiguration(device);
387         }
388 
setSubscriptionConfiguration(BluetoothDevice device, byte[] configuration)389         public int setSubscriptionConfiguration(BluetoothDevice device, byte[] configuration) {
390             return mNotifier.setSubscriptionConfiguration(device, configuration);
391         }
392 
isNotifiable()393         private boolean isNotifiable() {
394             return mNotifier != null;
395         }
396 
397         @Override
setValue(byte[] value)398         public boolean setValue(byte[] value) {
399             boolean success = super.setValue(value);
400             if (success && isNotifiable()) {
401                 mNotifier.notifyAll(this);
402             }
403 
404             return success;
405         }
406 
407         @Override
setValue(int value, int formatType, int offset)408         public boolean setValue(int value, int formatType, int offset) {
409             boolean success = super.setValue(value, formatType, offset);
410             if (success && isNotifiable()) {
411                 mNotifier.notifyAll(this);
412             }
413 
414             return success;
415         }
416 
417         @Override
setValue(String value)418         public boolean setValue(String value) {
419             boolean success = super.setValue(value);
420             if (success && isNotifiable()) {
421                 mNotifier.notifyAll(this);
422             }
423 
424             return success;
425         }
426 
setValueNoNotify(byte[] value)427         public boolean setValueNoNotify(byte[] value) {
428             return super.setValue(value);
429         }
430 
clearValue(boolean notify)431         public boolean clearValue(boolean notify) {
432             boolean success = super.setValue(new byte[0]);
433             if (success && notify && isNotifiable()) {
434                 mNotifier.notifyAll(this);
435             }
436 
437             return success;
438         }
439 
handleWriteRequest(BluetoothDevice device, int requestId, boolean responseNeeded, byte[] value)440         public void handleWriteRequest(BluetoothDevice device, int requestId,
441                 boolean responseNeeded, byte[] value) {
442             if (responseNeeded) {
443                 mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0,
444                         value);
445             }
446         }
447     }
448 
449     private class CallControlPointCharacteristic extends GattCharacteristic {
450 
CallControlPointCharacteristic()451         public CallControlPointCharacteristic() {
452             super(UUID_CALL_CONTROL_POINT,
453                     PROPERTY_WRITE | PROPERTY_WRITE_NO_RESPONSE | PROPERTY_NOTIFY,
454                     PERMISSION_WRITE_ENCRYPTED);
455         }
456 
457         @Override
handleWriteRequest(BluetoothDevice device, int requestId, boolean responseNeeded, byte[] value)458         public void handleWriteRequest(BluetoothDevice device, int requestId,
459                 boolean responseNeeded, byte[] value) {
460             int status;
461             if (value.length == 0) {
462                 // at least opcode is required
463                 status = BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
464             } else {
465                 status = BluetoothGatt.GATT_SUCCESS;
466             }
467 
468             if (responseNeeded) {
469                 mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0,
470                         value);
471             }
472 
473             int opcode = (int) value[0];
474             mCallback.onCallControlPointRequest(device, opcode,
475                     Arrays.copyOfRange(value, 1, value.length));
476         }
477 
setResult(BluetoothDevice device, int requestedOpcode, int callIndex, int requestResult)478         public void setResult(BluetoothDevice device, int requestedOpcode, int callIndex,
479                 int requestResult) {
480             byte[] value = new byte[3];
481             value[0] = (byte) (requestedOpcode);
482             value[1] = (byte) (callIndex);
483             value[2] = (byte) (requestResult);
484 
485             super.setValueNoNotify(value);
486 
487             // to avoid sending control point notification before write response
488             mHandler.post(() -> mNotifier.notify(device, this));
489         }
490     }
491 
492     private class ClientCharacteristicConfigurationDescriptor extends BluetoothGattDescriptor {
493 
ClientCharacteristicConfigurationDescriptor()494         ClientCharacteristicConfigurationDescriptor() {
495             super(UUID_CLIENT_CHARACTERISTIC_CONFIGURATION,
496                     PERMISSION_READ | PERMISSION_WRITE_ENCRYPTED);
497         }
498 
getValue(BluetoothDevice device)499         public byte[] getValue(BluetoothDevice device) {
500             GattCharacteristic characteristic = (GattCharacteristic) getCharacteristic();
501             byte value[] = characteristic.getSubscriptionConfiguration(device);
502             if (value == null) {
503                 return BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
504             }
505 
506             return value;
507         }
508 
setValue(BluetoothDevice device, byte[] value)509         public int setValue(BluetoothDevice device, byte[] value) {
510             GattCharacteristic characteristic = (GattCharacteristic) getCharacteristic();
511             int properties = characteristic.getProperties();
512 
513             if (value.length != 2) {
514                 return BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
515 
516             } else if ((!Arrays.equals(value, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
517                     && !Arrays.equals(value, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)
518                     && !Arrays.equals(value, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE))
519                     || ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == 0 && Arrays
520                             .equals(value, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE))
521                     || ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == 0 && Arrays
522                             .equals(value, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE))) {
523                 return BluetoothGatt.GATT_FAILURE;
524             }
525 
526             if (Arrays.equals(value, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
527                 addUuidToMetadata(new ParcelUuid(characteristic.getUuid()), device);
528             } else if (Arrays.equals(value, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) {
529                 removeUuidFromMetadata(new ParcelUuid(characteristic.getUuid()), device);
530             } else {
531                 Log.e(TAG, "Not handled CCC value: " + Arrays.toString(value));
532             }
533 
534             return characteristic.setSubscriptionConfiguration(device, value);
535         }
536     }
537 
setBearerProviderName(String providerName)538     public boolean setBearerProviderName(String providerName) {
539         return mBearerProviderNameCharacteristic.setValue(providerName);
540     }
541 
setBearerTechnology(int technology)542     public boolean setBearerTechnology(int technology) {
543         return mBearerTechnologyCharacteristic.setValue(technology,
544                 BluetoothGattCharacteristic.FORMAT_UINT8, 0);
545     }
546 
setBearerUriSchemesSupportedList(List<String> bearerUriSchemesSupportedList)547     public boolean setBearerUriSchemesSupportedList(List<String> bearerUriSchemesSupportedList) {
548         return mBearerUriSchemesSupportedListCharacteristic
549                 .setValue(String.join(",", bearerUriSchemesSupportedList));
550     }
551 
setCallState(Map<Integer, TbsCall> callsList)552     public boolean setCallState(Map<Integer, TbsCall> callsList) {
553         if (DBG) {
554             Log.d(TAG, "setCallState: callsList=" + callsList);
555         }
556         int i = 0;
557         byte[] value = new byte[callsList.size() * 3];
558         for (Map.Entry<Integer, TbsCall> entry : callsList.entrySet()) {
559             TbsCall call = entry.getValue();
560             value[i++] = (byte) (entry.getKey() & 0xff);
561             value[i++] = (byte) (call.getState() & 0xff);
562             value[i++] = (byte) (call.getFlags() & 0xff);
563         }
564 
565         return mCallStateCharacteristic.setValue(value);
566     }
567 
setBearerListCurrentCalls(Map<Integer, TbsCall> callsList)568     public boolean setBearerListCurrentCalls(Map<Integer, TbsCall> callsList) {
569         if (DBG) {
570             Log.d(TAG, "setBearerListCurrentCalls: callsList=" + callsList);
571         }
572         final int listItemLengthMax = Byte.MAX_VALUE;
573 
574         ByteArrayOutputStream stream = new ByteArrayOutputStream();
575         for (Map.Entry<Integer, TbsCall> entry : callsList.entrySet()) {
576             TbsCall call = entry.getValue();
577             if (call == null) {
578                 Log.w(TAG, "setBearerListCurrentCalls: call is null");
579                 continue;
580             }
581 
582             int uri_len = 0;
583             if (call.getUri() != null) {
584                 uri_len =  call.getUri().getBytes().length;
585             }
586 
587             int listItemLength = Math.min(listItemLengthMax, 3 + uri_len);
588             stream.write((byte) (listItemLength & 0xff));
589             stream.write((byte) (entry.getKey() & 0xff));
590             stream.write((byte) (call.getState() & 0xff));
591             stream.write((byte) (call.getFlags() & 0xff));
592             if (uri_len > 0) {
593                 stream.write(call.getUri().getBytes(), 0, listItemLength - 3);
594             }
595         }
596 
597         return mBearerListCurrentCallsCharacteristic.setValue(stream.toByteArray());
598     }
599 
updateStatusFlags(int flag, boolean set)600     private boolean updateStatusFlags(int flag, boolean set) {
601         Integer valueInt = mStatusFlagsCharacteristic
602                 .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 0);
603 
604         if (((valueInt & flag) != 0) == set) {
605             return false;
606         }
607 
608         valueInt ^= flag;
609 
610         return mStatusFlagsCharacteristic.setValue(valueInt,
611                 BluetoothGattCharacteristic.FORMAT_UINT16, 0);
612     }
613 
setInbandRingtoneFlag()614     public boolean setInbandRingtoneFlag() {
615         return updateStatusFlags(STATUS_FLAG_INBAND_RINGTONE_ENABLED, true);
616     }
617 
clearInbandRingtoneFlag()618     public boolean clearInbandRingtoneFlag() {
619         return updateStatusFlags(STATUS_FLAG_INBAND_RINGTONE_ENABLED, false);
620     }
621 
setSilentModeFlag()622     public boolean setSilentModeFlag() {
623         return updateStatusFlags(STATUS_FLAG_SILENT_MODE_ENABLED, true);
624     }
625 
clearSilentModeFlag()626     public boolean clearSilentModeFlag() {
627         return updateStatusFlags(STATUS_FLAG_SILENT_MODE_ENABLED, false);
628     }
629 
setCallControlPointOptionalOpcodes(boolean isLocalHoldOpcodeSupported, boolean isJoinOpcodeSupported)630     private void setCallControlPointOptionalOpcodes(boolean isLocalHoldOpcodeSupported,
631             boolean isJoinOpcodeSupported) {
632         int valueInt = 0;
633         if (isLocalHoldOpcodeSupported) {
634             valueInt |= CALL_CONTROL_POINT_OPTIONAL_OPCODE_LOCAL_HOLD;
635         }
636         if (isJoinOpcodeSupported) {
637             valueInt |= CALL_CONTROL_POINT_OPTIONAL_OPCODE_JOIN;
638         }
639 
640         byte[] value = new byte[2];
641         value[0] = (byte) (valueInt & 0xff);
642         value[1] = (byte) ((valueInt >> 8) & 0xff);
643 
644         mCallControlPointOptionalOpcodesCharacteristic.setValue(value);
645     }
646 
setTerminationReason(int callIndex, int terminationReason)647     public boolean setTerminationReason(int callIndex, int terminationReason) {
648         if (DBG) {
649             Log.d(TAG, "setTerminationReason: callIndex=" + callIndex + " terminationReason="
650                     + terminationReason);
651         }
652         byte[] value = new byte[2];
653         value[0] = (byte) (callIndex & 0xff);
654         value[1] = (byte) (terminationReason & 0xff);
655 
656         return mTerminationReasonCharacteristic.setValue(value);
657     }
658 
getIncomingCallIndex()659     public Integer getIncomingCallIndex() {
660         byte[] value = mIncomingCallCharacteristic.getValue();
661         if (value == null || value.length == 0) {
662             return null;
663         }
664 
665         return (int) value[0];
666     }
667 
setIncomingCall(int callIndex, String uri)668     public boolean setIncomingCall(int callIndex, String uri) {
669         if (DBG) {
670             Log.d(TAG, "setIncomingCall: callIndex=" + callIndex + " uri=" + uri);
671         }
672         int uri_len = 0;
673         if (uri != null) {
674             uri_len = uri.length();
675         }
676 
677         byte[] value = new byte[uri_len + 1];
678         value[0] = (byte) (callIndex & 0xff);
679 
680         if (uri_len > 0) {
681             System.arraycopy(uri.getBytes(), 0, value, 1, uri_len);
682         }
683 
684         return mIncomingCallCharacteristic.setValue(value);
685     }
686 
clearIncomingCall()687     public boolean clearIncomingCall() {
688         if (DBG) {
689             Log.d(TAG, "clearIncomingCall");
690         }
691         return mIncomingCallCharacteristic.clearValue(false);
692     }
693 
setCallFriendlyName(int callIndex, String callFriendlyName)694     public boolean setCallFriendlyName(int callIndex, String callFriendlyName) {
695         if (DBG) {
696             Log.d(TAG, "setCallFriendlyName: callIndex=" + callIndex + "callFriendlyName="
697                     + callFriendlyName);
698         }
699         byte[] value = new byte[callFriendlyName.length() + 1];
700         value[0] = (byte) (callIndex & 0xff);
701         System.arraycopy(callFriendlyName.getBytes(), 0, value, 1, callFriendlyName.length());
702 
703         return mCallFriendlyNameCharacteristic.setValue(value);
704     }
705 
getCallFriendlyNameIndex()706     public Integer getCallFriendlyNameIndex() {
707         byte[] value = mCallFriendlyNameCharacteristic.getValue();
708         if (value == null || value.length == 0) {
709             return null;
710         }
711 
712         return (int) value[0];
713     }
714 
clearFriendlyName()715     public boolean clearFriendlyName() {
716         if (DBG) {
717             Log.d(TAG, "clearFriendlyName");
718         }
719         return mCallFriendlyNameCharacteristic.clearValue(false);
720     }
721 
setCallControlPointResult(BluetoothDevice device, int requestedOpcode, int callIndex, int requestResult)722     public void setCallControlPointResult(BluetoothDevice device, int requestedOpcode,
723             int callIndex, int requestResult) {
724         if (DBG) {
725             Log.d(TAG,
726                     "setCallControlPointResult: device=" + device + " requestedOpcode="
727                             + requestedOpcode + " callIndex=" + callIndex + " requesuResult="
728                             + requestResult);
729         }
730         mCallControlPointCharacteristic.setResult(device, requestedOpcode, callIndex,
731                 requestResult);
732     }
733 
makeUuid(String uuid16)734     private static UUID makeUuid(String uuid16) {
735         return UUID.fromString(UUID_PREFIX + uuid16 + UUID_SUFFIX);
736     }
737 
restoreCccValuesForStoredDevices()738     private void restoreCccValuesForStoredDevices() {
739         BluetoothGattService gattService = mBluetoothGattServer.getService(UUID_GTBS);
740 
741         for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
742             byte[] gtbs_cccd = device.getMetadata(METADATA_GTBS_CCCD);
743 
744             if ((gtbs_cccd == null) || (gtbs_cccd.length == 0)) {
745                 return;
746             }
747 
748             List<ParcelUuid> uuidList = Arrays.asList(Utils.byteArrayToUuid(gtbs_cccd));
749 
750             /* Restore CCCD values for device */
751             for (ParcelUuid uuid : uuidList) {
752                 BluetoothGattCharacteristic characteristic =
753                         gattService.getCharacteristic(uuid.getUuid());
754                 if (characteristic == null) {
755                     Log.e(TAG, "Invalid UUID stored in metadata: " + uuid.toString());
756                     continue;
757                 }
758 
759                 BluetoothGattDescriptor descriptor =
760                         characteristic.getDescriptor(UUID_CLIENT_CHARACTERISTIC_CONFIGURATION);
761                 if (descriptor == null) {
762                     Log.e(TAG, "Invalid characteristic, does not include CCCD");
763                     continue;
764                 }
765 
766                 descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
767                 mSubscribers.add(device);
768             }
769         }
770     }
771 
772     private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
773             new IBluetoothStateChangeCallback.Stub() {
774                 public void onBluetoothStateChange(boolean up) {
775                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
776                     if (up) {
777                         restoreCccValuesForStoredDevices();
778                     }
779                 }
780             };
781 
782     /**
783      * Callback to handle incoming requests to the GATT server. All read/write requests for
784      * characteristics and descriptors are handled here.
785      */
786     @VisibleForTesting
787     final BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
788         @Override
789         public void onServiceAdded(int status, BluetoothGattService service) {
790             if (DBG) {
791                 Log.d(TAG, "onServiceAdded: status=" + status);
792             }
793             if (mCallback != null) {
794                 mCallback.onServiceAdded(status == BluetoothGatt.GATT_SUCCESS);
795             }
796 
797             restoreCccValuesForStoredDevices();
798         }
799 
800         @Override
801         public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset,
802                 BluetoothGattCharacteristic characteristic) {
803             if (DBG) {
804                 Log.d(TAG, "onCharacteristicReadRequest: device=" + device);
805             }
806             GattCharacteristic gattCharacteristic = (GattCharacteristic) characteristic;
807             byte[] value = gattCharacteristic.getValue();
808             if (value == null) {
809                 value = new byte[0];
810             }
811 
812             int status;
813             if (value.length < offset) {
814                 status = BluetoothGatt.GATT_INVALID_OFFSET;
815             } else {
816                 value = Arrays.copyOfRange(value, offset, value.length);
817                 status = BluetoothGatt.GATT_SUCCESS;
818             }
819 
820             mBluetoothGattServer.sendResponse(device, requestId, status, offset, value);
821         }
822 
823         @Override
824         public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
825                 BluetoothGattCharacteristic characteristic, boolean preparedWrite,
826                 boolean responseNeeded, int offset, byte[] value) {
827             if (DBG) {
828                 Log.d(TAG, "onCharacteristicWriteRequest: device=" + device);
829             }
830             GattCharacteristic gattCharacteristic = (GattCharacteristic) characteristic;
831             int status;
832             if (preparedWrite) {
833                 status = BluetoothGatt.GATT_FAILURE;
834             } else if (offset > 0) {
835                 status = BluetoothGatt.GATT_INVALID_OFFSET;
836             } else {
837                 gattCharacteristic.handleWriteRequest(device, requestId, responseNeeded, value);
838                 return;
839             }
840 
841             if (responseNeeded) {
842                 mBluetoothGattServer.sendResponse(device, requestId, status, offset, value);
843             }
844         }
845 
846         @Override
847         public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset,
848                 BluetoothGattDescriptor descriptor) {
849             if (DBG) {
850                 Log.d(TAG, "onDescriptorReadRequest: device=" + device);
851             }
852             ClientCharacteristicConfigurationDescriptor cccd =
853                     (ClientCharacteristicConfigurationDescriptor) descriptor;
854             byte[] value = cccd.getValue(device);
855             int status;
856             if (value.length < offset) {
857                 status = BluetoothGatt.GATT_INVALID_OFFSET;
858             } else {
859                 value = Arrays.copyOfRange(value, offset, value.length);
860                 status = BluetoothGatt.GATT_SUCCESS;
861             }
862 
863             mBluetoothGattServer.sendResponse(device, requestId, status, offset, value);
864         }
865 
866         @Override
867         public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
868                 BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded,
869                 int offset, byte[] value) {
870             if (DBG) {
871                 Log.d(TAG, "onDescriptorWriteRequest: device=" + device);
872             }
873             ClientCharacteristicConfigurationDescriptor cccd =
874                     (ClientCharacteristicConfigurationDescriptor) descriptor;
875             int status;
876             if (preparedWrite) {
877                 // TODO: handle prepareWrite
878                 status = BluetoothGatt.GATT_FAILURE;
879             } else if (offset > 0) {
880                 status = BluetoothGatt.GATT_INVALID_OFFSET;
881             } else if (value.length != 2) {
882                 status = BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
883             } else {
884                 status = cccd.setValue(device, value);
885             }
886 
887             if (responseNeeded) {
888                 mBluetoothGattServer.sendResponse(device, requestId, status, offset, value);
889             }
890         }
891     };
892 }
893