1 /*
2  * Copyright (C) 2024 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.settings.connecteddevice.audiosharing.audiostreams;
18 
19 import android.bluetooth.BluetoothLeBroadcastMetadata;
20 import android.content.Context;
21 import android.content.SharedPreferences;
22 import android.util.Log;
23 
24 import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
25 import com.android.settingslib.bluetooth.BluetoothUtils;
26 import com.android.settingslib.utils.ThreadUtils;
27 
28 import java.util.concurrent.ConcurrentHashMap;
29 
30 import javax.annotation.Nullable;
31 
32 /** Manages the caching and storage of Bluetooth audio stream metadata. */
33 public class AudioStreamsRepository {
34 
35     private static final String TAG = "AudioStreamsRepository";
36     private static final boolean DEBUG = BluetoothUtils.D;
37 
38     private static final String PREF_KEY = "bluetooth_audio_stream_pref";
39     private static final String METADATA_KEY = "bluetooth_audio_stream_metadata";
40 
41     @Nullable private static AudioStreamsRepository sInstance = null;
42 
AudioStreamsRepository()43     private AudioStreamsRepository() {}
44 
45     /**
46      * Gets the single instance of AudioStreamsRepository.
47      *
48      * @return The AudioStreamsRepository instance.
49      */
getInstance()50     public static synchronized AudioStreamsRepository getInstance() {
51         if (sInstance == null) {
52             sInstance = new AudioStreamsRepository();
53         }
54         return sInstance;
55     }
56 
57     private final ConcurrentHashMap<Integer, BluetoothLeBroadcastMetadata>
58             mBroadcastIdToMetadataCacheMap = new ConcurrentHashMap<>();
59 
60     /**
61      * Caches BluetoothLeBroadcastMetadata in a local cache.
62      *
63      * @param metadata The BluetoothLeBroadcastMetadata to be cached.
64      */
cacheMetadata(BluetoothLeBroadcastMetadata metadata)65     void cacheMetadata(BluetoothLeBroadcastMetadata metadata) {
66         if (DEBUG) {
67             Log.d(
68                     TAG,
69                     "cacheMetadata(): broadcastId "
70                             + metadata.getBroadcastId()
71                             + " saved in local cache.");
72         }
73         mBroadcastIdToMetadataCacheMap.put(metadata.getBroadcastId(), metadata);
74     }
75 
76     /**
77      * Gets cached BluetoothLeBroadcastMetadata by broadcastId.
78      *
79      * @param broadcastId The broadcastId to look up in the cache.
80      * @return The cached BluetoothLeBroadcastMetadata or null if not found.
81      */
82     @Nullable
getCachedMetadata(int broadcastId)83     BluetoothLeBroadcastMetadata getCachedMetadata(int broadcastId) {
84         var metadata = mBroadcastIdToMetadataCacheMap.get(broadcastId);
85         if (metadata == null) {
86             Log.w(
87                     TAG,
88                     "getCachedMetadata(): broadcastId not found in"
89                             + " mBroadcastIdToMetadataCacheMap.");
90             return null;
91         }
92         return metadata;
93     }
94 
95     /**
96      * Saves metadata to SharedPreferences asynchronously.
97      *
98      * @param context The context.
99      * @param metadata The BluetoothLeBroadcastMetadata to be saved.
100      */
saveMetadata(Context context, BluetoothLeBroadcastMetadata metadata)101     void saveMetadata(Context context, BluetoothLeBroadcastMetadata metadata) {
102         var unused =
103                 ThreadUtils.postOnBackgroundThread(
104                         () -> {
105                             SharedPreferences sharedPref =
106                                     context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE);
107                             if (sharedPref != null) {
108                                 SharedPreferences.Editor editor = sharedPref.edit();
109                                 editor.putString(
110                                         METADATA_KEY,
111                                         BluetoothLeBroadcastMetadataExt.INSTANCE.toQrCodeString(
112                                                 metadata));
113                                 editor.apply();
114                                 if (DEBUG) {
115                                     Log.d(
116                                             TAG,
117                                             "saveMetadata(): broadcastId "
118                                                     + metadata.getBroadcastId()
119                                                     + " metadata saved in storage.");
120                                 }
121                             }
122                         });
123     }
124 
125     /**
126      * Gets saved metadata from SharedPreferences.
127      *
128      * @param context The context.
129      * @param broadcastId The broadcastId to retrieve metadata for.
130      * @return The saved BluetoothLeBroadcastMetadata or null if not found.
131      */
132     @Nullable
getSavedMetadata(Context context, int broadcastId)133     BluetoothLeBroadcastMetadata getSavedMetadata(Context context, int broadcastId) {
134         SharedPreferences sharedPref = context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE);
135         if (sharedPref != null) {
136             String savedMetadataStr = sharedPref.getString(METADATA_KEY, null);
137             if (savedMetadataStr == null) {
138                 Log.w(TAG, "getSavedMetadata(): savedMetadataStr is null");
139                 return null;
140             }
141             var savedMetadata =
142                     BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(
143                             savedMetadataStr);
144             if (savedMetadata == null || savedMetadata.getBroadcastId() != broadcastId) {
145                 Log.w(TAG, "getSavedMetadata(): savedMetadata doesn't match broadcast Id.");
146                 return null;
147             }
148             if (DEBUG) {
149                 Log.d(
150                         TAG,
151                         "getSavedMetadata(): broadcastId "
152                                 + savedMetadata.getBroadcastId()
153                                 + " metadata found in storage.");
154             }
155             return savedMetadata;
156         }
157         return null;
158     }
159 }
160