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