• 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.bluetooth.BluetoothSinkAudioPolicy;
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 audio policy metadata to database with requested key
289      */
290     @VisibleForTesting
setAudioPolicyMetadata(BluetoothDevice device, BluetoothSinkAudioPolicy policies)291     public boolean setAudioPolicyMetadata(BluetoothDevice device,
292             BluetoothSinkAudioPolicy policies) {
293         synchronized (mMetadataCache) {
294             if (device == null) {
295                 Log.e(TAG, "setAudioPolicyMetadata: device is null");
296                 return false;
297             }
298 
299             String address = device.getAddress();
300             if (!mMetadataCache.containsKey(address)) {
301                 createMetadata(address, false);
302             }
303             Metadata data = mMetadataCache.get(address);
304             AudioPolicyEntity entity = data.audioPolicyMetadata;
305             entity.callEstablishAudioPolicy = policies.getCallEstablishPolicy();
306             entity.connectingTimeAudioPolicy = policies.getActiveDevicePolicyAfterConnection();
307             entity.inBandRingtoneAudioPolicy = policies.getInBandRingtonePolicy();
308 
309             updateDatabase(data);
310             return true;
311         }
312     }
313 
314     /**
315      * Get audio policy metadata from database with requested key
316      */
317     @VisibleForTesting
getAudioPolicyMetadata(BluetoothDevice device)318     public BluetoothSinkAudioPolicy getAudioPolicyMetadata(BluetoothDevice device) {
319         synchronized (mMetadataCache) {
320             if (device == null) {
321                 Log.e(TAG, "getAudioPolicyMetadata: device is null");
322                 return null;
323             }
324 
325             String address = device.getAddress();
326 
327             if (!mMetadataCache.containsKey(address)) {
328                 Log.d(TAG, "getAudioPolicyMetadata: device " + address + " is not in cache");
329                 return null;
330             }
331 
332             AudioPolicyEntity entity = mMetadataCache.get(address).audioPolicyMetadata;
333             return new BluetoothSinkAudioPolicy.Builder()
334                     .setCallEstablishPolicy(entity.callEstablishAudioPolicy)
335                     .setActiveDevicePolicyAfterConnection(entity.connectingTimeAudioPolicy)
336                     .setInBandRingtonePolicy(entity.inBandRingtoneAudioPolicy)
337                     .build();
338         }
339     }
340 
341     /**
342      * Set the device profile connection policy
343      *
344      * @param device {@link BluetoothDevice} wish to set
345      * @param profile The Bluetooth profile; one of {@link BluetoothProfile#HEADSET},
346      * {@link BluetoothProfile#HEADSET_CLIENT}, {@link BluetoothProfile#A2DP},
347      * {@link BluetoothProfile#A2DP_SINK}, {@link BluetoothProfile#HID_HOST},
348      * {@link BluetoothProfile#PAN}, {@link BluetoothProfile#PBAP},
349      * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
350      * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
351      * {@link BluetoothProfile#HEARING_AID}, {@link BluetoothProfile#LE_AUDIO},
352      * {@link BluetoothProfile#VOLUME_CONTROL}, {@link BluetoothProfile#CSIP_SET_COORDINATOR},
353      * {@link BluetoothProfile#LE_AUDIO_BROADCAST_ASSISTANT},
354      * @param newConnectionPolicy the connectionPolicy to set; one of
355      * {@link BluetoothProfile.CONNECTION_POLICY_UNKNOWN},
356      * {@link BluetoothProfile.CONNECTION_POLICY_FORBIDDEN},
357      * {@link BluetoothProfile.CONNECTION_POLICY_ALLOWED}
358      */
359     @VisibleForTesting
setProfileConnectionPolicy(BluetoothDevice device, int profile, int newConnectionPolicy)360     public boolean setProfileConnectionPolicy(BluetoothDevice device, int profile,
361             int newConnectionPolicy) {
362         synchronized (mMetadataCache) {
363             if (device == null) {
364                 Log.e(TAG, "setProfileConnectionPolicy: device is null");
365                 return false;
366             }
367 
368             if (newConnectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
369                     && newConnectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
370                     && newConnectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
371                 Log.e(TAG, "setProfileConnectionPolicy: invalid connection policy "
372                         + newConnectionPolicy);
373                 return false;
374             }
375 
376             String address = device.getAddress();
377             if (!mMetadataCache.containsKey(address)) {
378                 if (newConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_UNKNOWN) {
379                     return true;
380                 }
381                 createMetadata(address, false);
382             }
383             Metadata data = mMetadataCache.get(address);
384             int oldConnectionPolicy = data.getProfileConnectionPolicy(profile);
385             if (oldConnectionPolicy == newConnectionPolicy) {
386                 Log.v(TAG, "setProfileConnectionPolicy connection policy not changed.");
387                 return true;
388             }
389             String profileStr = BluetoothProfile.getProfileName(profile);
390             logMetadataChange(address, profileStr + " connection policy changed: "
391                     + ": " + oldConnectionPolicy + " -> " + newConnectionPolicy);
392 
393             data.setProfileConnectionPolicy(profile, newConnectionPolicy);
394             updateDatabase(data);
395             return true;
396         }
397     }
398 
399     /**
400      * Get the device profile connection policy
401      *
402      * @param device {@link BluetoothDevice} wish to get
403      * @param profile The Bluetooth profile; one of {@link BluetoothProfile#HEADSET},
404      * {@link BluetoothProfile#HEADSET_CLIENT}, {@link BluetoothProfile#A2DP},
405      * {@link BluetoothProfile#A2DP_SINK}, {@link BluetoothProfile#HID_HOST},
406      * {@link BluetoothProfile#PAN}, {@link BluetoothProfile#PBAP},
407      * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
408      * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
409      * {@link BluetoothProfile#HEARING_AID}, {@link BluetoothProfile#LE_AUDIO},
410      * {@link BluetoothProfile#VOLUME_CONTROL}, {@link BluetoothProfile#CSIP_SET_COORDINATOR},
411      * {@link BluetoothProfile#LE_AUDIO_BROADCAST_ASSISTANT},
412      * @return the profile connection policy of the device; one of
413      * {@link BluetoothProfile.CONNECTION_POLICY_UNKNOWN},
414      * {@link BluetoothProfile.CONNECTION_POLICY_FORBIDDEN},
415      * {@link BluetoothProfile.CONNECTION_POLICY_ALLOWED}
416      */
417     @VisibleForTesting
getProfileConnectionPolicy(BluetoothDevice device, int profile)418     public int getProfileConnectionPolicy(BluetoothDevice device, int profile) {
419         synchronized (mMetadataCache) {
420             if (device == null) {
421                 Log.e(TAG, "getProfileConnectionPolicy: device is null");
422                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
423             }
424 
425             String address = device.getAddress();
426 
427             if (!mMetadataCache.containsKey(address)) {
428                 Log.d(TAG, "getProfileConnectionPolicy: device " + device.getAnonymizedAddress()
429                         + " is not in cache");
430                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
431             }
432 
433             Metadata data = mMetadataCache.get(address);
434             int connectionPolicy = data.getProfileConnectionPolicy(profile);
435 
436             Log.v(TAG, "getProfileConnectionPolicy: device " + device.getAnonymizedAddress()
437                     + " profile=" + BluetoothProfile.getProfileName(profile)
438                     + ", connectionPolicy=" + connectionPolicy);
439             return connectionPolicy;
440         }
441     }
442 
443     /**
444      * Set the A2DP optional coedc support value
445      *
446      * @param device {@link BluetoothDevice} wish to set
447      * @param newValue the new A2DP optional coedc support value, one of
448      * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORT_UNKNOWN},
449      * {@link BluetoothA2dp#OPTIONAL_CODECS_NOT_SUPPORTED},
450      * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORTED}
451      */
452     @VisibleForTesting
setA2dpSupportsOptionalCodecs(BluetoothDevice device, int newValue)453     public void setA2dpSupportsOptionalCodecs(BluetoothDevice device, int newValue) {
454         synchronized (mMetadataCache) {
455             if (device == null) {
456                 Log.e(TAG, "setA2dpOptionalCodec: device is null");
457                 return;
458             }
459             if (newValue != BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN
460                     && newValue != BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED
461                     && newValue != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
462                 Log.e(TAG, "setA2dpSupportsOptionalCodecs: invalid value " + newValue);
463                 return;
464             }
465 
466             String address = device.getAddress();
467 
468             if (!mMetadataCache.containsKey(address)) {
469                 return;
470             }
471             Metadata data = mMetadataCache.get(address);
472             int oldValue = data.a2dpSupportsOptionalCodecs;
473             if (oldValue == newValue) {
474                 return;
475             }
476             logMetadataChange(address, "Supports optional codec changed: "
477                     + oldValue + " -> " + newValue);
478 
479             data.a2dpSupportsOptionalCodecs = newValue;
480             updateDatabase(data);
481         }
482     }
483 
484     /**
485      * Get the A2DP optional coedc support value
486      *
487      * @param device {@link BluetoothDevice} wish to get
488      * @return the A2DP optional coedc support value, one of
489      * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORT_UNKNOWN},
490      * {@link BluetoothA2dp#OPTIONAL_CODECS_NOT_SUPPORTED},
491      * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORTED},
492      */
493     @VisibleForTesting
494     @OptionalCodecsSupportStatus
getA2dpSupportsOptionalCodecs(BluetoothDevice device)495     public int getA2dpSupportsOptionalCodecs(BluetoothDevice device) {
496         synchronized (mMetadataCache) {
497             if (device == null) {
498                 Log.e(TAG, "setA2dpOptionalCodec: device is null");
499                 return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
500             }
501 
502             String address = device.getAddress();
503 
504             if (!mMetadataCache.containsKey(address)) {
505                 Log.d(TAG, "getA2dpOptionalCodec: device " + address + " is not in cache");
506                 return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
507             }
508 
509             Metadata data = mMetadataCache.get(address);
510             return data.a2dpSupportsOptionalCodecs;
511         }
512     }
513 
514     /**
515      * Set the A2DP optional coedc enabled value
516      *
517      * @param device {@link BluetoothDevice} wish to set
518      * @param newValue the new A2DP optional coedc enabled value, one of
519      * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_UNKNOWN},
520      * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_DISABLED},
521      * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_ENABLED}
522      */
523     @VisibleForTesting
setA2dpOptionalCodecsEnabled(BluetoothDevice device, int newValue)524     public void setA2dpOptionalCodecsEnabled(BluetoothDevice device, int newValue) {
525         synchronized (mMetadataCache) {
526             if (device == null) {
527                 Log.e(TAG, "setA2dpOptionalCodecEnabled: device is null");
528                 return;
529             }
530             if (newValue != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
531                     && newValue != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
532                     && newValue != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
533                 Log.e(TAG, "setA2dpOptionalCodecsEnabled: invalid value " + newValue);
534                 return;
535             }
536 
537             String address = device.getAddress();
538 
539             if (!mMetadataCache.containsKey(address)) {
540                 return;
541             }
542             Metadata data = mMetadataCache.get(address);
543             int oldValue = data.a2dpOptionalCodecsEnabled;
544             if (oldValue == newValue) {
545                 return;
546             }
547             logMetadataChange(address, "Enable optional codec changed: "
548                     + oldValue + " -> " + newValue);
549 
550             data.a2dpOptionalCodecsEnabled = newValue;
551             updateDatabase(data);
552         }
553     }
554 
555     /**
556      * Get the A2DP optional coedc enabled value
557      *
558      * @param device {@link BluetoothDevice} wish to get
559      * @return the A2DP optional coedc enabled value, one of
560      * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_UNKNOWN},
561      * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_DISABLED},
562      * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_ENABLED}
563      */
564     @VisibleForTesting
565     @OptionalCodecsPreferenceStatus
getA2dpOptionalCodecsEnabled(BluetoothDevice device)566     public int getA2dpOptionalCodecsEnabled(BluetoothDevice device) {
567         synchronized (mMetadataCache) {
568             if (device == null) {
569                 Log.e(TAG, "getA2dpOptionalCodecEnabled: device is null");
570                 return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
571             }
572 
573             String address = device.getAddress();
574 
575             if (!mMetadataCache.containsKey(address)) {
576                 Log.d(TAG, "getA2dpOptionalCodecEnabled: device " + address + " is not in cache");
577                 return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
578             }
579 
580             Metadata data = mMetadataCache.get(address);
581             return data.a2dpOptionalCodecsEnabled;
582         }
583     }
584 
585     /**
586      * Updates the time this device was last connected
587      *
588      * @param device is the remote bluetooth device for which we are setting the connection time
589      */
setConnection(BluetoothDevice device, boolean isA2dpDevice)590     public void setConnection(BluetoothDevice device, boolean isA2dpDevice) {
591         synchronized (mMetadataCache) {
592             Log.d(TAG, "setConnection: device " + device.getAnonymizedAddress()
593                     + " and isA2dpDevice=" + isA2dpDevice);
594             if (device == null) {
595                 Log.e(TAG, "setConnection: device is null");
596                 return;
597             }
598 
599             if (isA2dpDevice) {
600                 resetActiveA2dpDevice();
601             }
602 
603             String address = device.getAddress();
604 
605             if (!mMetadataCache.containsKey(address)) {
606                 Log.d(TAG, "setConnection: Creating new metadata entry for device: " + device);
607                 createMetadata(address, isA2dpDevice);
608                 return;
609             }
610             // Updates last_active_time to the current counter value and increments the counter
611             Metadata metadata = mMetadataCache.get(address);
612             metadata.last_active_time = MetadataDatabase.sCurrentConnectionNumber++;
613 
614             // Only update is_active_a2dp_device if an a2dp device is connected
615             if (isA2dpDevice) {
616                 metadata.is_active_a2dp_device = true;
617             }
618 
619             Log.d(TAG, "Updating last connected time for device: " + device.getAnonymizedAddress()
620                     + " to " + metadata.last_active_time);
621             updateDatabase(metadata);
622         }
623     }
624 
625     /**
626      * Sets is_active_device to false if currently true for device
627      *
628      * @param device is the remote bluetooth device with which we have disconnected a2dp
629      */
setDisconnection(BluetoothDevice device)630     public void setDisconnection(BluetoothDevice device) {
631         synchronized (mMetadataCache) {
632             if (device == null) {
633                 Log.e(TAG, "setDisconnection: device is null");
634                 return;
635             }
636 
637             String address = device.getAddress();
638 
639             if (!mMetadataCache.containsKey(address)) {
640                 return;
641             }
642             // Updates last connected time to either current time if connected or -1 if disconnected
643             Metadata metadata = mMetadataCache.get(address);
644             if (metadata.is_active_a2dp_device) {
645                 metadata.is_active_a2dp_device = false;
646                 Log.d(TAG, "setDisconnection: Updating is_active_device to false for device: "
647                         + device);
648                 updateDatabase(metadata);
649             }
650         }
651     }
652 
653     /**
654      * Remove a2dpActiveDevice from the current active device in the connection order table
655      */
resetActiveA2dpDevice()656     private void resetActiveA2dpDevice() {
657         synchronized (mMetadataCache) {
658             Log.d(TAG, "resetActiveA2dpDevice()");
659             for (Map.Entry<String, Metadata> entry : mMetadataCache.entrySet()) {
660                 Metadata metadata = entry.getValue();
661                 if (metadata.is_active_a2dp_device) {
662                     Log.d(TAG, "resetActiveA2dpDevice");
663                     metadata.is_active_a2dp_device = false;
664                     updateDatabase(metadata);
665                 }
666             }
667         }
668     }
669 
670     /**
671      * Gets the most recently connected bluetooth devices in order with most recently connected
672      * first and least recently connected last
673      *
674      * @return a {@link List} of {@link BluetoothDevice} representing connected bluetooth devices
675      * in order of most recently connected
676      */
getMostRecentlyConnectedDevices()677     public List<BluetoothDevice> getMostRecentlyConnectedDevices() {
678         List<BluetoothDevice> mostRecentlyConnectedDevices = new ArrayList<>();
679         synchronized (mMetadataCache) {
680             List<Metadata> sortedMetadata = new ArrayList<>(mMetadataCache.values());
681             sortedMetadata.sort((o1, o2) -> Long.compare(o2.last_active_time, o1.last_active_time));
682             for (Metadata metadata : sortedMetadata) {
683                 try {
684                     mostRecentlyConnectedDevices.add(BluetoothAdapter.getDefaultAdapter()
685                             .getRemoteDevice(metadata.getAddress()));
686                 } catch (IllegalArgumentException ex) {
687                     Log.d(TAG, "getBondedDevicesOrdered: Invalid address for "
688                             + "device " + metadata.getAddress());
689                 }
690             }
691         }
692         return mostRecentlyConnectedDevices;
693     }
694 
695     /**
696      * Gets the most recently connected bluetooth device in a given list.
697      *
698      * @param devicesList the list of {@link BluetoothDevice} to search in
699      * @return the most recently connected {@link BluetoothDevice} in the given
700      *         {@code devicesList}, or null if an error occurred
701      *
702      * @hide
703      */
getMostRecentlyConnectedDevicesInList( List<BluetoothDevice> devicesList)704     public BluetoothDevice getMostRecentlyConnectedDevicesInList(
705             List<BluetoothDevice> devicesList) {
706         if (devicesList == null) {
707             return null;
708         }
709 
710         BluetoothDevice mostRecentDevice = null;
711         long mostRecentLastActiveTime = -1;
712         synchronized (mMetadataCache) {
713             for (BluetoothDevice device : devicesList) {
714                 String address = device.getAddress();
715                 Metadata metadata = mMetadataCache.get(address);
716                 if (metadata != null && (mostRecentLastActiveTime == -1
717                             || mostRecentLastActiveTime < metadata.last_active_time)) {
718                     mostRecentLastActiveTime = metadata.last_active_time;
719                     mostRecentDevice = device;
720                 }
721 
722             }
723         }
724         return mostRecentDevice;
725     }
726 
727     /**
728      * Gets the last active a2dp device
729      *
730      * @return the most recently active a2dp device or null if the last a2dp device was null
731      */
getMostRecentlyConnectedA2dpDevice()732     public BluetoothDevice getMostRecentlyConnectedA2dpDevice() {
733         synchronized (mMetadataCache) {
734             for (Map.Entry<String, Metadata> entry : mMetadataCache.entrySet()) {
735                 Metadata metadata = entry.getValue();
736                 if (metadata.is_active_a2dp_device) {
737                     try {
738                         return BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
739                                 metadata.getAddress());
740                     } catch (IllegalArgumentException ex) {
741                         Log.d(TAG, "getMostRecentlyConnectedA2dpDevice: Invalid address for "
742                                 + "device " + metadata.getAddress());
743                     }
744                 }
745             }
746         }
747         return null;
748     }
749 
750     /**
751      *
752      * @param metadataList is the list of metadata
753      */
compactLastConnectionTime(List<Metadata> metadataList)754     private void compactLastConnectionTime(List<Metadata> metadataList) {
755         Log.d(TAG, "compactLastConnectionTime: Compacting metadata after load");
756         MetadataDatabase.sCurrentConnectionNumber = 0;
757         // Have to go in reverse order as list is ordered by descending last_active_time
758         for (int index = metadataList.size() - 1; index >= 0; index--) {
759             Metadata metadata = metadataList.get(index);
760             if (metadata.last_active_time != MetadataDatabase.sCurrentConnectionNumber) {
761                 Log.d(TAG, "compactLastConnectionTime: Setting last_active_item for device: "
762                         + metadata.getAnonymizedAddress() + " from " + metadata.last_active_time
763                         + " to " + MetadataDatabase.sCurrentConnectionNumber);
764                 metadata.last_active_time = MetadataDatabase.sCurrentConnectionNumber;
765                 updateDatabase(metadata);
766                 MetadataDatabase.sCurrentConnectionNumber++;
767             }
768         }
769     }
770 
771     /**
772      * Get the {@link Looper} for the handler thread. This is used in testing and helper
773      * objects
774      *
775      * @return {@link Looper} for the handler thread
776      */
777     @VisibleForTesting
getHandlerLooper()778     public Looper getHandlerLooper() {
779         if (mHandlerThread == null) {
780             return null;
781         }
782         return mHandlerThread.getLooper();
783     }
784 
785     /**
786      * Start and initialize the DatabaseManager
787      *
788      * @param database the Bluetooth storage {@link MetadataDatabase}
789      */
start(MetadataDatabase database)790     public void start(MetadataDatabase database) {
791         Log.d(TAG, "start()");
792         if (mAdapterService == null) {
793             Log.e(TAG, "stat failed, mAdapterService is null.");
794             return;
795         }
796 
797         if (database == null) {
798             Log.e(TAG, "stat failed, database is null.");
799             return;
800         }
801 
802         mDatabase = database;
803 
804         mHandlerThread = new HandlerThread("BluetoothDatabaseManager");
805         mHandlerThread.start();
806         mHandler = new DatabaseHandler(mHandlerThread.getLooper());
807 
808         IntentFilter filter = new IntentFilter();
809         filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
810         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
811         mAdapterService.registerReceiver(mReceiver, filter);
812 
813         loadDatabase();
814     }
815 
getDatabaseAbsolutePath()816     String getDatabaseAbsolutePath() {
817         //TODO backup database when Bluetooth turn off and FOTA?
818         return mAdapterService.getDatabasePath(MetadataDatabase.DATABASE_NAME)
819                 .getAbsolutePath();
820     }
821 
822     /**
823      * Clear all persistence data in database
824      */
factoryReset()825     public void factoryReset() {
826         Log.w(TAG, "factoryReset");
827         Message message = mHandler.obtainMessage(MSG_CLEAR_DATABASE);
828         mHandler.sendMessage(message);
829     }
830 
831     /**
832      * Close and de-init the DatabaseManager
833      */
cleanup()834     public void cleanup() {
835         removeUnusedMetadata();
836         mAdapterService.unregisterReceiver(mReceiver);
837         if (mHandlerThread != null) {
838             mHandlerThread.quit();
839             mHandlerThread = null;
840         }
841         mMetadataCache.clear();
842     }
843 
createMetadata(String address, boolean isActiveA2dpDevice)844     void createMetadata(String address, boolean isActiveA2dpDevice) {
845         Metadata data = new Metadata(address);
846         data.is_active_a2dp_device = isActiveA2dpDevice;
847         mMetadataCache.put(address, data);
848         updateDatabase(data);
849         logMetadataChange(address, "Metadata created");
850     }
851 
852     @VisibleForTesting
removeUnusedMetadata()853     void removeUnusedMetadata() {
854         BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
855         synchronized (mMetadataCache) {
856             mMetadataCache.forEach((address, metadata) -> {
857                 if (!address.equals(LOCAL_STORAGE)
858                         && !Arrays.asList(bondedDevices).stream().anyMatch(device ->
859                         address.equals(device.getAddress()))) {
860                     List<Integer> list = metadata.getChangedCustomizedMeta();
861                     for (int key : list) {
862                         mAdapterService.metadataChanged(address, key, null);
863                     }
864                     Log.i(TAG, "remove unpaired device from database " + address);
865                     deleteDatabase(mMetadataCache.get(address));
866                 }
867             });
868         }
869     }
870 
cacheMetadata(List<Metadata> list)871     void cacheMetadata(List<Metadata> list) {
872         synchronized (mMetadataCache) {
873             Log.i(TAG, "cacheMetadata");
874             // Unlock the main thread.
875             mSemaphore.release();
876 
877             if (!isMigrated(list)) {
878                 // Wait for data migrate from Settings Global
879                 mMigratedFromSettingsGlobal = false;
880                 return;
881             }
882             mMigratedFromSettingsGlobal = true;
883             for (Metadata data : list) {
884                 String address = data.getAddress();
885                 Log.v(TAG, "cacheMetadata: found device " + data.getAnonymizedAddress());
886                 mMetadataCache.put(address, data);
887             }
888             Log.i(TAG, "cacheMetadata: Database is ready");
889         }
890     }
891 
isMigrated(List<Metadata> list)892     boolean isMigrated(List<Metadata> list) {
893         for (Metadata data : list) {
894             String address = data.getAddress();
895             if (address.equals(LOCAL_STORAGE) && data.migrated) {
896                 return true;
897             }
898         }
899         return false;
900     }
901 
migrateSettingsGlobal()902     void migrateSettingsGlobal() {
903         Log.i(TAG, "migrateSettingGlobal");
904 
905         BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
906         ContentResolver contentResolver = mAdapterService.getContentResolver();
907 
908         for (BluetoothDevice device : bondedDevices) {
909             int a2dpConnectionPolicy = Settings.Global.getInt(contentResolver,
910                     getLegacyA2dpSinkPriorityKey(device.getAddress()),
911                     BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
912             int a2dpSinkConnectionPolicy = Settings.Global.getInt(contentResolver,
913                     getLegacyA2dpSrcPriorityKey(device.getAddress()),
914                     BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
915             int hearingaidConnectionPolicy = Settings.Global.getInt(contentResolver,
916                     getLegacyHearingAidPriorityKey(device.getAddress()),
917                     BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
918             int headsetConnectionPolicy = Settings.Global.getInt(contentResolver,
919                     getLegacyHeadsetPriorityKey(device.getAddress()),
920                     BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
921             int headsetClientConnectionPolicy = Settings.Global.getInt(contentResolver,
922                     getLegacyHeadsetPriorityKey(device.getAddress()),
923                     BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
924             int hidHostConnectionPolicy = Settings.Global.getInt(contentResolver,
925                     getLegacyHidHostPriorityKey(device.getAddress()),
926                     BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
927             int mapConnectionPolicy = Settings.Global.getInt(contentResolver,
928                     getLegacyMapPriorityKey(device.getAddress()),
929                     BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
930             int mapClientConnectionPolicy = Settings.Global.getInt(contentResolver,
931                     getLegacyMapClientPriorityKey(device.getAddress()),
932                     BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
933             int panConnectionPolicy = Settings.Global.getInt(contentResolver,
934                     getLegacyPanPriorityKey(device.getAddress()),
935                     BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
936             int pbapConnectionPolicy = Settings.Global.getInt(contentResolver,
937                     getLegacyPbapClientPriorityKey(device.getAddress()),
938                     BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
939             int pbapClientConnectionPolicy = Settings.Global.getInt(contentResolver,
940                     getLegacyPbapClientPriorityKey(device.getAddress()),
941                     BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
942             int sapConnectionPolicy = Settings.Global.getInt(contentResolver,
943                     getLegacySapPriorityKey(device.getAddress()),
944                     BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
945             int a2dpSupportsOptionalCodec = Settings.Global.getInt(contentResolver,
946                     getLegacyA2dpSupportsOptionalCodecsKey(device.getAddress()),
947                     BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
948             int a2dpOptionalCodecEnabled = Settings.Global.getInt(contentResolver,
949                     getLegacyA2dpOptionalCodecsEnabledKey(device.getAddress()),
950                     BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
951 
952             String address = device.getAddress();
953             Metadata data = new Metadata(address);
954             data.setProfileConnectionPolicy(BluetoothProfile.A2DP, a2dpConnectionPolicy);
955             data.setProfileConnectionPolicy(BluetoothProfile.A2DP_SINK, a2dpSinkConnectionPolicy);
956             data.setProfileConnectionPolicy(BluetoothProfile.HEADSET, headsetConnectionPolicy);
957             data.setProfileConnectionPolicy(BluetoothProfile.HEADSET_CLIENT,
958                     headsetClientConnectionPolicy);
959             data.setProfileConnectionPolicy(BluetoothProfile.HID_HOST, hidHostConnectionPolicy);
960             data.setProfileConnectionPolicy(BluetoothProfile.PAN, panConnectionPolicy);
961             data.setProfileConnectionPolicy(BluetoothProfile.PBAP, pbapConnectionPolicy);
962             data.setProfileConnectionPolicy(BluetoothProfile.PBAP_CLIENT,
963                     pbapClientConnectionPolicy);
964             data.setProfileConnectionPolicy(BluetoothProfile.MAP, mapConnectionPolicy);
965             data.setProfileConnectionPolicy(BluetoothProfile.MAP_CLIENT, mapClientConnectionPolicy);
966             data.setProfileConnectionPolicy(BluetoothProfile.SAP, sapConnectionPolicy);
967             data.setProfileConnectionPolicy(BluetoothProfile.HEARING_AID,
968                     hearingaidConnectionPolicy);
969             data.setProfileConnectionPolicy(BluetoothProfile.LE_AUDIO,
970                     BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
971             data.a2dpSupportsOptionalCodecs = a2dpSupportsOptionalCodec;
972             data.a2dpOptionalCodecsEnabled = a2dpOptionalCodecEnabled;
973             mMetadataCache.put(address, data);
974             updateDatabase(data);
975         }
976 
977         // Mark database migrated from Settings Global
978         Metadata localData = new Metadata(LOCAL_STORAGE);
979         localData.migrated = true;
980         mMetadataCache.put(LOCAL_STORAGE, localData);
981         updateDatabase(localData);
982 
983         // Reload database after migration is completed
984         loadDatabase();
985 
986     }
987 
988     /**
989      * Get the key that retrieves a bluetooth headset's priority.
990      */
getLegacyHeadsetPriorityKey(String address)991     private static String getLegacyHeadsetPriorityKey(String address) {
992         return LEGACY_HEADSET_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
993     }
994 
995     /**
996      * Get the key that retrieves a bluetooth a2dp sink's priority.
997      */
getLegacyA2dpSinkPriorityKey(String address)998     private static String getLegacyA2dpSinkPriorityKey(String address) {
999         return LEGACY_A2DP_SINK_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
1000     }
1001 
1002     /**
1003      * Get the key that retrieves a bluetooth a2dp src's priority.
1004      */
getLegacyA2dpSrcPriorityKey(String address)1005     private static String getLegacyA2dpSrcPriorityKey(String address) {
1006         return LEGACY_A2DP_SRC_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
1007     }
1008 
1009     /**
1010      * Get the key that retrieves a bluetooth a2dp device's ability to support optional codecs.
1011      */
getLegacyA2dpSupportsOptionalCodecsKey(String address)1012     private static String getLegacyA2dpSupportsOptionalCodecsKey(String address) {
1013         return LEGACY_A2DP_SUPPORTS_OPTIONAL_CODECS_PREFIX
1014                 + address.toUpperCase(Locale.ROOT);
1015     }
1016 
1017     /**
1018      * Get the key that retrieves whether a bluetooth a2dp device should have optional codecs
1019      * enabled.
1020      */
getLegacyA2dpOptionalCodecsEnabledKey(String address)1021     private static String getLegacyA2dpOptionalCodecsEnabledKey(String address) {
1022         return LEGACY_A2DP_OPTIONAL_CODECS_ENABLED_PREFIX
1023                 + address.toUpperCase(Locale.ROOT);
1024     }
1025 
1026     /**
1027      * Get the key that retrieves a bluetooth Input Device's priority.
1028      */
getLegacyHidHostPriorityKey(String address)1029     private static String getLegacyHidHostPriorityKey(String address) {
1030         return LEGACY_INPUT_DEVICE_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
1031     }
1032 
1033     /**
1034      * Get the key that retrieves a bluetooth pan client priority.
1035      */
getLegacyPanPriorityKey(String address)1036     private static String getLegacyPanPriorityKey(String address) {
1037         return LEGACY_PAN_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
1038     }
1039 
1040     /**
1041      * Get the key that retrieves a bluetooth hearing aid priority.
1042      */
getLegacyHearingAidPriorityKey(String address)1043     private static String getLegacyHearingAidPriorityKey(String address) {
1044         return LEGACY_HEARING_AID_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
1045     }
1046 
1047     /**
1048      * Get the key that retrieves a bluetooth map priority.
1049      */
getLegacyMapPriorityKey(String address)1050     private static String getLegacyMapPriorityKey(String address) {
1051         return LEGACY_MAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
1052     }
1053 
1054     /**
1055      * Get the key that retrieves a bluetooth map client priority.
1056      */
getLegacyMapClientPriorityKey(String address)1057     private static String getLegacyMapClientPriorityKey(String address) {
1058         return LEGACY_MAP_CLIENT_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
1059     }
1060 
1061     /**
1062      * Get the key that retrieves a bluetooth pbap client priority.
1063      */
getLegacyPbapClientPriorityKey(String address)1064     private static String getLegacyPbapClientPriorityKey(String address) {
1065         return LEGACY_PBAP_CLIENT_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
1066     }
1067 
1068     /**
1069      * Get the key that retrieves a bluetooth sap priority.
1070      */
getLegacySapPriorityKey(String address)1071     private static String getLegacySapPriorityKey(String address) {
1072         return LEGACY_SAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
1073     }
1074 
loadDatabase()1075     private void loadDatabase() {
1076         Log.d(TAG, "Load Database");
1077         Message message = mHandler.obtainMessage(MSG_LOAD_DATABASE);
1078         mHandler.sendMessage(message);
1079         try {
1080             // Lock the thread until handler thread finish loading database.
1081             mSemaphore.tryAcquire(LOAD_DATABASE_TIMEOUT, TimeUnit.MILLISECONDS);
1082         } catch (InterruptedException e) {
1083             Log.e(TAG, "loadDatabase: semaphore acquire failed");
1084         }
1085     }
1086 
updateDatabase(Metadata data)1087     private void updateDatabase(Metadata data) {
1088         if (data.getAddress() == null) {
1089             Log.e(TAG, "updateDatabase: address is null");
1090             return;
1091         }
1092         Log.d(TAG, "updateDatabase " + data.getAnonymizedAddress());
1093         Message message = mHandler.obtainMessage(MSG_UPDATE_DATABASE);
1094         message.obj = data;
1095         mHandler.sendMessage(message);
1096     }
1097 
1098     @VisibleForTesting
deleteDatabase(Metadata data)1099     void deleteDatabase(Metadata data) {
1100         String address = data.getAddress();
1101         if (address == null) {
1102             Log.e(TAG, "deleteDatabase: address is null");
1103             return;
1104         }
1105         logMetadataChange(address, "Metadata deleted");
1106         Message message = mHandler.obtainMessage(MSG_DELETE_DATABASE);
1107         message.obj = data.getAddress();
1108         mHandler.sendMessage(message);
1109     }
1110 
logManufacturerInfo(BluetoothDevice device, int key, byte[] bytesValue)1111     private void logManufacturerInfo(BluetoothDevice device, int key, byte[] bytesValue) {
1112         String callingApp = mAdapterService.getPackageManager().getNameForUid(
1113                 Binder.getCallingUid());
1114         String manufacturerName = "";
1115         String modelName = "";
1116         String hardwareVersion = "";
1117         String softwareVersion = "";
1118         String value = Utils.byteArrayToUtf8String(bytesValue);
1119         switch (key) {
1120             case BluetoothDevice.METADATA_MANUFACTURER_NAME:
1121                 manufacturerName = value;
1122                 break;
1123             case BluetoothDevice.METADATA_MODEL_NAME:
1124                 modelName = value;
1125                 break;
1126             case BluetoothDevice.METADATA_HARDWARE_VERSION:
1127                 hardwareVersion = value;
1128                 break;
1129             case BluetoothDevice.METADATA_SOFTWARE_VERSION:
1130                 softwareVersion = value;
1131                 break;
1132             default:
1133                 // Do not log anything if metadata doesn't fall into above categories
1134                 return;
1135         }
1136         String[] macAddress = device.getAddress().split(":");
1137         BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_DEVICE_INFO_REPORTED,
1138                 mAdapterService.obfuscateAddress(device),
1139                 BluetoothProtoEnums.DEVICE_INFO_EXTERNAL, callingApp, manufacturerName, modelName,
1140                 hardwareVersion, softwareVersion, mAdapterService.getMetricId(device),
1141                 device.getAddressType(),
1142                 Integer.parseInt(macAddress[0], 16),
1143                 Integer.parseInt(macAddress[1], 16),
1144                 Integer.parseInt(macAddress[2], 16));
1145     }
1146 
logMetadataChange(String address, String log)1147     private void logMetadataChange(String address, String log) {
1148         String time = Utils.getLocalTimeString();
1149         String uidPid = Utils.getUidPidString();
1150         mMetadataChangedLog.add(time + " (" + uidPid + ") " + address + " " + log);
1151     }
1152 
1153     /**
1154      * Dump database info to a PrintWriter
1155      *
1156      * @param writer the PrintWriter to write log
1157      */
dump(PrintWriter writer)1158     public void dump(PrintWriter writer) {
1159         writer.println("\nBluetoothDatabase:");
1160         writer.println("  Metadata Changes:");
1161         for (String log : mMetadataChangedLog) {
1162             writer.println("    " + log);
1163         }
1164         writer.println("\nMetadata:");
1165         for (HashMap.Entry<String, Metadata> entry : mMetadataCache.entrySet()) {
1166             if (entry.getKey().equals(LOCAL_STORAGE)) {
1167                 // No need to dump local storage
1168                 continue;
1169             }
1170             writer.println("    " + entry.getValue());
1171         }
1172     }
1173 }
1174