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