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