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