• 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.Utils;
40 import com.android.bluetooth.btservice.AdapterService;
41 import com.android.internal.annotations.GuardedBy;
42 import com.android.internal.annotations.VisibleForTesting;
43 
44 import java.io.ByteArrayOutputStream;
45 import java.nio.ByteBuffer;
46 import java.nio.ByteOrder;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.HashMap;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Objects;
53 import java.util.UUID;
54 
55 public class TbsGatt {
56 
57     private static final String TAG = "TbsGatt";
58     private static final boolean DBG = true;
59 
60     private static final String UUID_PREFIX = "0000";
61     private static final String UUID_SUFFIX = "-0000-1000-8000-00805f9b34fb";
62 
63     /* TBS assigned uuid's */
64     @VisibleForTesting
65     static final UUID UUID_TBS = makeUuid("184B");
66     @VisibleForTesting
67     public static final UUID UUID_GTBS = makeUuid("184C");
68     @VisibleForTesting
69     static final UUID UUID_BEARER_PROVIDER_NAME = makeUuid("2BB3");
70     @VisibleForTesting
71     static final UUID UUID_BEARER_UCI = makeUuid("2BB4");
72     @VisibleForTesting
73     static final UUID UUID_BEARER_TECHNOLOGY = makeUuid("2BB5");
74     @VisibleForTesting
75     static final UUID UUID_BEARER_URI_SCHEMES_SUPPORTED_LIST = makeUuid("2BB6");
76     @VisibleForTesting
77     static final UUID UUID_BEARER_LIST_CURRENT_CALLS = makeUuid("2BB9");
78     @VisibleForTesting
79     static final UUID UUID_CONTENT_CONTROL_ID = makeUuid("2BBA");
80     @VisibleForTesting
81     static final UUID UUID_STATUS_FLAGS = makeUuid("2BBB");
82     @VisibleForTesting
83     static final UUID UUID_CALL_STATE = makeUuid("2BBD");
84     @VisibleForTesting
85     static final UUID UUID_CALL_CONTROL_POINT = makeUuid("2BBE");
86     @VisibleForTesting
87     static final UUID UUID_CALL_CONTROL_POINT_OPTIONAL_OPCODES = makeUuid("2BBF");
88     @VisibleForTesting
89     static final UUID UUID_TERMINATION_REASON = makeUuid("2BC0");
90     @VisibleForTesting
91     static final UUID UUID_INCOMING_CALL = makeUuid("2BC1");
92     @VisibleForTesting
93     static final UUID UUID_CALL_FRIENDLY_NAME = makeUuid("2BC2");
94     @VisibleForTesting
95     static final UUID UUID_CLIENT_CHARACTERISTIC_CONFIGURATION = makeUuid("2902");
96 
97     @VisibleForTesting
98     static final int STATUS_FLAG_INBAND_RINGTONE_ENABLED = 0x0001;
99     @VisibleForTesting
100     static final int STATUS_FLAG_SILENT_MODE_ENABLED = 0x0002;
101 
102     @VisibleForTesting
103     static final int CALL_CONTROL_POINT_OPTIONAL_OPCODE_LOCAL_HOLD = 0x0001;
104     @VisibleForTesting
105     static final int CALL_CONTROL_POINT_OPTIONAL_OPCODE_JOIN = 0x0002;
106 
107     @VisibleForTesting
108     public static final int CALL_CONTROL_POINT_OPCODE_ACCEPT = 0x00;
109     @VisibleForTesting
110     public static final int CALL_CONTROL_POINT_OPCODE_TERMINATE = 0x01;
111     @VisibleForTesting
112     public static final int CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD = 0x02;
113     @VisibleForTesting
114     public static final int CALL_CONTROL_POINT_OPCODE_LOCAL_RETRIEVE = 0x03;
115     @VisibleForTesting
116     public static final int CALL_CONTROL_POINT_OPCODE_ORIGINATE = 0x04;
117     @VisibleForTesting
118     public static final int CALL_CONTROL_POINT_OPCODE_JOIN = 0x05;
119 
120     @VisibleForTesting
121     public static final int CALL_CONTROL_POINT_RESULT_SUCCESS = 0x00;
122     @VisibleForTesting
123     public static final int CALL_CONTROL_POINT_RESULT_OPCODE_NOT_SUPPORTED = 0x01;
124     @VisibleForTesting
125     public static final int CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE = 0x02;
126     @VisibleForTesting
127     public static final int CALL_CONTROL_POINT_RESULT_INVALID_CALL_INDEX = 0x03;
128     @VisibleForTesting
129     public static final int CALL_CONTROL_POINT_RESULT_STATE_MISMATCH = 0x04;
130     @VisibleForTesting
131     public static final int CALL_CONTROL_POINT_RESULT_LACK_OF_RESOURCES = 0x05;
132     @VisibleForTesting
133     public static final int CALL_CONTROL_POINT_RESULT_INVALID_OUTGOING_URI = 0x06;
134 
135     private final Object mPendingGattOperationsLock = new Object();
136     private final Context mContext;
137     private final GattCharacteristic mBearerProviderNameCharacteristic;
138     private final GattCharacteristic mBearerUciCharacteristic;
139     private final GattCharacteristic mBearerTechnologyCharacteristic;
140     private final GattCharacteristic mBearerUriSchemesSupportedListCharacteristic;
141     private final GattCharacteristic mBearerListCurrentCallsCharacteristic;
142     private final GattCharacteristic mContentControlIdCharacteristic;
143     private final GattCharacteristic mStatusFlagsCharacteristic;
144     private final GattCharacteristic mCallStateCharacteristic;
145     private final CallControlPointCharacteristic mCallControlPointCharacteristic;
146     private final GattCharacteristic mCallControlPointOptionalOpcodesCharacteristic;
147     private final GattCharacteristic mTerminationReasonCharacteristic;
148     private final GattCharacteristic mIncomingCallCharacteristic;
149     private final GattCharacteristic mCallFriendlyNameCharacteristic;
150     private boolean mSilentMode = false;
151     private Map<BluetoothDevice, Integer> mStatusFlagValue = new HashMap<>();
152     @GuardedBy("mPendingGattOperationsLock")
153     private Map<BluetoothDevice, List<GattOpContext>> mPendingGattOperations = new HashMap<>();
154     private BluetoothGattServerProxy mBluetoothGattServer;
155     private Handler mHandler;
156     private Callback mCallback;
157     private AdapterService mAdapterService;
158     private HashMap<BluetoothDevice, HashMap<UUID, Short>> mCccDescriptorValues;
159     private TbsService mTbsService;
160 
tbsUuidToString(UUID uuid)161     private static String tbsUuidToString(UUID uuid) {
162         if (uuid.equals(UUID_BEARER_PROVIDER_NAME)) {
163             return "BEARER_PROVIDER_NAME";
164         } else if (uuid.equals(UUID_BEARER_UCI)) {
165             return "BEARER_UCI";
166         } else if (uuid.equals(UUID_BEARER_TECHNOLOGY)) {
167             return "BEARER_TECHNOLOGY";
168         } else if (uuid.equals(UUID_BEARER_URI_SCHEMES_SUPPORTED_LIST)) {
169             return "BEARER_URI_SCHEMES_SUPPORTED_LIST";
170         } else if (uuid.equals(UUID_BEARER_LIST_CURRENT_CALLS)) {
171             return "BEARER_LIST_CURRENT_CALLS";
172         } else if (uuid.equals(UUID_CONTENT_CONTROL_ID)) {
173             return "CONTENT_CONTROL_ID";
174         } else if (uuid.equals(UUID_STATUS_FLAGS)) {
175             return "STATUS_FLAGS";
176         } else if (uuid.equals(UUID_CALL_STATE)) {
177             return "CALL_STATE";
178         } else if (uuid.equals(UUID_CALL_CONTROL_POINT)) {
179             return "CALL_CONTROL_POINT";
180         } else if (uuid.equals(UUID_CALL_CONTROL_POINT_OPTIONAL_OPCODES)) {
181             return "CALL_CONTROL_POINT_OPTIONAL_OPCODES";
182         } else if (uuid.equals(UUID_TERMINATION_REASON)) {
183             return "TERMINATION_REASON";
184         } else if (uuid.equals(UUID_INCOMING_CALL)) {
185             return "INCOMING_CALL";
186         } else if (uuid.equals(UUID_CALL_FRIENDLY_NAME)) {
187             return "CALL_FRIENDLY_NAME";
188         } else if (uuid.equals(UUID_CLIENT_CHARACTERISTIC_CONFIGURATION)) {
189             return "CLIENT_CHARACTERISTIC_CONFIGURATION";
190         } else {
191             return "UNKNOWN(" + uuid + ")";
192         }
193     }
194 
195     public static abstract class Callback {
196 
onServiceAdded(boolean success)197         public abstract void onServiceAdded(boolean success);
198 
onCallControlPointRequest(BluetoothDevice device, int opcode, byte[] args)199         public abstract void onCallControlPointRequest(BluetoothDevice device, int opcode,
200                 byte[] args);
201 
202         /**
203          * Check if device has enabled inband ringtone
204          *
205          * @param device device which is checked for inband ringtone availability
206          * @return  {@code true} if enabled, {@code false} otherwise
207          */
isInbandRingtoneEnabled(BluetoothDevice device)208         public abstract boolean isInbandRingtoneEnabled(BluetoothDevice device);
209     }
210 
211     private static class GattOpContext {
212         public enum Operation {
213             READ_CHARACTERISTIC,
214             WRITE_CHARACTERISTIC,
215             READ_DESCRIPTOR,
216             WRITE_DESCRIPTOR,
217         }
218 
GattOpContext(Operation operation, int requestId, BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value)219         GattOpContext(Operation operation, int requestId,
220                 BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor,
221                 boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
222             mOperation = operation;
223             mRequestId = requestId;
224             mCharacteristic = characteristic;
225             mDescriptor = descriptor;
226             mPreparedWrite = preparedWrite;
227             mResponseNeeded = responseNeeded;
228             mOffset = offset;
229             mValue = value;
230         }
231 
GattOpContext(Operation operation, int requestId, BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor)232         GattOpContext(Operation operation, int requestId,
233                 BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor) {
234             mOperation = operation;
235             mRequestId = requestId;
236             mCharacteristic = characteristic;
237             mDescriptor = descriptor;
238             mPreparedWrite = false;
239             mResponseNeeded = false;
240             mOffset = 0;
241             mValue = null;
242         }
243 
244         public Operation mOperation;
245         public int mRequestId;
246         public BluetoothGattCharacteristic mCharacteristic;
247         public BluetoothGattDescriptor mDescriptor;
248         public boolean mPreparedWrite;
249         public boolean mResponseNeeded;
250         public int mOffset;
251         public byte[] mValue;
252     }
253 
TbsGatt(TbsService tbsService)254     TbsGatt(TbsService tbsService) {
255         mContext = tbsService;
256         mAdapterService =  Objects.requireNonNull(AdapterService.getAdapterService(),
257                 "AdapterService shouldn't be null when creating MediaControlCattService");
258         IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager();
259         if (mgr != null) {
260             try {
261                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
262             } catch (RemoteException e) {
263                 throw e.rethrowFromSystemServer();
264             }
265         }
266 
267         mBearerProviderNameCharacteristic = new GattCharacteristic(UUID_BEARER_PROVIDER_NAME,
268                 BluetoothGattCharacteristic.PROPERTY_READ
269                         | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
270                 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
271         mBearerUciCharacteristic =
272                 new GattCharacteristic(UUID_BEARER_UCI, BluetoothGattCharacteristic.PROPERTY_READ,
273                         BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
274         mBearerTechnologyCharacteristic = new GattCharacteristic(UUID_BEARER_TECHNOLOGY,
275                 BluetoothGattCharacteristic.PROPERTY_READ
276                         | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
277                 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
278         mBearerUriSchemesSupportedListCharacteristic =
279                 new GattCharacteristic(UUID_BEARER_URI_SCHEMES_SUPPORTED_LIST,
280                         BluetoothGattCharacteristic.PROPERTY_READ
281                                 | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
282                         BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
283         mBearerListCurrentCallsCharacteristic =
284                 new GattCharacteristic(UUID_BEARER_LIST_CURRENT_CALLS,
285                         BluetoothGattCharacteristic.PROPERTY_READ
286                                 | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
287                         BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
288         mContentControlIdCharacteristic = new GattCharacteristic(UUID_CONTENT_CONTROL_ID,
289                 BluetoothGattCharacteristic.PROPERTY_READ,
290                 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
291         mStatusFlagsCharacteristic = new GattCharacteristic(UUID_STATUS_FLAGS,
292                 BluetoothGattCharacteristic.PROPERTY_READ
293                         | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
294                 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
295         mCallStateCharacteristic = new GattCharacteristic(UUID_CALL_STATE,
296                 BluetoothGattCharacteristic.PROPERTY_READ
297                         | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
298                 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
299         mCallControlPointCharacteristic = new CallControlPointCharacteristic();
300         mCallControlPointOptionalOpcodesCharacteristic = new GattCharacteristic(
301                 UUID_CALL_CONTROL_POINT_OPTIONAL_OPCODES, BluetoothGattCharacteristic.PROPERTY_READ,
302                 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
303         mTerminationReasonCharacteristic = new GattCharacteristic(UUID_TERMINATION_REASON,
304                 BluetoothGattCharacteristic.PROPERTY_NOTIFY, 0);
305         mIncomingCallCharacteristic = new GattCharacteristic(UUID_INCOMING_CALL,
306                 BluetoothGattCharacteristic.PROPERTY_READ
307                         | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
308                 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
309         mCallFriendlyNameCharacteristic = new GattCharacteristic(UUID_CALL_FRIENDLY_NAME,
310                 BluetoothGattCharacteristic.PROPERTY_READ
311                         | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
312                 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
313 
314         mTbsService = tbsService;
315         mBluetoothGattServer = null;
316     }
317 
318     @VisibleForTesting
setBluetoothGattServerForTesting(BluetoothGattServerProxy proxy)319     void setBluetoothGattServerForTesting(BluetoothGattServerProxy proxy) {
320         mBluetoothGattServer = proxy;
321     }
322 
init(int ccid, String uci, List<String> uriSchemes, boolean isLocalHoldOpcodeSupported, boolean isJoinOpcodeSupported, String providerName, int technology, Callback callback)323     public boolean init(int ccid, String uci, List<String> uriSchemes,
324             boolean isLocalHoldOpcodeSupported, boolean isJoinOpcodeSupported, String providerName,
325             int technology, Callback callback) {
326         mCccDescriptorValues = new HashMap<>();
327         mBearerProviderNameCharacteristic.setValue(providerName);
328         mBearerTechnologyCharacteristic.setValue(new byte[] {(byte) (technology & 0xFF)});
329         mBearerUciCharacteristic.setValue(uci);
330         setBearerUriSchemesSupportedList(uriSchemes);
331         mContentControlIdCharacteristic.setValue(ccid, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
332         setCallControlPointOptionalOpcodes(isLocalHoldOpcodeSupported, isJoinOpcodeSupported);
333         mStatusFlagsCharacteristic.setValue(0, BluetoothGattCharacteristic.FORMAT_UINT16, 0);
334         mCallback = callback;
335         mHandler = new Handler(Looper.getMainLooper());
336 
337         if (mBluetoothGattServer == null) {
338             mBluetoothGattServer = new BluetoothGattServerProxy(mContext);
339         }
340 
341         if (!mBluetoothGattServer.open(mGattServerCallback)) {
342             Log.e(TAG, " Could not open Gatt server");
343             return false;
344         }
345 
346         BluetoothGattService gattService =
347                 new BluetoothGattService(UUID_GTBS, BluetoothGattService.SERVICE_TYPE_PRIMARY);
348         gattService.addCharacteristic(mBearerProviderNameCharacteristic);
349         gattService.addCharacteristic(mBearerUciCharacteristic);
350         gattService.addCharacteristic(mBearerTechnologyCharacteristic);
351         gattService.addCharacteristic(mBearerUriSchemesSupportedListCharacteristic);
352         gattService.addCharacteristic(mBearerListCurrentCallsCharacteristic);
353         gattService.addCharacteristic(mContentControlIdCharacteristic);
354         gattService.addCharacteristic(mStatusFlagsCharacteristic);
355         gattService.addCharacteristic(mCallStateCharacteristic);
356         gattService.addCharacteristic(mCallControlPointCharacteristic);
357         gattService.addCharacteristic(mCallControlPointOptionalOpcodesCharacteristic);
358         gattService.addCharacteristic(mTerminationReasonCharacteristic);
359         gattService.addCharacteristic(mIncomingCallCharacteristic);
360         gattService.addCharacteristic(mCallFriendlyNameCharacteristic);
361 
362         return mBluetoothGattServer.addService(gattService);
363     }
364 
cleanup()365     public void cleanup() {
366         if (mBluetoothGattServer == null) {
367             return;
368         }
369         mBluetoothGattServer.close();
370         mBluetoothGattServer = null;
371     }
372 
getContext()373     public Context getContext() {
374         return mContext;
375     }
376 
removeUuidFromMetadata(ParcelUuid charUuid, BluetoothDevice device)377     private void removeUuidFromMetadata(ParcelUuid charUuid, BluetoothDevice device) {
378         List<ParcelUuid> uuidList;
379         byte[] gtbs_cccd = device.getMetadata(METADATA_GTBS_CCCD);
380 
381         if ((gtbs_cccd == null) || (gtbs_cccd.length == 0)) {
382             uuidList = new ArrayList<ParcelUuid>();
383         } else {
384             uuidList = new ArrayList<>(Arrays.asList(Utils.byteArrayToUuid(gtbs_cccd)));
385 
386             if (!uuidList.contains(charUuid)) {
387                 Log.d(TAG, "Characteristic CCCD can't be removed (not cached): "
388                         + charUuid.toString());
389                 return;
390             }
391         }
392 
393         uuidList.remove(charUuid);
394 
395         if (!device.setMetadata(METADATA_GTBS_CCCD,
396                 Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
397             Log.e(TAG, "Can't set CCCD for GTBS characteristic UUID: " + charUuid + ", (remove)");
398         }
399     }
400 
addUuidToMetadata(ParcelUuid charUuid, BluetoothDevice device)401     private void addUuidToMetadata(ParcelUuid charUuid, BluetoothDevice device) {
402         List<ParcelUuid> uuidList;
403         byte[] gtbs_cccd = device.getMetadata(METADATA_GTBS_CCCD);
404 
405         if ((gtbs_cccd == null) || (gtbs_cccd.length == 0)) {
406             uuidList = new ArrayList<ParcelUuid>();
407         } else {
408             uuidList = new ArrayList<>(Arrays.asList(Utils.byteArrayToUuid(gtbs_cccd)));
409 
410             if (uuidList.contains(charUuid)) {
411                 Log.d(TAG, "Characteristic CCCD already add: " + charUuid.toString());
412                 return;
413             }
414         }
415 
416         uuidList.add(charUuid);
417 
418         if (!device.setMetadata(METADATA_GTBS_CCCD,
419                 Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
420             Log.e(TAG, "Can't set CCCD for GTBS characteristic UUID: " + charUuid + ", (add)");
421         }
422     }
423 
424     @VisibleForTesting
setCcc(BluetoothDevice device, UUID charUuid, byte[] value)425     void setCcc(BluetoothDevice device, UUID charUuid, byte[] value) {
426         HashMap<UUID, Short> characteristicCcc = mCccDescriptorValues.get(device);
427         if (characteristicCcc == null) {
428             characteristicCcc = new HashMap<>();
429             mCccDescriptorValues.put(device, characteristicCcc);
430         }
431 
432         characteristicCcc.put(charUuid,
433                 ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN).getShort());
434 
435         Log.d(TAG, "setCcc, device: " + device.getAddress() + ", UUID: " + charUuid + ", value: "
436                 + characteristicCcc.get(charUuid));
437     }
438 
getCccBytes(BluetoothDevice device, UUID charUuid)439     private byte[] getCccBytes(BluetoothDevice device, UUID charUuid) {
440         Map<UUID, Short> characteristicCcc = mCccDescriptorValues.get(device);
441         if (characteristicCcc != null) {
442             ByteBuffer bb = ByteBuffer.allocate(Short.BYTES).order(ByteOrder.LITTLE_ENDIAN);
443             Short ccc = characteristicCcc.get(charUuid);
444             if (ccc != null) {
445                 bb.putShort(characteristicCcc.get(charUuid));
446                 return bb.array();
447             }
448         }
449 
450         return BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
451     }
452 
453     /** Class that handles GATT characteristic notifications */
454     private class BluetoothGattCharacteristicNotifier {
setSubscriptionConfiguration(BluetoothDevice device, UUID uuid, byte[] configuration)455         public int setSubscriptionConfiguration(BluetoothDevice device, UUID uuid,
456                 byte[] configuration) {
457             setCcc(device, uuid, configuration);
458 
459             return BluetoothGatt.GATT_SUCCESS;
460         }
461 
getSubscriptionConfiguration(BluetoothDevice device, UUID uuid)462         public byte[] getSubscriptionConfiguration(BluetoothDevice device, UUID uuid) {
463             return getCccBytes(device, uuid);
464         }
465 
isSubscribed(BluetoothDevice device, UUID uuid)466         public boolean isSubscribed(BluetoothDevice device, UUID uuid) {
467             return Arrays.equals(getCccBytes(device, uuid),
468                     BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
469         }
470 
notifyCharacteristicChanged(BluetoothDevice device, BluetoothGattCharacteristic characteristic, byte[] value)471         private void notifyCharacteristicChanged(BluetoothDevice device,
472                 BluetoothGattCharacteristic characteristic, byte[] value) {
473             if (mBluetoothGattServer != null) {
474                 mBluetoothGattServer.notifyCharacteristicChanged(device, characteristic, false,
475                                                                  value);
476             }
477         }
478 
notifyCharacteristicChanged(BluetoothDevice device, BluetoothGattCharacteristic characteristic)479         private void notifyCharacteristicChanged(BluetoothDevice device,
480                 BluetoothGattCharacteristic characteristic) {
481             if (mBluetoothGattServer != null) {
482                 mBluetoothGattServer.notifyCharacteristicChanged(device, characteristic, false);
483             }
484         }
485 
notifyWithValue(BluetoothDevice device, BluetoothGattCharacteristic characteristic, byte[] value)486         public void notifyWithValue(BluetoothDevice device,
487                 BluetoothGattCharacteristic characteristic, byte[] value) {
488             if (isSubscribed(device, characteristic.getUuid())) {
489                 notifyCharacteristicChanged(device, characteristic, value);
490             }
491         }
492 
notify(BluetoothDevice device, BluetoothGattCharacteristic characteristic)493         public void notify(BluetoothDevice device, BluetoothGattCharacteristic characteristic) {
494             if (isSubscribed(device, characteristic.getUuid())) {
495                 notifyCharacteristicChanged(device, characteristic);
496             }
497         }
498 
notifyAll(BluetoothGattCharacteristic characteristic)499         public void notifyAll(BluetoothGattCharacteristic characteristic) {
500             for (BluetoothDevice device : mCccDescriptorValues.keySet()) {
501                 notify(device, characteristic);
502             }
503         }
504     }
505 
506     /** Wrapper class for BluetoothGattCharacteristic */
507     private class GattCharacteristic extends BluetoothGattCharacteristic {
508 
509         protected BluetoothGattCharacteristicNotifier mNotifier;
510 
GattCharacteristic(UUID uuid, int properties, int permissions)511         public GattCharacteristic(UUID uuid, int properties, int permissions) {
512             super(uuid, properties, permissions);
513             if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
514                 mNotifier = new BluetoothGattCharacteristicNotifier();
515                 addDescriptor(new ClientCharacteristicConfigurationDescriptor());
516             } else {
517                 mNotifier = null;
518             }
519         }
520 
getSubscriptionConfiguration(BluetoothDevice device, UUID uuid)521         public byte[] getSubscriptionConfiguration(BluetoothDevice device, UUID uuid) {
522             return mNotifier.getSubscriptionConfiguration(device, uuid);
523         }
524 
setSubscriptionConfiguration(BluetoothDevice device, UUID uuid, byte[] configuration)525         public int setSubscriptionConfiguration(BluetoothDevice device, UUID uuid,
526                 byte[] configuration) {
527             return mNotifier.setSubscriptionConfiguration(device, uuid, configuration);
528         }
529 
isNotifiable()530         private boolean isNotifiable() {
531             return mNotifier != null;
532         }
533 
534         @Override
setValue(byte[] value)535         public boolean setValue(byte[] value) {
536             boolean success = super.setValue(value);
537             if (success && isNotifiable()) {
538                 mNotifier.notifyAll(this);
539             }
540 
541             return success;
542         }
543 
544         @Override
setValue(int value, int formatType, int offset)545         public boolean setValue(int value, int formatType, int offset) {
546             boolean success = super.setValue(value, formatType, offset);
547             if (success && isNotifiable()) {
548                 mNotifier.notifyAll(this);
549             }
550 
551             return success;
552         }
553 
554         @Override
setValue(String value)555         public boolean setValue(String value) {
556             boolean success = super.setValue(value);
557             if (success && isNotifiable()) {
558                 mNotifier.notifyAll(this);
559             }
560 
561             return success;
562         }
563 
setValueNoNotify(byte[] value)564         public boolean setValueNoNotify(byte[] value) {
565             return super.setValue(value);
566         }
567 
notifyWithValue(BluetoothDevice device, byte[] value)568         public boolean notifyWithValue(BluetoothDevice device, byte[] value) {
569             mNotifier.notifyWithValue(device, this, value);
570             return true;
571         }
572 
clearValue(boolean notify)573         public boolean clearValue(boolean notify) {
574             boolean success = super.setValue(new byte[0]);
575             if (success && notify && isNotifiable()) {
576                 mNotifier.notifyAll(this);
577             }
578 
579             return success;
580         }
581 
handleWriteRequest(BluetoothDevice device, int requestId, boolean responseNeeded, byte[] value)582         public void handleWriteRequest(BluetoothDevice device, int requestId,
583                 boolean responseNeeded, byte[] value) {
584             if (responseNeeded) {
585                 mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0,
586                         value);
587             }
588         }
589     }
590 
591     private class CallControlPointCharacteristic extends GattCharacteristic {
592 
CallControlPointCharacteristic()593         public CallControlPointCharacteristic() {
594             super(UUID_CALL_CONTROL_POINT,
595                     PROPERTY_WRITE | PROPERTY_WRITE_NO_RESPONSE | PROPERTY_NOTIFY,
596                     PERMISSION_WRITE_ENCRYPTED);
597         }
598 
599         @Override
handleWriteRequest(BluetoothDevice device, int requestId, boolean responseNeeded, byte[] value)600         public void handleWriteRequest(BluetoothDevice device, int requestId,
601                 boolean responseNeeded, byte[] value) {
602             int status;
603             if (value.length == 0) {
604                 // at least opcode is required
605                 status = BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
606             } else {
607                 status = BluetoothGatt.GATT_SUCCESS;
608             }
609 
610             if (responseNeeded) {
611                 mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0,
612                         value);
613             }
614 
615             int opcode = (int) value[0];
616             mCallback.onCallControlPointRequest(device, opcode,
617                     Arrays.copyOfRange(value, 1, value.length));
618         }
619 
setResult(BluetoothDevice device, int requestedOpcode, int callIndex, int requestResult)620         public void setResult(BluetoothDevice device, int requestedOpcode, int callIndex,
621                 int requestResult) {
622             byte[] value = new byte[3];
623             value[0] = (byte) (requestedOpcode);
624             value[1] = (byte) (callIndex);
625             value[2] = (byte) (requestResult);
626 
627             super.setValueNoNotify(value);
628 
629             // to avoid sending control point notification before write response
630             mHandler.post(() -> mNotifier.notify(device, this));
631         }
632     }
633 
634     private class ClientCharacteristicConfigurationDescriptor extends BluetoothGattDescriptor {
635 
ClientCharacteristicConfigurationDescriptor()636         ClientCharacteristicConfigurationDescriptor() {
637             super(UUID_CLIENT_CHARACTERISTIC_CONFIGURATION,
638                     PERMISSION_WRITE_ENCRYPTED | PERMISSION_READ_ENCRYPTED);
639         }
640 
getValue(BluetoothDevice device)641         public byte[] getValue(BluetoothDevice device) {
642             GattCharacteristic characteristic = (GattCharacteristic) getCharacteristic();
643             byte[] value = characteristic.getSubscriptionConfiguration(device,
644                     characteristic.getUuid());
645             if (value == null) {
646                 return BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
647             }
648 
649             return value;
650         }
651 
setValue(BluetoothDevice device, byte[] value)652         public int setValue(BluetoothDevice device, byte[] value) {
653             GattCharacteristic characteristic = (GattCharacteristic) getCharacteristic();
654             int properties = characteristic.getProperties();
655 
656             if (value.length != 2) {
657                 return BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
658 
659             } else if ((!Arrays.equals(value, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
660                     && !Arrays.equals(value, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)
661                     && !Arrays.equals(value, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE))
662                     || ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == 0 && Arrays
663                             .equals(value, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE))
664                     || ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == 0 && Arrays
665                             .equals(value, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE))) {
666                 return BluetoothGatt.GATT_FAILURE;
667             }
668 
669             if (Arrays.equals(value, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
670                 addUuidToMetadata(new ParcelUuid(characteristic.getUuid()), device);
671             } else if (Arrays.equals(value, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) {
672                 removeUuidFromMetadata(new ParcelUuid(characteristic.getUuid()), device);
673             } else {
674                 Log.e(TAG, "Not handled CCC value: " + Arrays.toString(value));
675             }
676 
677             return characteristic.setSubscriptionConfiguration(device, characteristic.getUuid(),
678                     value);
679         }
680     }
681 
setBearerProviderName(String providerName)682     public boolean setBearerProviderName(String providerName) {
683         return mBearerProviderNameCharacteristic.setValue(providerName);
684     }
685 
setBearerTechnology(int technology)686     public boolean setBearerTechnology(int technology) {
687         return mBearerTechnologyCharacteristic.setValue(technology,
688                 BluetoothGattCharacteristic.FORMAT_UINT8, 0);
689     }
690 
setBearerUriSchemesSupportedList(List<String> bearerUriSchemesSupportedList)691     public boolean setBearerUriSchemesSupportedList(List<String> bearerUriSchemesSupportedList) {
692         return mBearerUriSchemesSupportedListCharacteristic
693                 .setValue(String.join(",", bearerUriSchemesSupportedList));
694     }
695 
setCallState(Map<Integer, TbsCall> callsList)696     public boolean setCallState(Map<Integer, TbsCall> callsList) {
697         if (DBG) {
698             Log.d(TAG, "setCallState: callsList=" + callsList);
699         }
700         int i = 0;
701         byte[] value = new byte[callsList.size() * 3];
702         for (Map.Entry<Integer, TbsCall> entry : callsList.entrySet()) {
703             TbsCall call = entry.getValue();
704             value[i++] = (byte) (entry.getKey() & 0xff);
705             value[i++] = (byte) (call.getState() & 0xff);
706             value[i++] = (byte) (call.getFlags() & 0xff);
707         }
708 
709         return mCallStateCharacteristic.setValue(value);
710     }
711 
setBearerListCurrentCalls(Map<Integer, TbsCall> callsList)712     public boolean setBearerListCurrentCalls(Map<Integer, TbsCall> callsList) {
713         if (DBG) {
714             Log.d(TAG, "setBearerListCurrentCalls: callsList=" + callsList);
715         }
716         final int listItemLengthMax = Byte.MAX_VALUE;
717 
718         ByteArrayOutputStream stream = new ByteArrayOutputStream();
719         for (Map.Entry<Integer, TbsCall> entry : callsList.entrySet()) {
720             TbsCall call = entry.getValue();
721             if (call == null) {
722                 Log.w(TAG, "setBearerListCurrentCalls: call is null");
723                 continue;
724             }
725 
726             int uri_len = 0;
727             if (call.getUri() != null) {
728                 uri_len =  call.getUri().getBytes().length;
729             }
730 
731             int listItemLength = Math.min(listItemLengthMax, 3 + uri_len);
732             stream.write((byte) (listItemLength & 0xff));
733             stream.write((byte) (entry.getKey() & 0xff));
734             stream.write((byte) (call.getState() & 0xff));
735             stream.write((byte) (call.getFlags() & 0xff));
736             if (uri_len > 0) {
737                 stream.write(call.getUri().getBytes(), 0, listItemLength - 3);
738             }
739         }
740 
741         return mBearerListCurrentCallsCharacteristic.setValue(stream.toByteArray());
742     }
743 
updateStatusFlags(BluetoothDevice device, int valueInt)744     private boolean updateStatusFlags(BluetoothDevice device, int valueInt) {
745         /* uint16_t */
746         byte[] value = new byte[2];
747         value[0] = (byte) (valueInt & 0xFF);
748         value[1] = (byte) ((valueInt >> 8) & 0xFF);
749         return mStatusFlagsCharacteristic.notifyWithValue(device, value);
750     }
751 
updateStatusFlagsInbandRingtone(BluetoothDevice device, boolean set)752     private boolean updateStatusFlagsInbandRingtone(BluetoothDevice device, boolean set) {
753         boolean entryExist = mStatusFlagValue.containsKey(device);
754         if (entryExist
755                 && (((mStatusFlagValue.get(device)
756                         & STATUS_FLAG_INBAND_RINGTONE_ENABLED) != 0) == set)) {
757             Log.i(TAG, "Silent mode already set for " + device);
758             return false;
759         }
760 
761         Integer valueInt = entryExist ? mStatusFlagValue.get(device) : 0;
762         valueInt ^= STATUS_FLAG_INBAND_RINGTONE_ENABLED;
763 
764         if (entryExist) {
765             mStatusFlagValue.replace(device, valueInt);
766         } else {
767             mStatusFlagValue.put(device, valueInt);
768         }
769         return updateStatusFlags(device, valueInt);
770     }
771 
updateStatusFlagsSilentMode(boolean set)772     private boolean updateStatusFlagsSilentMode(boolean set) {
773         mSilentMode = set;
774         for (BluetoothDevice device: mCccDescriptorValues.keySet()) {
775             boolean entryExist = mStatusFlagValue.containsKey(device);
776             if (entryExist
777                     && (((mStatusFlagValue.get(device)
778                             & STATUS_FLAG_SILENT_MODE_ENABLED) != 0) == set)) {
779                 Log.i(TAG, "Silent mode already set for " + device);
780                 continue;
781             }
782 
783             Integer valueInt = entryExist ? mStatusFlagValue.get(device) : 0;
784             valueInt ^= STATUS_FLAG_SILENT_MODE_ENABLED;
785 
786             if (entryExist) {
787                 mStatusFlagValue.replace(device, valueInt);
788             } else {
789                 mStatusFlagValue.put(device, valueInt);
790             }
791             updateStatusFlags(device, valueInt);
792         }
793         return true;
794     }
795 
796     /**
797      * Set inband ringtone for the device.
798      * When set, notification will be sent to given device.
799      *
800      * @param device    device for which inband ringtone has been set
801      * @return          true, when notification has been sent, false otherwise
802      */
setInbandRingtoneFlag(BluetoothDevice device)803     public boolean setInbandRingtoneFlag(BluetoothDevice device) {
804         return updateStatusFlagsInbandRingtone(device, true);
805     }
806 
807     /**
808      * Clear inband ringtone for the device.
809      * When set, notification will be sent to given device.
810      *
811      * @param device    device for which inband ringtone has been cleared
812      * @return          true, when notification has been sent, false otherwise
813      */
clearInbandRingtoneFlag(BluetoothDevice device)814     public boolean clearInbandRingtoneFlag(BluetoothDevice device) {
815         return updateStatusFlagsInbandRingtone(device, false);
816     }
setSilentModeFlag()817     public boolean setSilentModeFlag() {
818         return updateStatusFlagsSilentMode(true);
819     }
820 
clearSilentModeFlag()821     public boolean clearSilentModeFlag() {
822         return updateStatusFlagsSilentMode(false);
823     }
824 
setCallControlPointOptionalOpcodes(boolean isLocalHoldOpcodeSupported, boolean isJoinOpcodeSupported)825     private void setCallControlPointOptionalOpcodes(boolean isLocalHoldOpcodeSupported,
826             boolean isJoinOpcodeSupported) {
827         int valueInt = 0;
828         if (isLocalHoldOpcodeSupported) {
829             valueInt |= CALL_CONTROL_POINT_OPTIONAL_OPCODE_LOCAL_HOLD;
830         }
831         if (isJoinOpcodeSupported) {
832             valueInt |= CALL_CONTROL_POINT_OPTIONAL_OPCODE_JOIN;
833         }
834 
835         byte[] value = new byte[2];
836         value[0] = (byte) (valueInt & 0xff);
837         value[1] = (byte) ((valueInt >> 8) & 0xff);
838 
839         mCallControlPointOptionalOpcodesCharacteristic.setValue(value);
840     }
841 
setTerminationReason(int callIndex, int terminationReason)842     public boolean setTerminationReason(int callIndex, int terminationReason) {
843         if (DBG) {
844             Log.d(TAG, "setTerminationReason: callIndex=" + callIndex + " terminationReason="
845                     + terminationReason);
846         }
847         byte[] value = new byte[2];
848         value[0] = (byte) (callIndex & 0xff);
849         value[1] = (byte) (terminationReason & 0xff);
850 
851         return mTerminationReasonCharacteristic.setValue(value);
852     }
853 
getIncomingCallIndex()854     public Integer getIncomingCallIndex() {
855         byte[] value = mIncomingCallCharacteristic.getValue();
856         if (value == null || value.length == 0) {
857             return null;
858         }
859 
860         return (int) value[0];
861     }
862 
setIncomingCall(int callIndex, String uri)863     public boolean setIncomingCall(int callIndex, String uri) {
864         if (DBG) {
865             Log.d(TAG, "setIncomingCall: callIndex=" + callIndex + " uri=" + uri);
866         }
867         int uri_len = 0;
868         if (uri != null) {
869             uri_len = uri.length();
870         }
871 
872         byte[] value = new byte[uri_len + 1];
873         value[0] = (byte) (callIndex & 0xff);
874 
875         if (uri_len > 0) {
876             System.arraycopy(uri.getBytes(), 0, value, 1, uri_len);
877         }
878 
879         return mIncomingCallCharacteristic.setValue(value);
880     }
881 
clearIncomingCall()882     public boolean clearIncomingCall() {
883         if (DBG) {
884             Log.d(TAG, "clearIncomingCall");
885         }
886         return mIncomingCallCharacteristic.clearValue(false);
887     }
888 
setCallFriendlyName(int callIndex, String callFriendlyName)889     public boolean setCallFriendlyName(int callIndex, String callFriendlyName) {
890         if (DBG) {
891             Log.d(TAG, "setCallFriendlyName: callIndex=" + callIndex + "callFriendlyName="
892                     + callFriendlyName);
893         }
894         byte[] value = new byte[callFriendlyName.length() + 1];
895         value[0] = (byte) (callIndex & 0xff);
896         System.arraycopy(callFriendlyName.getBytes(), 0, value, 1, callFriendlyName.length());
897 
898         return mCallFriendlyNameCharacteristic.setValue(value);
899     }
900 
getCallFriendlyNameIndex()901     public Integer getCallFriendlyNameIndex() {
902         byte[] value = mCallFriendlyNameCharacteristic.getValue();
903         if (value == null || value.length == 0) {
904             return null;
905         }
906 
907         return (int) value[0];
908     }
909 
clearFriendlyName()910     public boolean clearFriendlyName() {
911         if (DBG) {
912             Log.d(TAG, "clearFriendlyName");
913         }
914         return mCallFriendlyNameCharacteristic.clearValue(false);
915     }
916 
setCallControlPointResult(BluetoothDevice device, int requestedOpcode, int callIndex, int requestResult)917     public void setCallControlPointResult(BluetoothDevice device, int requestedOpcode,
918             int callIndex, int requestResult) {
919         if (DBG) {
920             Log.d(TAG,
921                     "setCallControlPointResult: device=" + device + " requestedOpcode="
922                             + requestedOpcode + " callIndex=" + callIndex + " requesuResult="
923                             + requestResult);
924         }
925         mCallControlPointCharacteristic.setResult(device, requestedOpcode, callIndex,
926                 requestResult);
927     }
928 
makeUuid(String uuid16)929     private static UUID makeUuid(String uuid16) {
930         return UUID.fromString(UUID_PREFIX + uuid16 + UUID_SUFFIX);
931     }
932 
restoreCccValuesForStoredDevices()933     private void restoreCccValuesForStoredDevices() {
934         BluetoothGattService gattService = mBluetoothGattServer.getService(UUID_GTBS);
935 
936         for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
937             byte[] gtbs_cccd = device.getMetadata(METADATA_GTBS_CCCD);
938 
939             if ((gtbs_cccd == null) || (gtbs_cccd.length == 0)) {
940                 return;
941             }
942 
943             List<ParcelUuid> uuidList = Arrays.asList(Utils.byteArrayToUuid(gtbs_cccd));
944 
945             /* Restore CCCD values for device */
946             for (ParcelUuid uuid : uuidList) {
947                 BluetoothGattCharacteristic characteristic =
948                         gattService.getCharacteristic(uuid.getUuid());
949                 if (characteristic == null) {
950                     Log.e(TAG, "Invalid UUID stored in metadata: " + uuid.toString());
951                     continue;
952                 }
953 
954                 BluetoothGattDescriptor descriptor =
955                         characteristic.getDescriptor(UUID_CLIENT_CHARACTERISTIC_CONFIGURATION);
956                 if (descriptor == null) {
957                     Log.e(TAG, "Invalid characteristic, does not include CCCD");
958                     continue;
959                 }
960 
961                 descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
962             }
963         }
964     }
965 
966     private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
967             new IBluetoothStateChangeCallback.Stub() {
968                 public void onBluetoothStateChange(boolean up) {
969                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
970                     if (up) {
971                         restoreCccValuesForStoredDevices();
972                     }
973                 }
974             };
975 
getDeviceAuthorization(BluetoothDevice device)976     private int getDeviceAuthorization(BluetoothDevice device) {
977         return mTbsService.getDeviceAuthorization(device);
978     }
979 
onRejectedAuthorizationGattOperation(BluetoothDevice device, GattOpContext op)980     private void onRejectedAuthorizationGattOperation(BluetoothDevice device, GattOpContext op) {
981         Log.w(TAG, "onRejectedAuthorizationGattOperation device: " + device);
982 
983         switch (op.mOperation) {
984             case READ_CHARACTERISTIC:
985             case READ_DESCRIPTOR:
986                 mBluetoothGattServer.sendResponse(device, op.mRequestId,
987                         BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION, op.mOffset, null);
988                 break;
989             case WRITE_CHARACTERISTIC:
990                 if (op.mResponseNeeded) {
991                     mBluetoothGattServer.sendResponse(device, op.mRequestId,
992                             BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION, op.mOffset, null);
993                 } else {
994                     // In case of control point operations we can send an application error code
995                     if (op.mCharacteristic.getUuid().equals(UUID_CALL_CONTROL_POINT)) {
996                         setCallControlPointResult(device, op.mOperation.ordinal(), 0,
997                                 TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE);
998                     }
999                 }
1000                 break;
1001             case WRITE_DESCRIPTOR:
1002                 if (op.mResponseNeeded) {
1003                     mBluetoothGattServer.sendResponse(device, op.mRequestId,
1004                             BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION, op.mOffset, null);
1005                 }
1006                 break;
1007 
1008             default:
1009                 break;
1010         }
1011     }
1012 
onUnauthorizedGattOperation(BluetoothDevice device, GattOpContext op)1013     private void onUnauthorizedGattOperation(BluetoothDevice device, GattOpContext op) {
1014         if (DBG) {
1015             Log.d(TAG, "onUnauthorizedGattOperation device: " + device);
1016         }
1017 
1018         synchronized (mPendingGattOperationsLock) {
1019             List<GattOpContext> operations = mPendingGattOperations.get(device);
1020             if (operations == null) {
1021                 operations = new ArrayList<>();
1022                 mPendingGattOperations.put(device, operations);
1023             }
1024 
1025             operations.add(op);
1026             // Send authorization request for each device only for it's first GATT request
1027             if (operations.size() == 1) {
1028                 mTbsService.onDeviceUnauthorized(device);
1029             }
1030         }
1031     }
1032 
onAuthorizedGattOperation(BluetoothDevice device, GattOpContext op)1033     private void onAuthorizedGattOperation(BluetoothDevice device, GattOpContext op) {
1034         int status = BluetoothGatt.GATT_SUCCESS;
1035         ClientCharacteristicConfigurationDescriptor cccd;
1036         byte[] value;
1037 
1038         if (DBG) {
1039             Log.d(TAG, "onAuthorizedGattOperation device: " + device);
1040         }
1041 
1042         switch (op.mOperation) {
1043             case READ_CHARACTERISTIC:
1044                 if (DBG) {
1045                     Log.d(TAG, "onCharacteristicReadRequest: device=" + device);
1046                 }
1047 
1048                 if (getDeviceAuthorization(device) != BluetoothDevice.ACCESS_ALLOWED) {
1049                     onRejectedAuthorizationGattOperation(device, op);
1050                     return;
1051                 }
1052 
1053                 if (op.mCharacteristic.getUuid().equals(UUID_STATUS_FLAGS)) {
1054                     value = new byte[2];
1055                     int valueInt = mSilentMode ? STATUS_FLAG_SILENT_MODE_ENABLED : 0;
1056                     if (mStatusFlagValue.containsKey(device)) {
1057                         valueInt = mStatusFlagValue.get(device);
1058                     } else if (mCallback.isInbandRingtoneEnabled(device)) {
1059                         valueInt |= STATUS_FLAG_INBAND_RINGTONE_ENABLED;
1060                     }
1061                     value[0] = (byte) (valueInt & 0xFF);
1062                     value[1] = (byte) ((valueInt >> 8) & 0xFF);
1063                 } else {
1064                     GattCharacteristic gattCharacteristic = (GattCharacteristic) op.mCharacteristic;
1065                     value = gattCharacteristic.getValue();
1066                     if (value == null) {
1067                         value = new byte[0];
1068                     }
1069                 }
1070 
1071                 if (value.length < op.mOffset) {
1072                     status = BluetoothGatt.GATT_INVALID_OFFSET;
1073                 } else {
1074                     value = Arrays.copyOfRange(value, op.mOffset, value.length);
1075                     status = BluetoothGatt.GATT_SUCCESS;
1076                 }
1077 
1078                 mBluetoothGattServer.sendResponse(device, op.mRequestId, status, op.mOffset, value);
1079                 break;
1080 
1081             case WRITE_CHARACTERISTIC:
1082                 if (DBG) {
1083                     Log.d(TAG, "onCharacteristicWriteRequest: device=" + device);
1084                 }
1085 
1086                 if (getDeviceAuthorization(device) != BluetoothDevice.ACCESS_ALLOWED) {
1087                     onRejectedAuthorizationGattOperation(device, op);
1088                     return;
1089                 }
1090 
1091                 GattCharacteristic gattCharacteristic = (GattCharacteristic) op.mCharacteristic;
1092                 if (op.mPreparedWrite) {
1093                     status = BluetoothGatt.GATT_FAILURE;
1094                 } else if (op.mOffset > 0) {
1095                     status = BluetoothGatt.GATT_INVALID_OFFSET;
1096                 } else {
1097                     gattCharacteristic.handleWriteRequest(device, op.mRequestId, op.mResponseNeeded,
1098                             op.mValue);
1099                     return;
1100                 }
1101 
1102                 if (op.mResponseNeeded) {
1103                     mBluetoothGattServer.sendResponse(device, op.mRequestId, status, op.mOffset,
1104                             op.mValue);
1105                 }
1106                 break;
1107 
1108             case READ_DESCRIPTOR:
1109                 if (DBG) {
1110                     Log.d(TAG, "onDescriptorReadRequest: device=" + device);
1111                 }
1112 
1113                 if (getDeviceAuthorization(device) != BluetoothDevice.ACCESS_ALLOWED) {
1114                     onRejectedAuthorizationGattOperation(device, op);
1115                     return;
1116                 }
1117 
1118                 cccd = (ClientCharacteristicConfigurationDescriptor) op.mDescriptor;
1119                 value = cccd.getValue(device);
1120                 if (value.length < op.mOffset) {
1121                     status = BluetoothGatt.GATT_INVALID_OFFSET;
1122                 } else {
1123                     value = Arrays.copyOfRange(value, op.mOffset, value.length);
1124                     status = BluetoothGatt.GATT_SUCCESS;
1125                 }
1126 
1127                 mBluetoothGattServer.sendResponse(device, op.mRequestId, status, op.mOffset, value);
1128                 break;
1129 
1130             case WRITE_DESCRIPTOR:
1131                 if (DBG) {
1132                     Log.d(TAG, "onDescriptorWriteRequest: device=" + device);
1133                 }
1134 
1135                 if (getDeviceAuthorization(device) != BluetoothDevice.ACCESS_ALLOWED) {
1136                     onRejectedAuthorizationGattOperation(device, op);
1137                     return;
1138                 }
1139 
1140                 cccd = (ClientCharacteristicConfigurationDescriptor) op.mDescriptor;
1141                 if (op.mPreparedWrite) {
1142                     // TODO: handle prepareWrite
1143                     status = BluetoothGatt.GATT_FAILURE;
1144                 } else if (op.mOffset > 0) {
1145                     status = BluetoothGatt.GATT_INVALID_OFFSET;
1146                 } else if (op.mValue.length != 2) {
1147                     status = BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
1148                 } else {
1149                     status = cccd.setValue(device, op.mValue);
1150                 }
1151 
1152                 if (op.mResponseNeeded) {
1153                     mBluetoothGattServer.sendResponse(device, op.mRequestId, status, op.mOffset,
1154                             op.mValue);
1155                 }
1156                 break;
1157 
1158             default:
1159                 break;
1160         }
1161     }
1162 
1163     /**
1164      * Callback for TBS GATT instance about authorization change for device.
1165      *
1166      * @param device device for which authorization is changed
1167      */
onDeviceAuthorizationSet(BluetoothDevice device)1168     public void onDeviceAuthorizationSet(BluetoothDevice device) {
1169         processPendingGattOperations(device);
1170     }
1171 
clearUnauthorizedGattOperationss(BluetoothDevice device)1172     private void clearUnauthorizedGattOperationss(BluetoothDevice device) {
1173         if (DBG) {
1174             Log.d(TAG, "clearUnauthorizedGattOperationss device: " + device);
1175         }
1176 
1177         synchronized (mPendingGattOperationsLock) {
1178             mPendingGattOperations.remove(device);
1179         }
1180     }
1181 
processPendingGattOperations(BluetoothDevice device)1182     private void processPendingGattOperations(BluetoothDevice device) {
1183         if (DBG) {
1184             Log.d(TAG, "processPendingGattOperations device: " + device);
1185         }
1186 
1187         synchronized (mPendingGattOperationsLock) {
1188             if (mPendingGattOperations.containsKey(device)) {
1189                 if (getDeviceAuthorization(device) == BluetoothDevice.ACCESS_ALLOWED) {
1190                     for (GattOpContext op : mPendingGattOperations.get(device)) {
1191                         onAuthorizedGattOperation(device, op);
1192                     }
1193                 } else {
1194                     for (GattOpContext op : mPendingGattOperations.get(device)) {
1195                         onRejectedAuthorizationGattOperation(device, op);
1196                     }
1197                 }
1198                 clearUnauthorizedGattOperationss(device);
1199             }
1200         }
1201     }
1202 
1203     /**
1204      * Callback to handle incoming requests to the GATT server. All read/write requests for
1205      * characteristics and descriptors are handled here.
1206      */
1207     @VisibleForTesting
1208     final BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
1209         @Override
1210         public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
1211             super.onConnectionStateChange(device, status, newState);
1212             if (DBG) {
1213                 Log.d(TAG, "BluetoothGattServerCallback: onConnectionStateChange");
1214             }
1215             if (newState == BluetoothProfile.STATE_DISCONNECTED) {
1216                 clearUnauthorizedGattOperationss(device);
1217             }
1218         }
1219 
1220         @Override
1221         public void onServiceAdded(int status, BluetoothGattService service) {
1222             if (DBG) {
1223                 Log.d(TAG, "onServiceAdded: status=" + status);
1224             }
1225             if (mCallback != null) {
1226                 mCallback.onServiceAdded(status == BluetoothGatt.GATT_SUCCESS);
1227             }
1228 
1229             restoreCccValuesForStoredDevices();
1230         }
1231 
1232         @Override
1233         public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset,
1234                 BluetoothGattCharacteristic characteristic) {
1235             super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
1236             if (DBG) {
1237                 Log.d(TAG, "BluetoothGattServerCallback: onCharacteristicReadRequest offset= "
1238                         + offset + " entire value= " + Arrays.toString(characteristic.getValue()));
1239             }
1240 
1241             if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) == 0) {
1242                 mBluetoothGattServer.sendResponse(device, requestId,
1243                         BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, offset, null);
1244                 return;
1245             }
1246 
1247             GattOpContext op = new GattOpContext(
1248                     GattOpContext.Operation.READ_CHARACTERISTIC, requestId, characteristic, null);
1249             switch (getDeviceAuthorization(device)) {
1250                 case BluetoothDevice.ACCESS_REJECTED:
1251                     onRejectedAuthorizationGattOperation(device, op);
1252                     break;
1253                 case BluetoothDevice.ACCESS_UNKNOWN:
1254                     onUnauthorizedGattOperation(device, op);
1255                     break;
1256                 default:
1257                     onAuthorizedGattOperation(device, op);
1258                     break;
1259             }
1260         }
1261 
1262         @Override
1263         public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
1264                 BluetoothGattCharacteristic characteristic, boolean preparedWrite,
1265                 boolean responseNeeded, int offset, byte[] value) {
1266             super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite,
1267                     responseNeeded, offset, value);
1268             if (DBG) {
1269                 Log.d(TAG,
1270                         "BluetoothGattServerCallback: "
1271                                 + "onCharacteristicWriteRequest");
1272             }
1273 
1274             if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE)
1275                     == 0) {
1276                 mBluetoothGattServer.sendResponse(
1277                         device, requestId, BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, offset, value);
1278                 return;
1279             }
1280 
1281             GattOpContext op = new GattOpContext(GattOpContext.Operation.WRITE_CHARACTERISTIC,
1282                     requestId, characteristic, null, preparedWrite, responseNeeded, offset, value);
1283             switch (getDeviceAuthorization(device)) {
1284                 case BluetoothDevice.ACCESS_REJECTED:
1285                     onRejectedAuthorizationGattOperation(device, op);
1286                     break;
1287                 case BluetoothDevice.ACCESS_UNKNOWN:
1288                     onUnauthorizedGattOperation(device, op);
1289                     break;
1290                 default:
1291                     onAuthorizedGattOperation(device, op);
1292                     break;
1293             }
1294         }
1295 
1296         @Override
1297         public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset,
1298                 BluetoothGattDescriptor descriptor) {
1299             super.onDescriptorReadRequest(device, requestId, offset, descriptor);
1300             if (DBG) {
1301                 Log.d(TAG,
1302                         "BluetoothGattServerCallback: "
1303                                 + "onDescriptorReadRequest");
1304             }
1305 
1306             if ((descriptor.getPermissions() & BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED)
1307                     == 0) {
1308                 mBluetoothGattServer.sendResponse(
1309                         device, requestId, BluetoothGatt.GATT_READ_NOT_PERMITTED, offset, null);
1310                 return;
1311             }
1312 
1313             GattOpContext op = new GattOpContext(
1314                     GattOpContext.Operation.READ_DESCRIPTOR, requestId, null, descriptor);
1315             switch (getDeviceAuthorization(device)) {
1316                 case BluetoothDevice.ACCESS_REJECTED:
1317                     onRejectedAuthorizationGattOperation(device, op);
1318                     break;
1319                 case BluetoothDevice.ACCESS_UNKNOWN:
1320                     onUnauthorizedGattOperation(device, op);
1321                     break;
1322                 default:
1323                     onAuthorizedGattOperation(device, op);
1324                     break;
1325             }
1326         }
1327 
1328         @Override
1329         public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
1330                 BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded,
1331                 int offset, byte[] value) {
1332             super.onDescriptorWriteRequest(
1333                     device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
1334             if (DBG) {
1335                 Log.d(TAG,
1336                         "BluetoothGattServerCallback: "
1337                                 + "onDescriptorWriteRequest");
1338             }
1339 
1340             if ((descriptor.getPermissions() & BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED)
1341                     == 0) {
1342                 mBluetoothGattServer.sendResponse(
1343                         device, requestId, BluetoothGatt.GATT_WRITE_NOT_PERMITTED, offset, value);
1344                 return;
1345             }
1346 
1347             GattOpContext op = new GattOpContext(GattOpContext.Operation.WRITE_DESCRIPTOR,
1348                     requestId, null, descriptor, preparedWrite, responseNeeded, offset, value);
1349             switch (getDeviceAuthorization(device)) {
1350                 case BluetoothDevice.ACCESS_REJECTED:
1351                     onRejectedAuthorizationGattOperation(device, op);
1352                     break;
1353                 case BluetoothDevice.ACCESS_UNKNOWN:
1354                     onUnauthorizedGattOperation(device, op);
1355                     break;
1356                 default:
1357                     onAuthorizedGattOperation(device, op);
1358                     break;
1359             }
1360         }
1361     };
1362 
dump(StringBuilder sb)1363     public void dump(StringBuilder sb) {
1364         sb.append("\n\tSilent mode: " + mSilentMode);
1365 
1366         for (Map.Entry<BluetoothDevice, HashMap<UUID, Short>> deviceEntry
1367                 : mCccDescriptorValues.entrySet()) {
1368             sb.append("\n\tCCC states for device: " + deviceEntry.getKey());
1369             for (Map.Entry<UUID, Short> entry : deviceEntry.getValue().entrySet()) {
1370                 sb.append("\n\t\tCharacteristic: " + tbsUuidToString(entry.getKey()) + ", value: "
1371                         + Utils.cccIntToStr(entry.getValue()));
1372             }
1373         }
1374     }
1375 }
1376