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