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