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