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