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