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