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