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