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.content.BroadcastReceiver; 27 import android.content.ContentResolver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.os.Binder; 32 import android.os.Handler; 33 import android.os.HandlerThread; 34 import android.os.Looper; 35 import android.os.Message; 36 import android.provider.Settings; 37 import android.util.Log; 38 39 import com.android.bluetooth.BluetoothStatsLog; 40 import com.android.bluetooth.Utils; 41 import com.android.bluetooth.btservice.AdapterService; 42 import com.android.internal.annotations.VisibleForTesting; 43 44 import com.google.common.collect.EvictingQueue; 45 46 import java.io.PrintWriter; 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.HashMap; 50 import java.util.List; 51 import java.util.Locale; 52 import java.util.Map; 53 import java.util.Objects; 54 import java.util.concurrent.Semaphore; 55 import java.util.concurrent.TimeUnit; 56 57 /** 58 * The active device manager is responsible to handle a Room database 59 * for Bluetooth persistent data. 60 */ 61 public class DatabaseManager { 62 private static final String TAG = "BluetoothDatabase"; 63 64 private AdapterService mAdapterService = null; 65 private HandlerThread mHandlerThread = null; 66 private Handler mHandler = null; 67 private MetadataDatabase mDatabase = null; 68 private boolean mMigratedFromSettingsGlobal = false; 69 70 @VisibleForTesting 71 final Map<String, Metadata> mMetadataCache = new HashMap<>(); 72 private final Semaphore mSemaphore = new Semaphore(1); 73 private static final int METADATA_CHANGED_LOG_MAX_SIZE = 20; 74 private final EvictingQueue<String> mMetadataChangedLog; 75 76 private static final int LOAD_DATABASE_TIMEOUT = 500; // milliseconds 77 private static final int MSG_LOAD_DATABASE = 0; 78 private static final int MSG_UPDATE_DATABASE = 1; 79 private static final int MSG_DELETE_DATABASE = 2; 80 private static final int MSG_CLEAR_DATABASE = 100; 81 private static final String LOCAL_STORAGE = "LocalStorage"; 82 83 private static final String 84 LEGACY_BTSNOOP_DEFAULT_MODE = "bluetooth_btsnoop_default_mode"; 85 private static final String 86 LEGACY_HEADSET_PRIORITY_PREFIX = "bluetooth_headset_priority_"; 87 private static final String 88 LEGACY_A2DP_SINK_PRIORITY_PREFIX = "bluetooth_a2dp_sink_priority_"; 89 private static final String 90 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 96 LEGACY_INPUT_DEVICE_PRIORITY_PREFIX = "bluetooth_input_device_priority_"; 97 private static final String 98 LEGACY_MAP_PRIORITY_PREFIX = "bluetooth_map_priority_"; 99 private static final String 100 LEGACY_MAP_CLIENT_PRIORITY_PREFIX = "bluetooth_map_client_priority_"; 101 private static final String 102 LEGACY_PBAP_CLIENT_PRIORITY_PREFIX = "bluetooth_pbap_client_priority_"; 103 private static final String 104 LEGACY_SAP_PRIORITY_PREFIX = "bluetooth_sap_priority_"; 105 private static final String 106 LEGACY_PAN_PRIORITY_PREFIX = "bluetooth_pan_priority_"; 107 private static final String 108 LEGACY_HEARING_AID_PRIORITY_PREFIX = "bluetooth_hearing_aid_priority_"; 109 110 /** 111 * Constructor of the DatabaseManager 112 */ DatabaseManager(AdapterService service)113 public DatabaseManager(AdapterService service) { 114 mAdapterService = service; 115 mMetadataChangedLog = EvictingQueue.create(METADATA_CHANGED_LOG_MAX_SIZE); 116 } 117 118 class DatabaseHandler extends Handler { DatabaseHandler(Looper looper)119 DatabaseHandler(Looper looper) { 120 super(looper); 121 } 122 123 @Override handleMessage(Message msg)124 public void handleMessage(Message msg) { 125 switch (msg.what) { 126 case MSG_LOAD_DATABASE: { 127 synchronized (mDatabase) { 128 List<Metadata> list; 129 try { 130 list = mDatabase.load(); 131 } catch (IllegalStateException e) { 132 Log.e(TAG, "Unable to open database: " + e); 133 mDatabase = MetadataDatabase 134 .createDatabaseWithoutMigration(mAdapterService); 135 list = mDatabase.load(); 136 } 137 compactLastConnectionTime(list); 138 cacheMetadata(list); 139 } 140 break; 141 } 142 case MSG_UPDATE_DATABASE: { 143 Metadata data = (Metadata) msg.obj; 144 synchronized (mDatabase) { 145 mDatabase.insert(data); 146 } 147 break; 148 } 149 case MSG_DELETE_DATABASE: { 150 String address = (String) msg.obj; 151 synchronized (mDatabase) { 152 mDatabase.delete(address); 153 } 154 break; 155 } 156 case MSG_CLEAR_DATABASE: { 157 synchronized (mDatabase) { 158 mDatabase.deleteAll(); 159 } 160 break; 161 } 162 } 163 } 164 } 165 166 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 167 @Override 168 public void onReceive(Context context, Intent intent) { 169 String action = intent.getAction(); 170 if (action == null) { 171 Log.e(TAG, "Received intent with null action"); 172 return; 173 } 174 switch (action) { 175 case BluetoothDevice.ACTION_BOND_STATE_CHANGED: { 176 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 177 BluetoothDevice.ERROR); 178 BluetoothDevice device = 179 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 180 Objects.requireNonNull(device, 181 "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); 182 bondStateChanged(device, state); 183 break; 184 } 185 case BluetoothAdapter.ACTION_STATE_CHANGED: { 186 int state = intent.getIntExtra(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 bondStateChanged(BluetoothDevice device, int state)198 void bondStateChanged(BluetoothDevice device, int state) { 199 synchronized (mMetadataCache) { 200 String address = device.getAddress(); 201 if (state != BluetoothDevice.BOND_NONE) { 202 if (mMetadataCache.containsKey(address)) { 203 return; 204 } 205 createMetadata(address, false); 206 } else { 207 Metadata metadata = mMetadataCache.get(address); 208 if (metadata != null) { 209 mMetadataCache.remove(address); 210 deleteDatabase(metadata); 211 } 212 } 213 } 214 } 215 isValidMetaKey(int key)216 boolean isValidMetaKey(int key) { 217 switch (key) { 218 case BluetoothDevice.METADATA_MANUFACTURER_NAME: 219 case BluetoothDevice.METADATA_MODEL_NAME: 220 case BluetoothDevice.METADATA_SOFTWARE_VERSION: 221 case BluetoothDevice.METADATA_HARDWARE_VERSION: 222 case BluetoothDevice.METADATA_COMPANION_APP: 223 case BluetoothDevice.METADATA_MAIN_ICON: 224 case BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET: 225 case BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON: 226 case BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON: 227 case BluetoothDevice.METADATA_UNTETHERED_CASE_ICON: 228 case BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY: 229 case BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY: 230 case BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY: 231 case BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING: 232 case BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING: 233 case BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING: 234 case BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI: 235 return true; 236 } 237 Log.w(TAG, "Invalid metadata key " + key); 238 return false; 239 } 240 241 /** 242 * Set customized metadata to database with requested key 243 */ 244 @VisibleForTesting setCustomMeta(BluetoothDevice device, int key, byte[] newValue)245 public boolean setCustomMeta(BluetoothDevice device, int key, byte[] newValue) { 246 synchronized (mMetadataCache) { 247 if (device == null) { 248 Log.e(TAG, "setCustomMeta: device is null"); 249 return false; 250 } 251 if (!isValidMetaKey(key)) { 252 Log.e(TAG, "setCustomMeta: meta key invalid " + key); 253 return false; 254 } 255 256 String address = device.getAddress(); 257 if (!mMetadataCache.containsKey(address)) { 258 createMetadata(address, false); 259 } 260 Metadata data = mMetadataCache.get(address); 261 byte[] oldValue = data.getCustomizedMeta(key); 262 if (oldValue != null && Arrays.equals(oldValue, newValue)) { 263 Log.v(TAG, "setCustomMeta: metadata not changed."); 264 return true; 265 } 266 logManufacturerInfo(device, key, newValue); 267 logMetadataChange(address, "setCustomMeta key=" + key); 268 data.setCustomizedMeta(key, newValue); 269 270 updateDatabase(data); 271 mAdapterService.metadataChanged(address, key, newValue); 272 return true; 273 } 274 } 275 276 /** 277 * Get customized metadata from database with requested key 278 */ 279 @VisibleForTesting getCustomMeta(BluetoothDevice device, int key)280 public byte[] getCustomMeta(BluetoothDevice device, int key) { 281 synchronized (mMetadataCache) { 282 if (device == null) { 283 Log.e(TAG, "getCustomMeta: device is null"); 284 return null; 285 } 286 if (!isValidMetaKey(key)) { 287 Log.e(TAG, "getCustomMeta: meta key invalid " + key); 288 return null; 289 } 290 291 String address = device.getAddress(); 292 293 if (!mMetadataCache.containsKey(address)) { 294 Log.d(TAG, "getCustomMeta: device " + address + " is not in cache"); 295 return null; 296 } 297 298 Metadata data = mMetadataCache.get(address); 299 return data.getCustomizedMeta(key); 300 } 301 } 302 303 /** 304 * Set the device profile connection policy 305 * 306 * @param device {@link BluetoothDevice} wish to set 307 * @param profile The Bluetooth profile; one of {@link BluetoothProfile#HEADSET}, 308 * {@link BluetoothProfile#HEADSET_CLIENT}, {@link BluetoothProfile#A2DP}, 309 * {@link BluetoothProfile#A2DP_SINK}, {@link BluetoothProfile#HID_HOST}, 310 * {@link BluetoothProfile#PAN}, {@link BluetoothProfile#PBAP}, 311 * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP}, 312 * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP}, 313 * {@link BluetoothProfile#HEARING_AID} 314 * @param newConnectionPolicy the connectionPolicy to set; one of 315 * {@link BluetoothProfile.CONNECTION_POLICY_UNKNOWN}, 316 * {@link BluetoothProfile.CONNECTION_POLICY_FORBIDDEN}, 317 * {@link BluetoothProfile.CONNECTION_POLICY_ALLOWED} 318 */ 319 @VisibleForTesting setProfileConnectionPolicy(BluetoothDevice device, int profile, int newConnectionPolicy)320 public boolean setProfileConnectionPolicy(BluetoothDevice device, int profile, 321 int newConnectionPolicy) { 322 synchronized (mMetadataCache) { 323 if (device == null) { 324 Log.e(TAG, "setProfileConnectionPolicy: device is null"); 325 return false; 326 } 327 328 if (newConnectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN 329 && newConnectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 330 && newConnectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 331 Log.e(TAG, "setProfileConnectionPolicy: invalid connection policy " 332 + newConnectionPolicy); 333 return false; 334 } 335 336 String address = device.getAddress(); 337 if (!mMetadataCache.containsKey(address)) { 338 if (newConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_UNKNOWN) { 339 return true; 340 } 341 createMetadata(address, false); 342 } 343 Metadata data = mMetadataCache.get(address); 344 int oldConnectionPolicy = data.getProfileConnectionPolicy(profile); 345 if (oldConnectionPolicy == newConnectionPolicy) { 346 Log.v(TAG, "setProfileConnectionPolicy connection policy not changed."); 347 return true; 348 } 349 String profileStr = BluetoothProfile.getProfileName(profile); 350 logMetadataChange(address, profileStr + " connection policy changed: " 351 + ": " + oldConnectionPolicy + " -> " + newConnectionPolicy); 352 353 data.setProfileConnectionPolicy(profile, newConnectionPolicy); 354 updateDatabase(data); 355 return true; 356 } 357 } 358 359 /** 360 * Get the device profile connection policy 361 * 362 * @param device {@link BluetoothDevice} wish to get 363 * @param profile The Bluetooth profile; one of {@link BluetoothProfile#HEADSET}, 364 * {@link BluetoothProfile#HEADSET_CLIENT}, {@link BluetoothProfile#A2DP}, 365 * {@link BluetoothProfile#A2DP_SINK}, {@link BluetoothProfile#HID_HOST}, 366 * {@link BluetoothProfile#PAN}, {@link BluetoothProfile#PBAP}, 367 * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP}, 368 * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP}, 369 * {@link BluetoothProfile#HEARING_AID} 370 * @return the profile connection policy of the device; one of 371 * {@link BluetoothProfile.CONNECTION_POLICY_UNKNOWN}, 372 * {@link BluetoothProfile.CONNECTION_POLICY_FORBIDDEN}, 373 * {@link BluetoothProfile.CONNECTION_POLICY_ALLOWED} 374 */ 375 @VisibleForTesting getProfileConnectionPolicy(BluetoothDevice device, int profile)376 public int getProfileConnectionPolicy(BluetoothDevice device, int profile) { 377 synchronized (mMetadataCache) { 378 if (device == null) { 379 Log.e(TAG, "getProfileConnectionPolicy: device is null"); 380 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 381 } 382 383 String address = device.getAddress(); 384 385 if (!mMetadataCache.containsKey(address)) { 386 Log.d(TAG, "getProfileConnectionPolicy: device " + address + " is not in cache"); 387 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 388 } 389 390 Metadata data = mMetadataCache.get(address); 391 int connectionPolicy = data.getProfileConnectionPolicy(profile); 392 393 Log.v(TAG, "getProfileConnectionPolicy: " + address + ", profile=" + profile 394 + ", connectionPolicy = " + connectionPolicy); 395 return connectionPolicy; 396 } 397 } 398 399 /** 400 * Set the A2DP optional coedc support value 401 * 402 * @param device {@link BluetoothDevice} wish to set 403 * @param newValue the new A2DP optional coedc support value, one of 404 * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORT_UNKNOWN}, 405 * {@link BluetoothA2dp#OPTIONAL_CODECS_NOT_SUPPORTED}, 406 * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORTED} 407 */ 408 @VisibleForTesting setA2dpSupportsOptionalCodecs(BluetoothDevice device, int newValue)409 public void setA2dpSupportsOptionalCodecs(BluetoothDevice device, int newValue) { 410 synchronized (mMetadataCache) { 411 if (device == null) { 412 Log.e(TAG, "setA2dpOptionalCodec: device is null"); 413 return; 414 } 415 if (newValue != BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN 416 && newValue != BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED 417 && newValue != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) { 418 Log.e(TAG, "setA2dpSupportsOptionalCodecs: invalid value " + newValue); 419 return; 420 } 421 422 String address = device.getAddress(); 423 424 if (!mMetadataCache.containsKey(address)) { 425 return; 426 } 427 Metadata data = mMetadataCache.get(address); 428 int oldValue = data.a2dpSupportsOptionalCodecs; 429 if (oldValue == newValue) { 430 return; 431 } 432 logMetadataChange(address, "Supports optional codec changed: " 433 + oldValue + " -> " + newValue); 434 435 data.a2dpSupportsOptionalCodecs = newValue; 436 updateDatabase(data); 437 } 438 } 439 440 /** 441 * Get the A2DP optional coedc support value 442 * 443 * @param device {@link BluetoothDevice} wish to get 444 * @return the A2DP optional coedc support value, one of 445 * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORT_UNKNOWN}, 446 * {@link BluetoothA2dp#OPTIONAL_CODECS_NOT_SUPPORTED}, 447 * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORTED}, 448 */ 449 @VisibleForTesting 450 @OptionalCodecsSupportStatus getA2dpSupportsOptionalCodecs(BluetoothDevice device)451 public int getA2dpSupportsOptionalCodecs(BluetoothDevice device) { 452 synchronized (mMetadataCache) { 453 if (device == null) { 454 Log.e(TAG, "setA2dpOptionalCodec: device is null"); 455 return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN; 456 } 457 458 String address = device.getAddress(); 459 460 if (!mMetadataCache.containsKey(address)) { 461 Log.d(TAG, "getA2dpOptionalCodec: device " + address + " is not in cache"); 462 return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN; 463 } 464 465 Metadata data = mMetadataCache.get(address); 466 return data.a2dpSupportsOptionalCodecs; 467 } 468 } 469 470 /** 471 * Set the A2DP optional coedc enabled value 472 * 473 * @param device {@link BluetoothDevice} wish to set 474 * @param newValue the new A2DP optional coedc enabled value, one of 475 * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_UNKNOWN}, 476 * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_DISABLED}, 477 * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_ENABLED} 478 */ 479 @VisibleForTesting setA2dpOptionalCodecsEnabled(BluetoothDevice device, int newValue)480 public void setA2dpOptionalCodecsEnabled(BluetoothDevice device, int newValue) { 481 synchronized (mMetadataCache) { 482 if (device == null) { 483 Log.e(TAG, "setA2dpOptionalCodecEnabled: device is null"); 484 return; 485 } 486 if (newValue != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN 487 && newValue != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED 488 && newValue != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) { 489 Log.e(TAG, "setA2dpOptionalCodecsEnabled: invalid value " + newValue); 490 return; 491 } 492 493 String address = device.getAddress(); 494 495 if (!mMetadataCache.containsKey(address)) { 496 return; 497 } 498 Metadata data = mMetadataCache.get(address); 499 int oldValue = data.a2dpOptionalCodecsEnabled; 500 if (oldValue == newValue) { 501 return; 502 } 503 logMetadataChange(address, "Enable optional codec changed: " 504 + oldValue + " -> " + newValue); 505 506 data.a2dpOptionalCodecsEnabled = newValue; 507 updateDatabase(data); 508 } 509 } 510 511 /** 512 * Get the A2DP optional coedc enabled value 513 * 514 * @param device {@link BluetoothDevice} wish to get 515 * @return the A2DP optional coedc enabled value, one of 516 * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_UNKNOWN}, 517 * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_DISABLED}, 518 * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_ENABLED} 519 */ 520 @VisibleForTesting 521 @OptionalCodecsPreferenceStatus getA2dpOptionalCodecsEnabled(BluetoothDevice device)522 public int getA2dpOptionalCodecsEnabled(BluetoothDevice device) { 523 synchronized (mMetadataCache) { 524 if (device == null) { 525 Log.e(TAG, "getA2dpOptionalCodecEnabled: device is null"); 526 return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN; 527 } 528 529 String address = device.getAddress(); 530 531 if (!mMetadataCache.containsKey(address)) { 532 Log.d(TAG, "getA2dpOptionalCodecEnabled: device " + address + " is not in cache"); 533 return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN; 534 } 535 536 Metadata data = mMetadataCache.get(address); 537 return data.a2dpOptionalCodecsEnabled; 538 } 539 } 540 541 /** 542 * Updates the time this device was last connected 543 * 544 * @param device is the remote bluetooth device for which we are setting the connection time 545 */ setConnection(BluetoothDevice device, boolean isA2dpDevice)546 public void setConnection(BluetoothDevice device, boolean isA2dpDevice) { 547 synchronized (mMetadataCache) { 548 Log.d(TAG, "setConnection: device=" + device + " and isA2dpDevice=" + isA2dpDevice); 549 if (device == null) { 550 Log.e(TAG, "setConnection: device is null"); 551 return; 552 } 553 554 if (isA2dpDevice) { 555 resetActiveA2dpDevice(); 556 } 557 558 String address = device.getAddress(); 559 560 if (!mMetadataCache.containsKey(address)) { 561 Log.d(TAG, "setConnection: Creating new metadata entry for device: " + device); 562 createMetadata(address, isA2dpDevice); 563 return; 564 } 565 // Updates last_active_time to the current counter value and increments the counter 566 Metadata metadata = mMetadataCache.get(address); 567 metadata.last_active_time = MetadataDatabase.sCurrentConnectionNumber++; 568 569 // Only update is_active_a2dp_device if an a2dp device is connected 570 if (isA2dpDevice) { 571 metadata.is_active_a2dp_device = true; 572 } 573 574 Log.d(TAG, "Updating last connected time for device: " + device + " to " 575 + metadata.last_active_time); 576 updateDatabase(metadata); 577 } 578 } 579 580 /** 581 * Sets is_active_device to false if currently true for device 582 * 583 * @param device is the remote bluetooth device with which we have disconnected a2dp 584 */ setDisconnection(BluetoothDevice device)585 public void setDisconnection(BluetoothDevice device) { 586 synchronized (mMetadataCache) { 587 if (device == null) { 588 Log.e(TAG, "setDisconnection: device is null"); 589 return; 590 } 591 592 String address = device.getAddress(); 593 594 if (!mMetadataCache.containsKey(address)) { 595 return; 596 } 597 // Updates last connected time to either current time if connected or -1 if disconnected 598 Metadata metadata = mMetadataCache.get(address); 599 if (metadata.is_active_a2dp_device) { 600 metadata.is_active_a2dp_device = false; 601 Log.d(TAG, "setDisconnection: Updating is_active_device to false for device: " 602 + device); 603 updateDatabase(metadata); 604 } 605 } 606 } 607 608 /** 609 * Remove a2dpActiveDevice from the current active device in the connection order table 610 */ resetActiveA2dpDevice()611 private void resetActiveA2dpDevice() { 612 synchronized (mMetadataCache) { 613 Log.d(TAG, "resetActiveA2dpDevice()"); 614 for (Map.Entry<String, Metadata> entry : mMetadataCache.entrySet()) { 615 Metadata metadata = entry.getValue(); 616 if (metadata.is_active_a2dp_device) { 617 Log.d(TAG, "resetActiveA2dpDevice"); 618 metadata.is_active_a2dp_device = false; 619 updateDatabase(metadata); 620 } 621 } 622 } 623 } 624 625 /** 626 * Gets the most recently connected bluetooth devices in order with most recently connected 627 * first and least recently connected last 628 * 629 * @return a {@link List} of {@link BluetoothDevice} representing connected bluetooth devices 630 * in order of most recently connected 631 */ getMostRecentlyConnectedDevices()632 public List<BluetoothDevice> getMostRecentlyConnectedDevices() { 633 List<BluetoothDevice> mostRecentlyConnectedDevices = new ArrayList<>(); 634 synchronized (mMetadataCache) { 635 List<Metadata> sortedMetadata = new ArrayList<>(mMetadataCache.values()); 636 sortedMetadata.sort((o1, o2) -> Long.compare(o2.last_active_time, o1.last_active_time)); 637 for (Metadata metadata : sortedMetadata) { 638 try { 639 mostRecentlyConnectedDevices.add(BluetoothAdapter.getDefaultAdapter() 640 .getRemoteDevice(metadata.getAddress())); 641 } catch (IllegalArgumentException ex) { 642 Log.d(TAG, "getBondedDevicesOrdered: Invalid address for " 643 + "device " + metadata.getAddress()); 644 } 645 } 646 } 647 return mostRecentlyConnectedDevices; 648 } 649 650 /** 651 * Gets the last active a2dp device 652 * 653 * @return the most recently active a2dp device or null if the last a2dp device was null 654 */ getMostRecentlyConnectedA2dpDevice()655 public BluetoothDevice getMostRecentlyConnectedA2dpDevice() { 656 synchronized (mMetadataCache) { 657 for (Map.Entry<String, Metadata> entry : mMetadataCache.entrySet()) { 658 Metadata metadata = entry.getValue(); 659 if (metadata.is_active_a2dp_device) { 660 try { 661 return BluetoothAdapter.getDefaultAdapter().getRemoteDevice( 662 metadata.getAddress()); 663 } catch (IllegalArgumentException ex) { 664 Log.d(TAG, "getMostRecentlyConnectedA2dpDevice: Invalid address for " 665 + "device " + metadata.getAddress()); 666 } 667 } 668 } 669 } 670 return null; 671 } 672 673 /** 674 * 675 * @param metadataList is the list of metadata 676 */ compactLastConnectionTime(List<Metadata> metadataList)677 private void compactLastConnectionTime(List<Metadata> metadataList) { 678 Log.d(TAG, "compactLastConnectionTime: Compacting metadata after load"); 679 MetadataDatabase.sCurrentConnectionNumber = 0; 680 // Have to go in reverse order as list is ordered by descending last_active_time 681 for (int index = metadataList.size() - 1; index >= 0; index--) { 682 Metadata metadata = metadataList.get(index); 683 if (metadata.last_active_time != MetadataDatabase.sCurrentConnectionNumber) { 684 Log.d(TAG, "compactLastConnectionTime: Setting last_active_item for device: " 685 + metadata.getAddress() + " from " + metadata.last_active_time + " to " 686 + MetadataDatabase.sCurrentConnectionNumber); 687 metadata.last_active_time = MetadataDatabase.sCurrentConnectionNumber; 688 updateDatabase(metadata); 689 MetadataDatabase.sCurrentConnectionNumber++; 690 } 691 } 692 } 693 694 /** 695 * Get the {@link Looper} for the handler thread. This is used in testing and helper 696 * objects 697 * 698 * @return {@link Looper} for the handler thread 699 */ 700 @VisibleForTesting getHandlerLooper()701 public Looper getHandlerLooper() { 702 if (mHandlerThread == null) { 703 return null; 704 } 705 return mHandlerThread.getLooper(); 706 } 707 708 /** 709 * Start and initialize the DatabaseManager 710 * 711 * @param database the Bluetooth storage {@link MetadataDatabase} 712 */ start(MetadataDatabase database)713 public void start(MetadataDatabase database) { 714 Log.d(TAG, "start()"); 715 if (mAdapterService == null) { 716 Log.e(TAG, "stat failed, mAdapterService is null."); 717 return; 718 } 719 720 if (database == null) { 721 Log.e(TAG, "stat failed, database is null."); 722 return; 723 } 724 725 mDatabase = database; 726 727 mHandlerThread = new HandlerThread("BluetoothDatabaseManager"); 728 mHandlerThread.start(); 729 mHandler = new DatabaseHandler(mHandlerThread.getLooper()); 730 731 IntentFilter filter = new IntentFilter(); 732 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 733 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 734 mAdapterService.registerReceiver(mReceiver, filter); 735 736 loadDatabase(); 737 } 738 getDatabaseAbsolutePath()739 String getDatabaseAbsolutePath() { 740 //TODO backup database when Bluetooth turn off and FOTA? 741 return mAdapterService.getDatabasePath(MetadataDatabase.DATABASE_NAME) 742 .getAbsolutePath(); 743 } 744 745 /** 746 * Clear all persistence data in database 747 */ factoryReset()748 public void factoryReset() { 749 Log.w(TAG, "factoryReset"); 750 Message message = mHandler.obtainMessage(MSG_CLEAR_DATABASE); 751 mHandler.sendMessage(message); 752 } 753 754 /** 755 * Close and de-init the DatabaseManager 756 */ cleanup()757 public void cleanup() { 758 removeUnusedMetadata(); 759 mAdapterService.unregisterReceiver(mReceiver); 760 if (mHandlerThread != null) { 761 mHandlerThread.quit(); 762 mHandlerThread = null; 763 } 764 mMetadataCache.clear(); 765 } 766 createMetadata(String address, boolean isActiveA2dpDevice)767 void createMetadata(String address, boolean isActiveA2dpDevice) { 768 Metadata data = new Metadata(address); 769 data.is_active_a2dp_device = isActiveA2dpDevice; 770 mMetadataCache.put(address, data); 771 updateDatabase(data); 772 logMetadataChange(address, "Metadata created"); 773 } 774 775 @VisibleForTesting removeUnusedMetadata()776 void removeUnusedMetadata() { 777 BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 778 synchronized (mMetadataCache) { 779 mMetadataCache.forEach((address, metadata) -> { 780 if (!address.equals(LOCAL_STORAGE) 781 && !Arrays.asList(bondedDevices).stream().anyMatch(device -> 782 address.equals(device.getAddress()))) { 783 List<Integer> list = metadata.getChangedCustomizedMeta(); 784 for (int key : list) { 785 mAdapterService.metadataChanged(address, key, null); 786 } 787 Log.i(TAG, "remove unpaired device from database " + address); 788 deleteDatabase(mMetadataCache.get(address)); 789 } 790 }); 791 } 792 } 793 cacheMetadata(List<Metadata> list)794 void cacheMetadata(List<Metadata> list) { 795 synchronized (mMetadataCache) { 796 Log.i(TAG, "cacheMetadata"); 797 // Unlock the main thread. 798 mSemaphore.release(); 799 800 if (!isMigrated(list)) { 801 // Wait for data migrate from Settings Global 802 mMigratedFromSettingsGlobal = false; 803 return; 804 } 805 mMigratedFromSettingsGlobal = true; 806 for (Metadata data : list) { 807 String address = data.getAddress(); 808 Log.v(TAG, "cacheMetadata: found device " + address); 809 mMetadataCache.put(address, data); 810 } 811 Log.i(TAG, "cacheMetadata: Database is ready"); 812 } 813 } 814 isMigrated(List<Metadata> list)815 boolean isMigrated(List<Metadata> list) { 816 for (Metadata data : list) { 817 String address = data.getAddress(); 818 if (address.equals(LOCAL_STORAGE) && data.migrated) { 819 return true; 820 } 821 } 822 return false; 823 } 824 migrateSettingsGlobal()825 void migrateSettingsGlobal() { 826 Log.i(TAG, "migrateSettingGlobal"); 827 828 BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 829 ContentResolver contentResolver = mAdapterService.getContentResolver(); 830 831 for (BluetoothDevice device : bondedDevices) { 832 int a2dpConnectionPolicy = Settings.Global.getInt(contentResolver, 833 getLegacyA2dpSinkPriorityKey(device.getAddress()), 834 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 835 int a2dpSinkConnectionPolicy = Settings.Global.getInt(contentResolver, 836 getLegacyA2dpSrcPriorityKey(device.getAddress()), 837 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 838 int hearingaidConnectionPolicy = Settings.Global.getInt(contentResolver, 839 getLegacyHearingAidPriorityKey(device.getAddress()), 840 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 841 int headsetConnectionPolicy = Settings.Global.getInt(contentResolver, 842 getLegacyHeadsetPriorityKey(device.getAddress()), 843 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 844 int headsetClientConnectionPolicy = Settings.Global.getInt(contentResolver, 845 getLegacyHeadsetPriorityKey(device.getAddress()), 846 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 847 int hidHostConnectionPolicy = Settings.Global.getInt(contentResolver, 848 getLegacyHidHostPriorityKey(device.getAddress()), 849 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 850 int mapConnectionPolicy = Settings.Global.getInt(contentResolver, 851 getLegacyMapPriorityKey(device.getAddress()), 852 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 853 int mapClientConnectionPolicy = Settings.Global.getInt(contentResolver, 854 getLegacyMapClientPriorityKey(device.getAddress()), 855 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 856 int panConnectionPolicy = Settings.Global.getInt(contentResolver, 857 getLegacyPanPriorityKey(device.getAddress()), 858 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 859 int pbapConnectionPolicy = Settings.Global.getInt(contentResolver, 860 getLegacyPbapClientPriorityKey(device.getAddress()), 861 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 862 int pbapClientConnectionPolicy = Settings.Global.getInt(contentResolver, 863 getLegacyPbapClientPriorityKey(device.getAddress()), 864 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 865 int sapConnectionPolicy = Settings.Global.getInt(contentResolver, 866 getLegacySapPriorityKey(device.getAddress()), 867 BluetoothProfile.CONNECTION_POLICY_UNKNOWN); 868 int a2dpSupportsOptionalCodec = Settings.Global.getInt(contentResolver, 869 getLegacyA2dpSupportsOptionalCodecsKey(device.getAddress()), 870 BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN); 871 int a2dpOptionalCodecEnabled = Settings.Global.getInt(contentResolver, 872 getLegacyA2dpOptionalCodecsEnabledKey(device.getAddress()), 873 BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN); 874 875 String address = device.getAddress(); 876 Metadata data = new Metadata(address); 877 data.setProfileConnectionPolicy(BluetoothProfile.A2DP, a2dpConnectionPolicy); 878 data.setProfileConnectionPolicy(BluetoothProfile.A2DP_SINK, a2dpSinkConnectionPolicy); 879 data.setProfileConnectionPolicy(BluetoothProfile.HEADSET, headsetConnectionPolicy); 880 data.setProfileConnectionPolicy(BluetoothProfile.HEADSET_CLIENT, 881 headsetClientConnectionPolicy); 882 data.setProfileConnectionPolicy(BluetoothProfile.HID_HOST, hidHostConnectionPolicy); 883 data.setProfileConnectionPolicy(BluetoothProfile.PAN, panConnectionPolicy); 884 data.setProfileConnectionPolicy(BluetoothProfile.PBAP, pbapConnectionPolicy); 885 data.setProfileConnectionPolicy(BluetoothProfile.PBAP_CLIENT, 886 pbapClientConnectionPolicy); 887 data.setProfileConnectionPolicy(BluetoothProfile.MAP, mapConnectionPolicy); 888 data.setProfileConnectionPolicy(BluetoothProfile.MAP_CLIENT, mapClientConnectionPolicy); 889 data.setProfileConnectionPolicy(BluetoothProfile.SAP, sapConnectionPolicy); 890 data.setProfileConnectionPolicy(BluetoothProfile.HEARING_AID, 891 hearingaidConnectionPolicy); 892 data.a2dpSupportsOptionalCodecs = a2dpSupportsOptionalCodec; 893 data.a2dpOptionalCodecsEnabled = a2dpOptionalCodecEnabled; 894 mMetadataCache.put(address, data); 895 updateDatabase(data); 896 } 897 898 // Mark database migrated from Settings Global 899 Metadata localData = new Metadata(LOCAL_STORAGE); 900 localData.migrated = true; 901 mMetadataCache.put(LOCAL_STORAGE, localData); 902 updateDatabase(localData); 903 904 // Reload database after migration is completed 905 loadDatabase(); 906 907 } 908 909 /** 910 * Get the key that retrieves a bluetooth headset's priority. 911 */ getLegacyHeadsetPriorityKey(String address)912 private static String getLegacyHeadsetPriorityKey(String address) { 913 return LEGACY_HEADSET_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 914 } 915 916 /** 917 * Get the key that retrieves a bluetooth a2dp sink's priority. 918 */ getLegacyA2dpSinkPriorityKey(String address)919 private static String getLegacyA2dpSinkPriorityKey(String address) { 920 return LEGACY_A2DP_SINK_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 921 } 922 923 /** 924 * Get the key that retrieves a bluetooth a2dp src's priority. 925 */ getLegacyA2dpSrcPriorityKey(String address)926 private static String getLegacyA2dpSrcPriorityKey(String address) { 927 return LEGACY_A2DP_SRC_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 928 } 929 930 /** 931 * Get the key that retrieves a bluetooth a2dp device's ability to support optional codecs. 932 */ getLegacyA2dpSupportsOptionalCodecsKey(String address)933 private static String getLegacyA2dpSupportsOptionalCodecsKey(String address) { 934 return LEGACY_A2DP_SUPPORTS_OPTIONAL_CODECS_PREFIX 935 + address.toUpperCase(Locale.ROOT); 936 } 937 938 /** 939 * Get the key that retrieves whether a bluetooth a2dp device should have optional codecs 940 * enabled. 941 */ getLegacyA2dpOptionalCodecsEnabledKey(String address)942 private static String getLegacyA2dpOptionalCodecsEnabledKey(String address) { 943 return LEGACY_A2DP_OPTIONAL_CODECS_ENABLED_PREFIX 944 + address.toUpperCase(Locale.ROOT); 945 } 946 947 /** 948 * Get the key that retrieves a bluetooth Input Device's priority. 949 */ getLegacyHidHostPriorityKey(String address)950 private static String getLegacyHidHostPriorityKey(String address) { 951 return LEGACY_INPUT_DEVICE_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 952 } 953 954 /** 955 * Get the key that retrieves a bluetooth pan client priority. 956 */ getLegacyPanPriorityKey(String address)957 private static String getLegacyPanPriorityKey(String address) { 958 return LEGACY_PAN_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 959 } 960 961 /** 962 * Get the key that retrieves a bluetooth hearing aid priority. 963 */ getLegacyHearingAidPriorityKey(String address)964 private static String getLegacyHearingAidPriorityKey(String address) { 965 return LEGACY_HEARING_AID_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 966 } 967 968 /** 969 * Get the key that retrieves a bluetooth map priority. 970 */ getLegacyMapPriorityKey(String address)971 private static String getLegacyMapPriorityKey(String address) { 972 return LEGACY_MAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 973 } 974 975 /** 976 * Get the key that retrieves a bluetooth map client priority. 977 */ getLegacyMapClientPriorityKey(String address)978 private static String getLegacyMapClientPriorityKey(String address) { 979 return LEGACY_MAP_CLIENT_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 980 } 981 982 /** 983 * Get the key that retrieves a bluetooth pbap client priority. 984 */ getLegacyPbapClientPriorityKey(String address)985 private static String getLegacyPbapClientPriorityKey(String address) { 986 return LEGACY_PBAP_CLIENT_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 987 } 988 989 /** 990 * Get the key that retrieves a bluetooth sap priority. 991 */ getLegacySapPriorityKey(String address)992 private static String getLegacySapPriorityKey(String address) { 993 return LEGACY_SAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); 994 } 995 loadDatabase()996 private void loadDatabase() { 997 Log.d(TAG, "Load Database"); 998 Message message = mHandler.obtainMessage(MSG_LOAD_DATABASE); 999 mHandler.sendMessage(message); 1000 try { 1001 // Lock the thread until handler thread finish loading database. 1002 mSemaphore.tryAcquire(LOAD_DATABASE_TIMEOUT, TimeUnit.MILLISECONDS); 1003 } catch (InterruptedException e) { 1004 Log.e(TAG, "loadDatabase: semaphore acquire failed"); 1005 } 1006 } 1007 updateDatabase(Metadata data)1008 private void updateDatabase(Metadata data) { 1009 if (data.getAddress() == null) { 1010 Log.e(TAG, "updateDatabase: address is null"); 1011 return; 1012 } 1013 Log.d(TAG, "updateDatabase " + data.getAddress()); 1014 Message message = mHandler.obtainMessage(MSG_UPDATE_DATABASE); 1015 message.obj = data; 1016 mHandler.sendMessage(message); 1017 } 1018 1019 @VisibleForTesting deleteDatabase(Metadata data)1020 void deleteDatabase(Metadata data) { 1021 String address = data.getAddress(); 1022 if (address == null) { 1023 Log.e(TAG, "deleteDatabase: address is null"); 1024 return; 1025 } 1026 logMetadataChange(address, "Metadata deleted"); 1027 Message message = mHandler.obtainMessage(MSG_DELETE_DATABASE); 1028 message.obj = data.getAddress(); 1029 mHandler.sendMessage(message); 1030 } 1031 logManufacturerInfo(BluetoothDevice device, int key, byte[] bytesValue)1032 private void logManufacturerInfo(BluetoothDevice device, int key, byte[] bytesValue) { 1033 String callingApp = mAdapterService.getPackageManager().getNameForUid( 1034 Binder.getCallingUid()); 1035 String manufacturerName = ""; 1036 String modelName = ""; 1037 String hardwareVersion = ""; 1038 String softwareVersion = ""; 1039 String value = Utils.byteArrayToUtf8String(bytesValue); 1040 switch (key) { 1041 case BluetoothDevice.METADATA_MANUFACTURER_NAME: 1042 manufacturerName = value; 1043 break; 1044 case BluetoothDevice.METADATA_MODEL_NAME: 1045 modelName = value; 1046 break; 1047 case BluetoothDevice.METADATA_HARDWARE_VERSION: 1048 hardwareVersion = value; 1049 break; 1050 case BluetoothDevice.METADATA_SOFTWARE_VERSION: 1051 softwareVersion = value; 1052 break; 1053 default: 1054 // Do not log anything if metadata doesn't fall into above categories 1055 return; 1056 } 1057 BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_DEVICE_INFO_REPORTED, 1058 mAdapterService.obfuscateAddress(device), 1059 BluetoothProtoEnums.DEVICE_INFO_EXTERNAL, callingApp, manufacturerName, modelName, 1060 hardwareVersion, softwareVersion, mAdapterService.getMetricId(device)); 1061 } 1062 logMetadataChange(String address, String log)1063 private void logMetadataChange(String address, String log) { 1064 String time = Utils.getLocalTimeString(); 1065 String uidPid = Utils.getUidPidString(); 1066 mMetadataChangedLog.add(time + " (" + uidPid + ") " + address + " " + log); 1067 } 1068 1069 /** 1070 * Dump database info to a PrintWriter 1071 * 1072 * @param writer the PrintWriter to write log 1073 */ dump(PrintWriter writer)1074 public void dump(PrintWriter writer) { 1075 writer.println("\nBluetoothDatabase:"); 1076 writer.println(" Metadata Changes:"); 1077 for (String log : mMetadataChangedLog) { 1078 writer.println(" " + log); 1079 } 1080 writer.println("\nMetadata:"); 1081 for (HashMap.Entry<String, Metadata> entry : mMetadataCache.entrySet()) { 1082 if (entry.getKey().equals(LOCAL_STORAGE)) { 1083 // No need to dump local storage 1084 continue; 1085 } 1086 writer.println(" " + entry.getValue()); 1087 } 1088 } 1089 } 1090