1 /* 2 * Copyright 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bluetooth.btservice.storage; 18 19 import android.bluetooth.BluetoothA2dp; 20 import android.bluetooth.BluetoothA2dp.OptionalCodecsPreferenceStatus; 21 import android.bluetooth.BluetoothA2dp.OptionalCodecsSupportStatus; 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothProfile; 25 import android.bluetooth.BluetoothProtoEnums; 26 import android.bluetooth.BluetoothSinkAudioPolicy; 27 import android.bluetooth.BluetoothStatusCodes; 28 import android.content.BroadcastReceiver; 29 import android.content.ContentResolver; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.os.Binder; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.os.HandlerThread; 37 import android.os.Looper; 38 import android.os.Message; 39 import android.provider.Settings; 40 import android.util.Log; 41 42 import com.android.bluetooth.BluetoothStatsLog; 43 import com.android.bluetooth.Utils; 44 import com.android.bluetooth.btservice.AdapterService; 45 import com.android.internal.annotations.GuardedBy; 46 import com.android.internal.annotations.VisibleForTesting; 47 48 import com.google.common.collect.EvictingQueue; 49 50 import java.io.PrintWriter; 51 import java.util.ArrayList; 52 import java.util.Arrays; 53 import java.util.HashMap; 54 import java.util.List; 55 import java.util.Locale; 56 import java.util.Map; 57 import java.util.Objects; 58 import java.util.concurrent.Semaphore; 59 import java.util.concurrent.TimeUnit; 60 61 /** 62 * The active device manager is responsible to handle a Room database 63 * for Bluetooth persistent data. 64 */ 65 public class DatabaseManager { 66 private static final String TAG = "BluetoothDatabase"; 67 68 private AdapterService mAdapterService = null; 69 private HandlerThread mHandlerThread = null; 70 private Handler mHandler = null; 71 private final Object mDatabaseLock = new Object(); 72 private @GuardedBy("mDatabaseLock") MetadataDatabase mDatabase = null; 73 private boolean mMigratedFromSettingsGlobal = false; 74 75 @VisibleForTesting 76 final Map<String, Metadata> mMetadataCache = new HashMap<>(); 77 private final Semaphore mSemaphore = new Semaphore(1); 78 private static final int METADATA_CHANGED_LOG_MAX_SIZE = 20; 79 private final EvictingQueue<String> mMetadataChangedLog; 80 81 private static final int LOAD_DATABASE_TIMEOUT = 500; // milliseconds 82 private static final int MSG_LOAD_DATABASE = 0; 83 private static final int MSG_UPDATE_DATABASE = 1; 84 private static final int MSG_DELETE_DATABASE = 2; 85 private static final int MSG_CLEAR_DATABASE = 100; 86 private static final String LOCAL_STORAGE = "LocalStorage"; 87 88 private static final String 89 LEGACY_HEADSET_PRIORITY_PREFIX = "bluetooth_headset_priority_"; 90 private static final String 91 LEGACY_A2DP_SINK_PRIORITY_PREFIX = "bluetooth_a2dp_sink_priority_"; 92 private static final String 93 LEGACY_A2DP_SRC_PRIORITY_PREFIX = "bluetooth_a2dp_src_priority_"; 94 private static final String LEGACY_A2DP_SUPPORTS_OPTIONAL_CODECS_PREFIX = 95 "bluetooth_a2dp_supports_optional_codecs_"; 96 private static final String LEGACY_A2DP_OPTIONAL_CODECS_ENABLED_PREFIX = 97 "bluetooth_a2dp_optional_codecs_enabled_"; 98 private static final String 99 LEGACY_INPUT_DEVICE_PRIORITY_PREFIX = "bluetooth_input_device_priority_"; 100 private static final String 101 LEGACY_MAP_PRIORITY_PREFIX = "bluetooth_map_priority_"; 102 private static final String 103 LEGACY_MAP_CLIENT_PRIORITY_PREFIX = "bluetooth_map_client_priority_"; 104 private static final String 105 LEGACY_PBAP_CLIENT_PRIORITY_PREFIX = "bluetooth_pbap_client_priority_"; 106 private static final String 107 LEGACY_SAP_PRIORITY_PREFIX = "bluetooth_sap_priority_"; 108 private static final String 109 LEGACY_PAN_PRIORITY_PREFIX = "bluetooth_pan_priority_"; 110 private static final String 111 LEGACY_HEARING_AID_PRIORITY_PREFIX = "bluetooth_hearing_aid_priority_"; 112 113 /** 114 * Constructor of the DatabaseManager 115 */ DatabaseManager(AdapterService service)116 public DatabaseManager(AdapterService service) { 117 mAdapterService = service; 118 mMetadataChangedLog = EvictingQueue.create(METADATA_CHANGED_LOG_MAX_SIZE); 119 } 120 121 class DatabaseHandler extends Handler { DatabaseHandler(Looper looper)122 DatabaseHandler(Looper looper) { 123 super(looper); 124 } 125 126 @Override handleMessage(Message msg)127 public void handleMessage(Message msg) { 128 switch (msg.what) { 129 case MSG_LOAD_DATABASE: { 130 synchronized (mDatabaseLock) { 131 List<Metadata> list; 132 try { 133 list = mDatabase.load(); 134 } catch (IllegalStateException e) { 135 Log.e(TAG, "Unable to open database: " + e); 136 mDatabase = MetadataDatabase 137 .createDatabaseWithoutMigration(mAdapterService); 138 list = mDatabase.load(); 139 } 140 compactLastConnectionTime(list); 141 cacheMetadata(list); 142 } 143 break; 144 } 145 case MSG_UPDATE_DATABASE: { 146 Metadata data = (Metadata) msg.obj; 147 synchronized (mDatabaseLock) { 148 mDatabase.insert(data); 149 } 150 break; 151 } 152 case MSG_DELETE_DATABASE: { 153 String address = (String) msg.obj; 154 synchronized (mDatabaseLock) { 155 mDatabase.delete(address); 156 } 157 break; 158 } 159 case MSG_CLEAR_DATABASE: { 160 synchronized (mDatabaseLock) { 161 mDatabase.deleteAll(); 162 } 163 break; 164 } 165 } 166 } 167 } 168 169 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 170 @Override 171 public void onReceive(Context context, Intent intent) { 172 String action = intent.getAction(); 173 if (action == null) { 174 Log.e(TAG, "Received intent with null action"); 175 return; 176 } 177 switch (action) { 178 case BluetoothDevice.ACTION_BOND_STATE_CHANGED: { 179 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 180 BluetoothDevice.ERROR); 181 BluetoothDevice device = 182 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 183 Objects.requireNonNull(device, 184 "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); 185 bondStateChanged(device, state); 186 break; 187 } 188 case BluetoothAdapter.ACTION_STATE_CHANGED: { 189 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 190 BluetoothAdapter.STATE_OFF); 191 if (!mMigratedFromSettingsGlobal 192 && state == BluetoothAdapter.STATE_TURNING_ON) { 193 migrateSettingsGlobal(); 194 } 195 break; 196 } 197 } 198 } 199 }; 200 bondStateChanged(BluetoothDevice device, int state)201 void bondStateChanged(BluetoothDevice device, int state) { 202 synchronized (mMetadataCache) { 203 String address = device.getAddress(); 204 if (state != BluetoothDevice.BOND_NONE) { 205 if (mMetadataCache.containsKey(address)) { 206 return; 207 } 208 createMetadata(address, false); 209 } else { 210 Metadata metadata = mMetadataCache.get(address); 211 if (metadata != null) { 212 mMetadataCache.remove(address); 213 deleteDatabase(metadata); 214 } 215 } 216 } 217 } 218 isValidMetaKey(int key)219 boolean isValidMetaKey(int key) { 220 if (key >= 0 && key <= BluetoothDevice.getMaxMetadataKey()) { 221 return true; 222 } 223 Log.w(TAG, "Invalid metadata key " + key); 224 return false; 225 } 226 227 /** 228 * Set customized metadata to database with requested key 229 */ 230 @VisibleForTesting setCustomMeta(BluetoothDevice device, int key, byte[] newValue)231 public boolean setCustomMeta(BluetoothDevice device, int key, byte[] newValue) { 232 synchronized (mMetadataCache) { 233 if (device == null) { 234 Log.e(TAG, "setCustomMeta: device is null"); 235 return false; 236 } 237 if (!isValidMetaKey(key)) { 238 Log.e(TAG, "setCustomMeta: meta key invalid " + key); 239 return false; 240 } 241 242 String address = device.getAddress(); 243 if (!mMetadataCache.containsKey(address)) { 244 createMetadata(address, false); 245 } 246 Metadata data = mMetadataCache.get(address); 247 byte[] oldValue = data.getCustomizedMeta(key); 248 if (oldValue != null && Arrays.equals(oldValue, newValue)) { 249 Log.v(TAG, "setCustomMeta: metadata not changed."); 250 return true; 251 } 252 logManufacturerInfo(device, key, newValue); 253 logMetadataChange(data, "setCustomMeta key=" + key); 254 data.setCustomizedMeta(key, newValue); 255 256 updateDatabase(data); 257 mAdapterService.metadataChanged(address, key, newValue); 258 return true; 259 } 260 } 261 262 /** 263 * Get customized metadata from database with requested key 264 */ 265 @VisibleForTesting getCustomMeta(BluetoothDevice device, int key)266 public byte[] getCustomMeta(BluetoothDevice device, int key) { 267 synchronized (mMetadataCache) { 268 if (device == null) { 269 Log.e(TAG, "getCustomMeta: device is null"); 270 return null; 271 } 272 if (!isValidMetaKey(key)) { 273 Log.e(TAG, "getCustomMeta: meta key invalid " + key); 274 return null; 275 } 276 277 String address = device.getAddress(); 278 279 if (!mMetadataCache.containsKey(address)) { 280 Log.d(TAG, "getCustomMeta: device " + device + " is not in cache"); 281 return null; 282 } 283 284 Metadata data = mMetadataCache.get(address); 285 return data.getCustomizedMeta(key); 286 } 287 } 288 289 /** 290 * Set audio policy metadata to database with requested key 291 */ 292 @VisibleForTesting setAudioPolicyMetadata(BluetoothDevice device, BluetoothSinkAudioPolicy policies)293 public boolean setAudioPolicyMetadata(BluetoothDevice device, 294 BluetoothSinkAudioPolicy policies) { 295 synchronized (mMetadataCache) { 296 if (device == null) { 297 Log.e(TAG, "setAudioPolicyMetadata: device is null"); 298 return false; 299 } 300 301 String address = device.getAddress(); 302 if (!mMetadataCache.containsKey(address)) { 303 createMetadata(address, false); 304 } 305 Metadata data = mMetadataCache.get(address); 306 AudioPolicyEntity entity = data.audioPolicyMetadata; 307 entity.callEstablishAudioPolicy = policies.getCallEstablishPolicy(); 308 entity.connectingTimeAudioPolicy = policies.getActiveDevicePolicyAfterConnection(); 309 entity.inBandRingtoneAudioPolicy = policies.getInBandRingtonePolicy(); 310 311 updateDatabase(data); 312 return true; 313 } 314 } 315 316 /** 317 * Get audio policy metadata from database with requested key 318 */ 319 @VisibleForTesting getAudioPolicyMetadata(BluetoothDevice device)320 public BluetoothSinkAudioPolicy getAudioPolicyMetadata(BluetoothDevice device) { 321 synchronized (mMetadataCache) { 322 if (device == null) { 323 Log.e(TAG, "getAudioPolicyMetadata: device is null"); 324 return null; 325 } 326 327 String address = device.getAddress(); 328 329 if (!mMetadataCache.containsKey(address)) { 330 Log.d(TAG, "getAudioPolicyMetadata: device " + device + " is not in cache"); 331 return null; 332 } 333 334 AudioPolicyEntity entity = mMetadataCache.get(address).audioPolicyMetadata; 335 return new BluetoothSinkAudioPolicy.Builder() 336 .setCallEstablishPolicy(entity.callEstablishAudioPolicy) 337 .setActiveDevicePolicyAfterConnection(entity.connectingTimeAudioPolicy) 338 .setInBandRingtonePolicy(entity.inBandRingtoneAudioPolicy) 339 .build(); 340 } 341 } 342 343 /** 344 * Set the device profile connection policy 345 * 346 * @param device {@link BluetoothDevice} wish to set 347 * @param profile The Bluetooth profile; one of {@link BluetoothProfile#HEADSET}, 348 * {@link BluetoothProfile#HEADSET_CLIENT}, {@link BluetoothProfile#A2DP}, 349 * {@link BluetoothProfile#A2DP_SINK}, {@link BluetoothProfile#HID_HOST}, 350 * {@link BluetoothProfile#PAN}, {@link BluetoothProfile#PBAP}, 351 * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP}, 352 * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP}, 353 * {@link BluetoothProfile#HEARING_AID}, {@link BluetoothProfile#LE_AUDIO}, 354 * {@link BluetoothProfile#VOLUME_CONTROL}, {@link BluetoothProfile#CSIP_SET_COORDINATOR}, 355 * {@link BluetoothProfile#LE_AUDIO_BROADCAST_ASSISTANT}, 356 * @param newConnectionPolicy the connectionPolicy to set; one of 357 * {@link BluetoothProfile.CONNECTION_POLICY_UNKNOWN}, 358 * {@link BluetoothProfile.CONNECTION_POLICY_FORBIDDEN}, 359 * {@link BluetoothProfile.CONNECTION_POLICY_ALLOWED} 360 */ 361 @VisibleForTesting setProfileConnectionPolicy(BluetoothDevice device, int profile, int newConnectionPolicy)362 public boolean setProfileConnectionPolicy(BluetoothDevice device, int profile, 363 int newConnectionPolicy) { 364 synchronized (mMetadataCache) { 365 if (device == null) { 366 Log.e(TAG, "setProfileConnectionPolicy: device is null"); 367 return false; 368 } 369 370 if (newConnectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN 371 && newConnectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 372 && newConnectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 373 Log.e(TAG, "setProfileConnectionPolicy: invalid connection policy " 374 + newConnectionPolicy); 375 return false; 376 } 377 378 String address = device.getAddress(); 379 if (!mMetadataCache.containsKey(address)) { 380 if (newConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_UNKNOWN) { 381 return true; 382 } 383 createMetadata(address, false); 384 } 385 Metadata data = mMetadataCache.get(address); 386 int oldConnectionPolicy = data.getProfileConnectionPolicy(profile); 387 if (oldConnectionPolicy == newConnectionPolicy) { 388 Log.v(TAG, "setProfileConnectionPolicy connection policy not changed."); 389 return true; 390 } 391 String profileStr = BluetoothProfile.getProfileName(profile); 392 logMetadataChange(data, profileStr + " connection policy changed: " 393 + oldConnectionPolicy + " -> " + newConnectionPolicy); 394 395 Log.v(TAG, "setProfileConnectionPolicy: device " + device.getAnonymizedAddress() 396 + " profile=" + profileStr + ", connectionPolicy=" + newConnectionPolicy); 397 398 data.setProfileConnectionPolicy(profile, newConnectionPolicy); 399 updateDatabase(data); 400 return true; 401 } 402 } 403 404 /** 405 * Get the device profile connection policy 406 * 407 * @param device {@link BluetoothDevice} wish to get 408 * @param profile The Bluetooth profile; one of {@link BluetoothProfile#HEADSET}, 409 * {@link BluetoothProfile#HEADSET_CLIENT}, {@link BluetoothProfile#A2DP}, 410 * {@link BluetoothProfile#A2DP_SINK}, {@link BluetoothProfile#HID_HOST}, 411 * {@link BluetoothProfile#PAN}, {@link BluetoothProfile#PBAP}, 412 * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP}, 413 * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP}, 414 * {@link BluetoothProfile#HEARING_AID}, {@link BluetoothProfile#LE_AUDIO}, 415 * {@link BluetoothProfile#VOLUME_CONTROL}, {@link BluetoothProfile#CSIP_SET_COORDINATOR}, 416 * {@link BluetoothProfile#LE_AUDIO_BROADCAST_ASSISTANT}, 417 * @return the profile connection policy of the device; one of 418 * {@link BluetoothProfile.CONNECTION_POLICY_UNKNOWN}, 419 * {@link BluetoothProfile.CONNECTION_POLICY_FORBIDDEN}, 420 * {@link BluetoothProfile.CONNECTION_POLICY_ALLOWED} 421 */ 422 @VisibleForTesting getProfileConnectionPolicy(BluetoothDevice device, int profile)423 public int getProfileConnectionPolicy(BluetoothDevice device, int profile) { 424 synchronized (mMetadataCache) { 425 if (device == null) { 426 Log.e(TAG, "getProfileConnectionPolicy: device is null"); 427 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 428 } 429 430 String address = device.getAddress(); 431 432 if (!mMetadataCache.containsKey(address)) { 433 Log.d(TAG, "getProfileConnectionPolicy: device " + device.getAnonymizedAddress() 434 + " is not in cache"); 435 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 436 } 437 438 Metadata data = mMetadataCache.get(address); 439 int connectionPolicy = data.getProfileConnectionPolicy(profile); 440 441 Log.v(TAG, "getProfileConnectionPolicy: device " + device.getAnonymizedAddress() 442 + " profile=" + BluetoothProfile.getProfileName(profile) 443 + ", connectionPolicy=" + connectionPolicy); 444 return connectionPolicy; 445 } 446 } 447 448 /** 449 * Set the A2DP optional coedc support value 450 * 451 * @param device {@link BluetoothDevice} wish to set 452 * @param newValue the new A2DP optional coedc support value, one of 453 * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORT_UNKNOWN}, 454 * {@link BluetoothA2dp#OPTIONAL_CODECS_NOT_SUPPORTED}, 455 * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORTED} 456 */ 457 @VisibleForTesting setA2dpSupportsOptionalCodecs(BluetoothDevice device, int newValue)458 public void setA2dpSupportsOptionalCodecs(BluetoothDevice device, int newValue) { 459 synchronized (mMetadataCache) { 460 if (device == null) { 461 Log.e(TAG, "setA2dpOptionalCodec: device is null"); 462 return; 463 } 464 if (newValue != BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN 465 && newValue != BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED 466 && newValue != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) { 467 Log.e(TAG, "setA2dpSupportsOptionalCodecs: invalid value " + newValue); 468 return; 469 } 470 471 String address = device.getAddress(); 472 473 if (!mMetadataCache.containsKey(address)) { 474 return; 475 } 476 Metadata data = mMetadataCache.get(address); 477 int oldValue = data.a2dpSupportsOptionalCodecs; 478 if (oldValue == newValue) { 479 return; 480 } 481 logMetadataChange(data, "Supports optional codec changed: " 482 + oldValue + " -> " + newValue); 483 484 data.a2dpSupportsOptionalCodecs = newValue; 485 updateDatabase(data); 486 } 487 } 488 489 /** 490 * Get the A2DP optional coedc support value 491 * 492 * @param device {@link BluetoothDevice} wish to get 493 * @return the A2DP optional coedc support value, one of 494 * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORT_UNKNOWN}, 495 * {@link BluetoothA2dp#OPTIONAL_CODECS_NOT_SUPPORTED}, 496 * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORTED}, 497 */ 498 @VisibleForTesting 499 @OptionalCodecsSupportStatus getA2dpSupportsOptionalCodecs(BluetoothDevice device)500 public int getA2dpSupportsOptionalCodecs(BluetoothDevice device) { 501 synchronized (mMetadataCache) { 502 if (device == null) { 503 Log.e(TAG, "setA2dpOptionalCodec: device is null"); 504 return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN; 505 } 506 507 String address = device.getAddress(); 508 509 if (!mMetadataCache.containsKey(address)) { 510 Log.d(TAG, "getA2dpOptionalCodec: device " + device + " is not in cache"); 511 return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN; 512 } 513 514 Metadata data = mMetadataCache.get(address); 515 return data.a2dpSupportsOptionalCodecs; 516 } 517 } 518 519 /** 520 * Set the A2DP optional coedc enabled value 521 * 522 * @param device {@link BluetoothDevice} wish to set 523 * @param newValue the new A2DP optional coedc enabled value, one of 524 * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_UNKNOWN}, 525 * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_DISABLED}, 526 * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_ENABLED} 527 */ 528 @VisibleForTesting setA2dpOptionalCodecsEnabled(BluetoothDevice device, int newValue)529 public void setA2dpOptionalCodecsEnabled(BluetoothDevice device, int newValue) { 530 synchronized (mMetadataCache) { 531 if (device == null) { 532 Log.e(TAG, "setA2dpOptionalCodecEnabled: device is null"); 533 return; 534 } 535 if (newValue != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN 536 && newValue != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED 537 && newValue != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) { 538 Log.e(TAG, "setA2dpOptionalCodecsEnabled: invalid value " + newValue); 539 return; 540 } 541 542 String address = device.getAddress(); 543 544 if (!mMetadataCache.containsKey(address)) { 545 return; 546 } 547 Metadata data = mMetadataCache.get(address); 548 int oldValue = data.a2dpOptionalCodecsEnabled; 549 if (oldValue == newValue) { 550 return; 551 } 552 logMetadataChange(data, "Enable optional codec changed: " 553 + oldValue + " -> " + newValue); 554 555 data.a2dpOptionalCodecsEnabled = newValue; 556 updateDatabase(data); 557 } 558 } 559 560 /** 561 * Get the A2DP optional coedc enabled value 562 * 563 * @param device {@link BluetoothDevice} wish to get 564 * @return the A2DP optional coedc enabled value, one of 565 * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_UNKNOWN}, 566 * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_DISABLED}, 567 * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_ENABLED} 568 */ 569 @VisibleForTesting 570 @OptionalCodecsPreferenceStatus getA2dpOptionalCodecsEnabled(BluetoothDevice device)571 public int getA2dpOptionalCodecsEnabled(BluetoothDevice device) { 572 synchronized (mMetadataCache) { 573 if (device == null) { 574 Log.e(TAG, "getA2dpOptionalCodecEnabled: device is null"); 575 return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN; 576 } 577 578 String address = device.getAddress(); 579 580 if (!mMetadataCache.containsKey(address)) { 581 Log.d(TAG, "getA2dpOptionalCodecEnabled: device " + device + " is not in cache"); 582 return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN; 583 } 584 585 Metadata data = mMetadataCache.get(address); 586 return data.a2dpOptionalCodecsEnabled; 587 } 588 } 589 590 /** 591 * Updates the time this device was last connected 592 * 593 * @param device is the remote bluetooth device for which we are setting the connection time 594 */ setConnection(BluetoothDevice device, boolean isA2dpDevice)595 public void setConnection(BluetoothDevice device, boolean isA2dpDevice) { 596 synchronized (mMetadataCache) { 597 Log.d(TAG, "setConnection: device " + device.getAnonymizedAddress() 598 + " and isA2dpDevice=" + isA2dpDevice); 599 if (device == null) { 600 Log.e(TAG, "setConnection: device is null"); 601 return; 602 } 603 604 if (isA2dpDevice) { 605 resetActiveA2dpDevice(); 606 } 607 608 String address = device.getAddress(); 609 610 if (!mMetadataCache.containsKey(address)) { 611 Log.d(TAG, "setConnection: Creating new metadata entry for device: " + device); 612 createMetadata(address, isA2dpDevice); 613 return; 614 } 615 // Updates last_active_time to the current counter value and increments the counter 616 Metadata metadata = mMetadataCache.get(address); 617 synchronized (MetadataDatabase.class) { 618 metadata.last_active_time = MetadataDatabase.sCurrentConnectionNumber++; 619 } 620 621 // Only update is_active_a2dp_device if an a2dp device is connected 622 if (isA2dpDevice) { 623 metadata.is_active_a2dp_device = true; 624 } 625 626 Log.d(TAG, "Updating last connected time for device: " + device.getAnonymizedAddress() 627 + " to " + metadata.last_active_time); 628 updateDatabase(metadata); 629 } 630 } 631 632 /** 633 * Sets is_active_device to false if currently true for device 634 * 635 * @param device is the remote bluetooth device with which we have disconnected a2dp 636 */ setDisconnection(BluetoothDevice device)637 public void setDisconnection(BluetoothDevice device) { 638 synchronized (mMetadataCache) { 639 if (device == null) { 640 Log.e(TAG, "setDisconnection: device is null"); 641 return; 642 } 643 644 String address = device.getAddress(); 645 646 if (!mMetadataCache.containsKey(address)) { 647 return; 648 } 649 // Updates last connected time to either current time if connected or -1 if disconnected 650 Metadata metadata = mMetadataCache.get(address); 651 if (metadata.is_active_a2dp_device) { 652 metadata.is_active_a2dp_device = false; 653 Log.d(TAG, "setDisconnection: Updating is_active_device to false for device: " 654 + device); 655 updateDatabase(metadata); 656 } 657 } 658 } 659 660 /** 661 * Remove a2dpActiveDevice from the current active device in the connection order table 662 */ resetActiveA2dpDevice()663 private void resetActiveA2dpDevice() { 664 synchronized (mMetadataCache) { 665 Log.d(TAG, "resetActiveA2dpDevice()"); 666 for (Map.Entry<String, Metadata> entry : mMetadataCache.entrySet()) { 667 Metadata metadata = entry.getValue(); 668 if (metadata.is_active_a2dp_device) { 669 Log.d(TAG, "resetActiveA2dpDevice"); 670 metadata.is_active_a2dp_device = false; 671 updateDatabase(metadata); 672 } 673 } 674 } 675 } 676 677 /** 678 * Gets the most recently connected bluetooth devices in order with most recently connected 679 * first and least recently connected last 680 * 681 * @return a {@link List} of {@link BluetoothDevice} representing connected bluetooth devices 682 * in order of most recently connected 683 */ getMostRecentlyConnectedDevices()684 public List<BluetoothDevice> getMostRecentlyConnectedDevices() { 685 List<BluetoothDevice> mostRecentlyConnectedDevices = new ArrayList<>(); 686 synchronized (mMetadataCache) { 687 List<Metadata> sortedMetadata = new ArrayList<>(mMetadataCache.values()); 688 sortedMetadata.sort((o1, o2) -> Long.compare(o2.last_active_time, o1.last_active_time)); 689 for (Metadata metadata : sortedMetadata) { 690 try { 691 mostRecentlyConnectedDevices.add(BluetoothAdapter.getDefaultAdapter() 692 .getRemoteDevice(metadata.getAddress())); 693 } catch (IllegalArgumentException ex) { 694 Log.d(TAG, "getBondedDevicesOrdered: Invalid address for " 695 + "device " + metadata.getAnonymizedAddress()); 696 } 697 } 698 } 699 return mostRecentlyConnectedDevices; 700 } 701 702 /** 703 * Gets the most recently connected bluetooth device in a given list. 704 * 705 * @param devicesList the list of {@link BluetoothDevice} to search in 706 * @return the most recently connected {@link BluetoothDevice} in the given 707 * {@code devicesList}, or null if an error occurred 708 * 709 * @hide 710 */ getMostRecentlyConnectedDevicesInList( List<BluetoothDevice> devicesList)711 public BluetoothDevice getMostRecentlyConnectedDevicesInList( 712 List<BluetoothDevice> devicesList) { 713 if (devicesList == null) { 714 return null; 715 } 716 717 BluetoothDevice mostRecentDevice = null; 718 long mostRecentLastActiveTime = -1; 719 synchronized (mMetadataCache) { 720 for (BluetoothDevice device : devicesList) { 721 String address = device.getAddress(); 722 Metadata metadata = mMetadataCache.get(address); 723 if (metadata != null && (mostRecentLastActiveTime == -1 724 || mostRecentLastActiveTime < metadata.last_active_time)) { 725 mostRecentLastActiveTime = metadata.last_active_time; 726 mostRecentDevice = device; 727 } 728 729 } 730 } 731 return mostRecentDevice; 732 } 733 734 /** 735 * Gets the last active a2dp device 736 * 737 * @return the most recently active a2dp device or null if the last a2dp device was null 738 */ getMostRecentlyConnectedA2dpDevice()739 public BluetoothDevice getMostRecentlyConnectedA2dpDevice() { 740 synchronized (mMetadataCache) { 741 for (Map.Entry<String, Metadata> entry : mMetadataCache.entrySet()) { 742 Metadata metadata = entry.getValue(); 743 if (metadata.is_active_a2dp_device) { 744 try { 745 return BluetoothAdapter.getDefaultAdapter().getRemoteDevice( 746 metadata.getAddress()); 747 } catch (IllegalArgumentException ex) { 748 Log.d(TAG, "getMostRecentlyConnectedA2dpDevice: Invalid address for " 749 + "device " + metadata.getAnonymizedAddress()); 750 } 751 } 752 } 753 } 754 return null; 755 } 756 757 /** 758 * 759 * @param metadataList is the list of metadata 760 */ compactLastConnectionTime(List<Metadata> metadataList)761 private void compactLastConnectionTime(List<Metadata> metadataList) { 762 Log.d(TAG, "compactLastConnectionTime: Compacting metadata after load"); 763 synchronized (MetadataDatabase.class) { 764 MetadataDatabase.sCurrentConnectionNumber = 0; 765 // Have to go in reverse order as list is ordered by descending last_active_time 766 for (int index = metadataList.size() - 1; index >= 0; index--) { 767 Metadata metadata = metadataList.get(index); 768 if (metadata.last_active_time != MetadataDatabase.sCurrentConnectionNumber) { 769 Log.d(TAG, "compactLastConnectionTime: Setting last_active_item for device: " 770 + metadata.getAnonymizedAddress() + " from " + metadata.last_active_time 771 + " to " + MetadataDatabase.sCurrentConnectionNumber); 772 metadata.last_active_time = MetadataDatabase.sCurrentConnectionNumber; 773 updateDatabase(metadata); 774 MetadataDatabase.sCurrentConnectionNumber++; 775 } 776 } 777 } 778 } 779 780 /** 781 * Sets the preferred profile for the supplied audio modes. See 782 * {@link BluetoothAdapter#setPreferredAudioProfiles(BluetoothDevice, Bundle)} for more details. 783 * 784 * If a device in the group has been designated to store the preference for the group, this will 785 * update its database preferences. If there is not one designated, the first device from the 786 * group list will be chosen for this purpose. From then on, any preferred audio profile changes 787 * for this group will be stored on that device. 788 * 789 * @param groupDevices is the CSIP group for which we are setting the preferred audio profiles 790 * @param modeToProfileBundle contains the preferred profile 791 * @return whether the new preferences were saved in the database 792 */ setPreferredAudioProfiles(List<BluetoothDevice> groupDevices, Bundle modeToProfileBundle)793 public int setPreferredAudioProfiles(List<BluetoothDevice> groupDevices, 794 Bundle modeToProfileBundle) { 795 Objects.requireNonNull(groupDevices, "groupDevices must not be null"); 796 Objects.requireNonNull(modeToProfileBundle, "modeToProfileBundle must not be null"); 797 if (groupDevices.isEmpty()) { 798 throw new IllegalArgumentException("groupDevices cannot be empty"); 799 } 800 int outputProfile = modeToProfileBundle.getInt( 801 BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY); 802 int duplexProfile = modeToProfileBundle.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX); 803 boolean isPreferenceSet = false; 804 805 synchronized (mMetadataCache) { 806 for (BluetoothDevice device: groupDevices) { 807 if (device == null) { 808 Log.e(TAG, "setPreferredAudioProfiles: device is null"); 809 throw new IllegalArgumentException("setPreferredAudioProfiles: device is null"); 810 } 811 812 String address = device.getAddress(); 813 if (!mMetadataCache.containsKey(address)) { 814 Log.e(TAG, "setPreferredAudioProfiles: Device not found in the database"); 815 return BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED; 816 } 817 818 // Finds the device in the group which stores the group's preferences 819 Metadata metadata = mMetadataCache.get(address); 820 if (outputProfile != 0 && (metadata.preferred_output_only_profile != 0 821 || metadata.preferred_duplex_profile != 0)) { 822 Log.i(TAG, "setPreferredAudioProfiles: Updating OUTPUT_ONLY audio profile for " 823 + "device: " + device + " to " 824 + BluetoothProfile.getProfileName(outputProfile)); 825 metadata.preferred_output_only_profile = outputProfile; 826 isPreferenceSet = true; 827 } 828 if (duplexProfile != 0 && (metadata.preferred_output_only_profile != 0 829 || metadata.preferred_duplex_profile != 0)) { 830 Log.i(TAG, 831 "setPreferredAudioProfiles: Updating DUPLEX audio profile for device: " 832 + device + " to " + BluetoothProfile.getProfileName( 833 duplexProfile)); 834 metadata.preferred_duplex_profile = duplexProfile; 835 isPreferenceSet = true; 836 } 837 838 updateDatabase(metadata); 839 } 840 841 // If no device in the group has a preference set, choose the first device in the list 842 if (!isPreferenceSet) { 843 Log.i(TAG, "No device in the group has preferred audio profiles set"); 844 BluetoothDevice firstGroupDevice = groupDevices.get(0); 845 // Updates preferred audio profiles for the device 846 Metadata metadata = mMetadataCache.get(firstGroupDevice.getAddress()); 847 if (outputProfile != 0) { 848 Log.i(TAG, "setPreferredAudioProfiles: Updating output only audio profile for " 849 + "device: " + firstGroupDevice + " to " 850 + BluetoothProfile.getProfileName(outputProfile)); 851 metadata.preferred_output_only_profile = outputProfile; 852 } 853 if (duplexProfile != 0) { 854 Log.i(TAG, 855 "setPreferredAudioProfiles: Updating duplex audio profile for device: " 856 + firstGroupDevice + " to " + BluetoothProfile.getProfileName( 857 duplexProfile)); 858 metadata.preferred_duplex_profile = duplexProfile; 859 } 860 861 updateDatabase(metadata); 862 } 863 } 864 return BluetoothStatusCodes.SUCCESS; 865 } 866 867 /** 868 * Sets the preferred profile for the supplied audio modes. See 869 * {@link BluetoothAdapter#getPreferredAudioProfiles(BluetoothDevice)} for more details. 870 * 871 * @param device is the device for which we want to get the preferred audio profiles 872 * @return a Bundle containing the preferred audio profiles 873 */ getPreferredAudioProfiles(BluetoothDevice device)874 public Bundle getPreferredAudioProfiles(BluetoothDevice device) { 875 synchronized (mMetadataCache) { 876 if (device == null) { 877 Log.e(TAG, "getPreferredAudioProfiles: device is null"); 878 throw new IllegalArgumentException("getPreferredAudioProfiles: device is null"); 879 } 880 881 String address = device.getAddress(); 882 883 if (!mMetadataCache.containsKey(address)) { 884 return Bundle.EMPTY; 885 } 886 887 // Gets the preferred audio profiles for each audio mode 888 Metadata metadata = mMetadataCache.get(address); 889 int outputOnlyProfile = metadata.preferred_output_only_profile; 890 int duplexProfile = metadata.preferred_duplex_profile; 891 892 // Checks if the default values are present (aka no explicit preference) 893 if (outputOnlyProfile == 0 && duplexProfile == 0) { 894 return Bundle.EMPTY; 895 } 896 897 Bundle modeToProfileBundle = new Bundle(); 898 if (outputOnlyProfile != 0) { 899 modeToProfileBundle.putInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY, 900 outputOnlyProfile); 901 } 902 if (duplexProfile != 0) { 903 modeToProfileBundle.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, duplexProfile); 904 } 905 906 return modeToProfileBundle; 907 } 908 } 909 910 /** 911 * Get the {@link Looper} for the handler thread. This is used in testing and helper 912 * objects 913 * 914 * @return {@link Looper} for the handler thread 915 */ 916 @VisibleForTesting getHandlerLooper()917 public Looper getHandlerLooper() { 918 if (mHandlerThread == null) { 919 return null; 920 } 921 return mHandlerThread.getLooper(); 922 } 923 924 /** 925 * Start and initialize the DatabaseManager 926 * 927 * @param database the Bluetooth storage {@link MetadataDatabase} 928 */ start(MetadataDatabase database)929 public void start(MetadataDatabase database) { 930 Log.d(TAG, "start()"); 931 if (mAdapterService == null) { 932 Log.e(TAG, "stat failed, mAdapterService is null."); 933 return; 934 } 935 936 if (database == null) { 937 Log.e(TAG, "stat failed, database is null."); 938 return; 939 } 940 941 synchronized (mDatabaseLock) { 942 mDatabase = database; 943 } 944 945 mHandlerThread = new HandlerThread("BluetoothDatabaseManager"); 946 mHandlerThread.start(); 947 mHandler = new DatabaseHandler(mHandlerThread.getLooper()); 948 949 IntentFilter filter = new IntentFilter(); 950 filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 951 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 952 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 953 mAdapterService.registerReceiver(mReceiver, filter); 954 955 loadDatabase(); 956 } 957 getDatabaseAbsolutePath()958 String getDatabaseAbsolutePath() { 959 //TODO backup database when Bluetooth turn off and FOTA? 960 return mAdapterService.getDatabasePath(MetadataDatabase.DATABASE_NAME) 961 .getAbsolutePath(); 962 } 963 964 /** 965 * Clear all persistence data in database 966 */ factoryReset()967 public void factoryReset() { 968 Log.w(TAG, "factoryReset"); 969 Message message = mHandler.obtainMessage(MSG_CLEAR_DATABASE); 970 mHandler.sendMessage(message); 971 } 972 973 /** 974 * Close and de-init the DatabaseManager 975 */ cleanup()976 public void cleanup() { 977 removeUnusedMetadata(); 978 mAdapterService.unregisterReceiver(mReceiver); 979 if (mHandlerThread != null) { 980 mHandlerThread.quit(); 981 mHandlerThread = null; 982 } 983 mMetadataCache.clear(); 984 } 985 createMetadata(String address, boolean isActiveA2dpDevice)986 void createMetadata(String address, boolean isActiveA2dpDevice) { 987 Metadata data = new Metadata(address); 988 data.is_active_a2dp_device = isActiveA2dpDevice; 989 mMetadataCache.put(address, data); 990 updateDatabase(data); 991 logMetadataChange(data, "Metadata created"); 992 } 993 994 @VisibleForTesting removeUnusedMetadata()995 void removeUnusedMetadata() { 996 BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 997 synchronized (mMetadataCache) { 998 mMetadataCache.forEach((address, metadata) -> { 999 if (!address.equals(LOCAL_STORAGE) 1000 && !Arrays.asList(bondedDevices).stream().anyMatch(device -> 1001 address.equals(device.getAddress()))) { 1002 List<Integer> list = metadata.getChangedCustomizedMeta(); 1003 for (int key : list) { 1004 mAdapterService.metadataChanged(address, key, null); 1005 } 1006 Log.i(TAG, "remove unpaired device from database " 1007 + metadata.getAnonymizedAddress()); 1008 deleteDatabase(mMetadataCache.get(address)); 1009 } 1010 }); 1011 } 1012 } 1013 cacheMetadata(List<Metadata> list)1014 void cacheMetadata(List<Metadata> list) { 1015 synchronized (mMetadataCache) { 1016 Log.i(TAG, "cacheMetadata"); 1017 // Unlock the main thread. 1018 mSemaphore.release(); 1019 1020 if (!isMigrated(list)) { 1021 // Wait for data migrate from Settings Global 1022 mMigratedFromSettingsGlobal = false; 1023 return; 1024 } 1025 mMigratedFromSettingsGlobal = true; 1026 for (Metadata data : list) { 1027 String address = data.getAddress(); 1028 Log.v(TAG, "cacheMetadata: found device " + data.getAnonymizedAddress()); 1029 mMetadataCache.put(address, data); 1030 } 1031 Log.i(TAG, "cacheMetadata: Database is ready"); 1032 } 1033 } 1034 isMigrated(List<Metadata> list)1035 boolean isMigrated(List<Metadata> list) { 1036 for (Metadata data : list) { 1037 String address = data.getAddress(); 1038 if (address.equals(LOCAL_STORAGE) && data.migrated) { 1039 return true; 1040 } 1041 } 1042 return false; 1043 } 1044 migrateSettingsGlobal()1045 void migrateSettingsGlobal() { 1046 Log.i(TAG, "migrateSettingGlobal"); 1047 1048 BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 1049 ContentResolver contentResolver = mAdapterService.getContentResolver(); 1050 1051 for (BluetoothDevice device : bondedDevices) { 1052 int a2dpConnectionPolicy = Settings.Global.getInt(contentResolver, 1053 getLegacyA2dpSinkPriorityKey(device.getAddress()), 1054 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 1055 int a2dpSinkConnectionPolicy = Settings.Global.getInt(contentResolver, 1056 getLegacyA2dpSrcPriorityKey(device.getAddress()), 1057 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 1058 int hearingaidConnectionPolicy = Settings.Global.getInt(contentResolver, 1059 getLegacyHearingAidPriorityKey(device.getAddress()), 1060 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 1061 int headsetConnectionPolicy = Settings.Global.getInt(contentResolver, 1062 getLegacyHeadsetPriorityKey(device.getAddress()), 1063 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 1064 int headsetClientConnectionPolicy = Settings.Global.getInt(contentResolver, 1065 getLegacyHeadsetPriorityKey(device.getAddress()), 1066 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 1067 int hidHostConnectionPolicy = Settings.Global.getInt(contentResolver, 1068 getLegacyHidHostPriorityKey(device.getAddress()), 1069 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 1070 int mapConnectionPolicy = Settings.Global.getInt(contentResolver, 1071 getLegacyMapPriorityKey(device.getAddress()), 1072 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 1073 int mapClientConnectionPolicy = Settings.Global.getInt(contentResolver, 1074 getLegacyMapClientPriorityKey(device.getAddress()), 1075 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 1076 int panConnectionPolicy = Settings.Global.getInt(contentResolver, 1077 getLegacyPanPriorityKey(device.getAddress()), 1078 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 1079 int pbapConnectionPolicy = Settings.Global.getInt(contentResolver, 1080 getLegacyPbapClientPriorityKey(device.getAddress()), 1081 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 1082 int pbapClientConnectionPolicy = Settings.Global.getInt(contentResolver, 1083 getLegacyPbapClientPriorityKey(device.getAddress()), 1084 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 1085 int sapConnectionPolicy = Settings.Global.getInt(contentResolver, 1086 getLegacySapPriorityKey(device.getAddress()), 1087 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 1088 int a2dpSupportsOptionalCodec = Settings.Global.getInt(contentResolver, 1089 getLegacyA2dpSupportsOptionalCodecsKey(device.getAddress()), 1090 BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN); 1091 int a2dpOptionalCodecEnabled = Settings.Global.getInt(contentResolver, 1092 getLegacyA2dpOptionalCodecsEnabledKey(device.getAddress()), 1093 BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN); 1094 1095 String address = device.getAddress(); 1096 Metadata data = new Metadata(address); 1097 data.setProfileConnectionPolicy(BluetoothProfile.A2DP, a2dpConnectionPolicy); 1098 data.setProfileConnectionPolicy(BluetoothProfile.A2DP_SINK, a2dpSinkConnectionPolicy); 1099 data.setProfileConnectionPolicy(BluetoothProfile.HEADSET, headsetConnectionPolicy); 1100 data.setProfileConnectionPolicy(BluetoothProfile.HEADSET_CLIENT, 1101 headsetClientConnectionPolicy); 1102 data.setProfileConnectionPolicy(BluetoothProfile.HID_HOST, hidHostConnectionPolicy); 1103 data.setProfileConnectionPolicy(BluetoothProfile.PAN, panConnectionPolicy); 1104 data.setProfileConnectionPolicy(BluetoothProfile.PBAP, pbapConnectionPolicy); 1105 data.setProfileConnectionPolicy(BluetoothProfile.PBAP_CLIENT, 1106 pbapClientConnectionPolicy); 1107 data.setProfileConnectionPolicy(BluetoothProfile.MAP, mapConnectionPolicy); 1108 data.setProfileConnectionPolicy(BluetoothProfile.MAP_CLIENT, mapClientConnectionPolicy); 1109 data.setProfileConnectionPolicy(BluetoothProfile.SAP, sapConnectionPolicy); 1110 data.setProfileConnectionPolicy(BluetoothProfile.HEARING_AID, 1111 hearingaidConnectionPolicy); 1112 data.setProfileConnectionPolicy(BluetoothProfile.LE_AUDIO, 1113 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 1114 data.a2dpSupportsOptionalCodecs = a2dpSupportsOptionalCodec; 1115 data.a2dpOptionalCodecsEnabled = a2dpOptionalCodecEnabled; 1116 mMetadataCache.put(address, data); 1117 updateDatabase(data); 1118 } 1119 1120 // Mark database migrated from Settings Global 1121 Metadata localData = new Metadata(LOCAL_STORAGE); 1122 localData.migrated = true; 1123 mMetadataCache.put(LOCAL_STORAGE, localData); 1124 updateDatabase(localData); 1125 1126 // Reload database after migration is completed 1127 loadDatabase(); 1128 1129 } 1130 1131 /** 1132 * Get the key that retrieves a bluetooth headset's priority. 1133 */ getLegacyHeadsetPriorityKey(String address)1134 private static String getLegacyHeadsetPriorityKey(String address) { 1135 return LEGACY_HEADSET_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 1136 } 1137 1138 /** 1139 * Get the key that retrieves a bluetooth a2dp sink's priority. 1140 */ getLegacyA2dpSinkPriorityKey(String address)1141 private static String getLegacyA2dpSinkPriorityKey(String address) { 1142 return LEGACY_A2DP_SINK_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 1143 } 1144 1145 /** 1146 * Get the key that retrieves a bluetooth a2dp src's priority. 1147 */ getLegacyA2dpSrcPriorityKey(String address)1148 private static String getLegacyA2dpSrcPriorityKey(String address) { 1149 return LEGACY_A2DP_SRC_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 1150 } 1151 1152 /** 1153 * Get the key that retrieves a bluetooth a2dp device's ability to support optional codecs. 1154 */ getLegacyA2dpSupportsOptionalCodecsKey(String address)1155 private static String getLegacyA2dpSupportsOptionalCodecsKey(String address) { 1156 return LEGACY_A2DP_SUPPORTS_OPTIONAL_CODECS_PREFIX 1157 + address.toUpperCase(Locale.ROOT); 1158 } 1159 1160 /** 1161 * Get the key that retrieves whether a bluetooth a2dp device should have optional codecs 1162 * enabled. 1163 */ getLegacyA2dpOptionalCodecsEnabledKey(String address)1164 private static String getLegacyA2dpOptionalCodecsEnabledKey(String address) { 1165 return LEGACY_A2DP_OPTIONAL_CODECS_ENABLED_PREFIX 1166 + address.toUpperCase(Locale.ROOT); 1167 } 1168 1169 /** 1170 * Get the key that retrieves a bluetooth Input Device's priority. 1171 */ getLegacyHidHostPriorityKey(String address)1172 private static String getLegacyHidHostPriorityKey(String address) { 1173 return LEGACY_INPUT_DEVICE_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 1174 } 1175 1176 /** 1177 * Get the key that retrieves a bluetooth pan client priority. 1178 */ getLegacyPanPriorityKey(String address)1179 private static String getLegacyPanPriorityKey(String address) { 1180 return LEGACY_PAN_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 1181 } 1182 1183 /** 1184 * Get the key that retrieves a bluetooth hearing aid priority. 1185 */ getLegacyHearingAidPriorityKey(String address)1186 private static String getLegacyHearingAidPriorityKey(String address) { 1187 return LEGACY_HEARING_AID_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 1188 } 1189 1190 /** 1191 * Get the key that retrieves a bluetooth map priority. 1192 */ getLegacyMapPriorityKey(String address)1193 private static String getLegacyMapPriorityKey(String address) { 1194 return LEGACY_MAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 1195 } 1196 1197 /** 1198 * Get the key that retrieves a bluetooth map client priority. 1199 */ getLegacyMapClientPriorityKey(String address)1200 private static String getLegacyMapClientPriorityKey(String address) { 1201 return LEGACY_MAP_CLIENT_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 1202 } 1203 1204 /** 1205 * Get the key that retrieves a bluetooth pbap client priority. 1206 */ getLegacyPbapClientPriorityKey(String address)1207 private static String getLegacyPbapClientPriorityKey(String address) { 1208 return LEGACY_PBAP_CLIENT_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 1209 } 1210 1211 /** 1212 * Get the key that retrieves a bluetooth sap priority. 1213 */ getLegacySapPriorityKey(String address)1214 private static String getLegacySapPriorityKey(String address) { 1215 return LEGACY_SAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 1216 } 1217 loadDatabase()1218 private void loadDatabase() { 1219 Log.d(TAG, "Load Database"); 1220 Message message = mHandler.obtainMessage(MSG_LOAD_DATABASE); 1221 mHandler.sendMessage(message); 1222 try { 1223 // Lock the thread until handler thread finish loading database. 1224 mSemaphore.tryAcquire(LOAD_DATABASE_TIMEOUT, TimeUnit.MILLISECONDS); 1225 } catch (InterruptedException e) { 1226 Log.e(TAG, "loadDatabase: semaphore acquire failed"); 1227 } 1228 } 1229 updateDatabase(Metadata data)1230 private void updateDatabase(Metadata data) { 1231 if (data.getAddress() == null) { 1232 Log.e(TAG, "updateDatabase: address is null"); 1233 return; 1234 } 1235 Log.d(TAG, "updateDatabase " + data.getAnonymizedAddress()); 1236 Message message = mHandler.obtainMessage(MSG_UPDATE_DATABASE); 1237 message.obj = data; 1238 mHandler.sendMessage(message); 1239 } 1240 1241 @VisibleForTesting deleteDatabase(Metadata data)1242 void deleteDatabase(Metadata data) { 1243 String address = data.getAddress(); 1244 if (address == null) { 1245 Log.e(TAG, "deleteDatabase: address is null"); 1246 return; 1247 } 1248 logMetadataChange(data, "Metadata deleted"); 1249 Message message = mHandler.obtainMessage(MSG_DELETE_DATABASE); 1250 message.obj = data.getAddress(); 1251 mHandler.sendMessage(message); 1252 } 1253 logManufacturerInfo(BluetoothDevice device, int key, byte[] bytesValue)1254 private void logManufacturerInfo(BluetoothDevice device, int key, byte[] bytesValue) { 1255 String callingApp = mAdapterService.getPackageManager().getNameForUid( 1256 Binder.getCallingUid()); 1257 String manufacturerName = ""; 1258 String modelName = ""; 1259 String hardwareVersion = ""; 1260 String softwareVersion = ""; 1261 switch (key) { 1262 case BluetoothDevice.METADATA_MANUFACTURER_NAME: 1263 manufacturerName = Utils.byteArrayToUtf8String(bytesValue); 1264 break; 1265 case BluetoothDevice.METADATA_MODEL_NAME: 1266 modelName = Utils.byteArrayToUtf8String(bytesValue); 1267 break; 1268 case BluetoothDevice.METADATA_HARDWARE_VERSION: 1269 hardwareVersion = Utils.byteArrayToUtf8String(bytesValue); 1270 break; 1271 case BluetoothDevice.METADATA_SOFTWARE_VERSION: 1272 softwareVersion = Utils.byteArrayToUtf8String(bytesValue); 1273 break; 1274 default: 1275 // Do not log anything if metadata doesn't fall into above categories 1276 return; 1277 } 1278 String[] macAddress = device.getAddress().split(":"); 1279 BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_DEVICE_INFO_REPORTED, 1280 mAdapterService.obfuscateAddress(device), 1281 BluetoothProtoEnums.DEVICE_INFO_EXTERNAL, callingApp, manufacturerName, modelName, 1282 hardwareVersion, softwareVersion, mAdapterService.getMetricId(device), 1283 device.getAddressType(), 1284 Integer.parseInt(macAddress[0], 16), 1285 Integer.parseInt(macAddress[1], 16), 1286 Integer.parseInt(macAddress[2], 16)); 1287 } 1288 logMetadataChange(Metadata data, String log)1289 private void logMetadataChange(Metadata data, String log) { 1290 String time = Utils.getLocalTimeString(); 1291 String uidPid = Utils.getUidPidString(); 1292 mMetadataChangedLog.add(time + " (" + uidPid + ") " + data.getAnonymizedAddress() 1293 + " " + log); 1294 } 1295 1296 /** 1297 * Dump database info to a PrintWriter 1298 * 1299 * @param writer the PrintWriter to write log 1300 */ dump(PrintWriter writer)1301 public void dump(PrintWriter writer) { 1302 writer.println("\nBluetoothDatabase:"); 1303 writer.println(" Metadata Changes:"); 1304 for (String log : mMetadataChangedLog) { 1305 writer.println(" " + log); 1306 } 1307 writer.println("\nMetadata:"); 1308 for (Map.Entry<String, Metadata> entry : mMetadataCache.entrySet()) { 1309 if (entry.getKey().equals(LOCAL_STORAGE)) { 1310 // No need to dump local storage 1311 continue; 1312 } 1313 writer.println(" " + entry.getValue()); 1314 } 1315 } 1316 } 1317