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