1 /*
2  * Copyright (C) 2021 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.server.uwb;
18 
19 import static android.uwb.UwbManager.AdapterStateCallback.STATE_ENABLED_ACTIVE;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.os.Handler;
25 import android.os.PersistableBundle;
26 import android.provider.Settings;
27 import android.util.AtomicFile;
28 import android.util.Log;
29 
30 import com.android.internal.annotations.GuardedBy;
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.server.uwb.util.FileUtils;
33 
34 
35 import java.io.ByteArrayInputStream;
36 import java.io.ByteArrayOutputStream;
37 import java.io.FileDescriptor;
38 import java.io.FileNotFoundException;
39 import java.io.IOException;
40 import java.io.PrintWriter;
41 import java.util.ArrayList;
42 import java.util.HashMap;
43 import java.util.Map;
44 
45 /**
46  * Store data for storing UWB settings. These are key (string) / value pairs that are stored in
47  * UwbSettingsStore.xml file. The values allowed are those that can be serialized via
48  * {@link android.os.PersistableBundle}.
49  */
50 public class UwbSettingsStore {
51     private static final String TAG = "UwbSettingsStore";
52     /**
53      * File name used for storing settings.
54      */
55     public static final String FILE_NAME = "UwbSettingsStore.xml";
56     /**
57      * Current config store data version. This will be incremented for any additions.
58      */
59     private static final int CURRENT_SETTINGS_STORE_DATA_VERSION = 1;
60     /** This list of older versions will be used to restore data from older store versions. */
61     /**
62      * First version of the config store data format.
63      */
64     private static final int INITIAL_SETTINGS_STORE_VERSION = 1;
65 
66     /**
67      * Store the version of the data. This can be used to handle migration of data if some
68      * non-backward compatible change introduced.
69      */
70     private static final String VERSION_KEY = "version";
71 
72     /**
73      * Constant copied over from {@link android.provider.Settings} since existing key is @hide.
74      */
75     @VisibleForTesting
76     public static final String SETTINGS_TOGGLE_STATE_KEY_FOR_MIGRATION = "uwb_enabled";
77 
78     // List of all allowed keys.
79     private static final ArrayList<Key> sKeys = new ArrayList<>();
80 
81     /******** Uwb shared pref keys ***************/
82     /**
83      * Store the UWB settings toggle state.
84      */
85     public static final Key<Boolean> SETTINGS_TOGGLE_STATE =
86             new Key<>("settings_toggle", true);
87     public static final Key<String> SETTINGS_LOG_MODE =
88             new Key<>("settings_log_mode", UciLogModeStore.Mode.FILTERED.getMode());
89 
90     public static final Key<Boolean> SETTINGS_FIRST_TOGGLE_DONE =
91             new Key<>("settings_first_toggle_done", false);
92 
93     public static final Key<String> SETTINGS_CACHED_COUNTRY_CODE =
94             new Key<>("settings_cached_country_code", "");
95     /******** Uwb shared pref keys ***************/
96 
97     private final Context mContext;
98     private final Handler mHandler;
99     private final AtomicFile mAtomicFile;
100     private final UwbInjector mUwbInjector;
101 
102     private final Object mLock = new Object();
103     @GuardedBy("mLock")
104     private final PersistableBundle mSettings = new PersistableBundle();
105     @GuardedBy("mLock")
106     private final Map<String, Map<OnSettingsChangedListener, Handler>> mListeners =
107             new HashMap<>();
108 
109     /**
110      * Interface for a settings change listener.
111      *
112      * @param <T> Type of the value.
113      */
114     public interface OnSettingsChangedListener<T> {
115         /**
116          * Invoked when a particular key settings changes.
117          *
118          * @param key      Key that was changed.
119          * @param newValue New value that was assigned to the key.
120          */
onSettingsChanged(@onNull Key<T> key, @Nullable T newValue)121         void onSettingsChanged(@NonNull Key<T> key, @Nullable T newValue);
122     }
123 
UwbSettingsStore(@onNull Context context, @NonNull Handler handler, @NonNull AtomicFile atomicFile, UwbInjector uwbInjector)124     public UwbSettingsStore(@NonNull Context context, @NonNull Handler handler, @NonNull
125             AtomicFile atomicFile, UwbInjector uwbInjector) {
126         mContext = context;
127         mHandler = handler;
128         mAtomicFile = atomicFile;
129         mUwbInjector = uwbInjector;
130     }
131 
132     /**
133      * Initialize the settings store by triggering the store file read.
134      */
initialize()135     public void initialize() {
136         Log.i(TAG, "Reading from store file: " + mAtomicFile.getBaseFile());
137         readFromStoreFile();
138         // Migrate toggle settings from Android 12 to Android 13.
139         boolean isStoreEmpty;
140         synchronized (mLock) {
141             isStoreEmpty = mSettings.isEmpty();
142         }
143         if (isStoreEmpty) {
144             try {
145                 boolean toggleEnabled =
146                         mUwbInjector.getGlobalSettingsInt(SETTINGS_TOGGLE_STATE_KEY_FOR_MIGRATION)
147                                 == STATE_ENABLED_ACTIVE;
148                 Log.i(TAG, "Migrate settings toggle from older release: " + toggleEnabled);
149                 put(SETTINGS_TOGGLE_STATE, toggleEnabled);
150             } catch (Settings.SettingNotFoundException e) {
151                 /* ignore */
152             }
153         }
154         invokeAllListeners();
155     }
156 
invokeAllListeners()157     private void invokeAllListeners() {
158         synchronized (mLock) {
159             for (Key key : sKeys) {
160                 invokeListeners(key);
161             }
162         }
163     }
164 
invokeListeners(@onNull Key<T> key)165     private <T> void invokeListeners(@NonNull Key<T> key) {
166         synchronized (mLock) {
167             if (!mSettings.containsKey(key.key)) return;
168             Object newValue = mSettings.get(key.key);
169             Map<OnSettingsChangedListener, Handler> listeners = mListeners.get(key.key);
170             if (listeners == null || listeners.isEmpty()) return;
171             for (Map.Entry<OnSettingsChangedListener, Handler> listener
172                     : listeners.entrySet()) {
173                 // Trigger the callback in the appropriate handler.
174                 listener.getValue().post(() ->
175                         listener.getKey().onSettingsChanged(key, newValue));
176             }
177         }
178     }
179 
180     /**
181      * Trigger config store writes and invoke listeners in the main service looper's handler.
182      */
triggerSaveToStoreAndInvokeListeners(@onNull Key<T> key)183     private <T> void triggerSaveToStoreAndInvokeListeners(@NonNull Key<T> key) {
184         mHandler.post(() -> {
185             writeToStoreFile();
186             invokeListeners(key);
187         });
188     }
189 
putObject(@onNull String key, @Nullable Object value)190     private void putObject(@NonNull String key, @Nullable Object value) {
191         synchronized (mLock) {
192             if (value == null) {
193                 mSettings.putString(key, null);
194             } else if (value instanceof Boolean) {
195                 mSettings.putBoolean(key, (Boolean) value);
196             } else if (value instanceof Integer) {
197                 mSettings.putInt(key, (Integer) value);
198             } else if (value instanceof Long) {
199                 mSettings.putLong(key, (Long) value);
200             } else if (value instanceof Double) {
201                 mSettings.putDouble(key, (Double) value);
202             } else if (value instanceof String) {
203                 mSettings.putString(key, (String) value);
204             } else {
205                 throw new IllegalArgumentException("Unsupported type " + value.getClass());
206             }
207         }
208     }
209 
getObject(@onNull String key, T defaultValue)210     private <T> T getObject(@NonNull String key, T defaultValue) {
211         Object value;
212         synchronized (mLock) {
213             if (defaultValue instanceof Boolean) {
214                 value = mSettings.getBoolean(key, (Boolean) defaultValue);
215             } else if (defaultValue instanceof Integer) {
216                 value = mSettings.getInt(key, (Integer) defaultValue);
217             } else if (defaultValue instanceof Long) {
218                 value = mSettings.getLong(key, (Long) defaultValue);
219             } else if (defaultValue instanceof Double) {
220                 value = mSettings.getDouble(key, (Double) defaultValue);
221             } else if (defaultValue instanceof String) {
222                 value = mSettings.getString(key, (String) defaultValue);
223             } else {
224                 throw new IllegalArgumentException("Unsupported type " + defaultValue.getClass());
225             }
226         }
227         return (T) value;
228     }
229 
230     /**
231      * Store a value to the stored settings.
232      *
233      * @param key   One of the settings keys.
234      * @param value Value to be stored.
235      */
put(@onNull Key<T> key, @Nullable T value)236     public <T> void put(@NonNull Key<T> key, @Nullable T value) {
237         putObject(key.key, value);
238         triggerSaveToStoreAndInvokeListeners(key);
239     }
240 
241     /**
242      * Retrieve a value from the stored settings.
243      *
244      * @param key One of the settings keys.
245      * @return value stored in settings, defValue if the key does not exist.
246      */
get(@onNull Key<T> key)247     public @Nullable <T> T get(@NonNull Key<T> key) {
248         return getObject(key.key, key.defaultValue);
249     }
250 
251     /**
252      * Register for settings change listener.
253      *
254      * @param key      One of the settings keys.
255      * @param listener Listener to be registered.
256      * @param handler  Handler to post the listener
257      */
registerChangeListener(@onNull Key<T> key, @NonNull OnSettingsChangedListener<T> listener, @NonNull Handler handler)258     public <T> void registerChangeListener(@NonNull Key<T> key,
259             @NonNull OnSettingsChangedListener<T> listener, @NonNull Handler handler) {
260         synchronized (mLock) {
261             mListeners.computeIfAbsent(
262                     key.key, ignore -> new HashMap<>()).put(listener, handler);
263         }
264     }
265 
266     /**
267      * Unregister for settings change listener.
268      *
269      * @param key      One of the settings keys.
270      * @param listener Listener to be unregistered.
271      */
unregisterChangeListener(@onNull Key<T> key, @NonNull OnSettingsChangedListener<T> listener)272     public <T> void unregisterChangeListener(@NonNull Key<T> key,
273             @NonNull OnSettingsChangedListener<T> listener) {
274         synchronized (mLock) {
275             Map<OnSettingsChangedListener, Handler> listeners = mListeners.get(key.key);
276             if (listeners == null || listeners.isEmpty()) {
277                 Log.e(TAG, "No listeners for " + key);
278                 return;
279             }
280             if (listeners.remove(listener) == null) {
281                 Log.e(TAG, "Unknown listener for " + key);
282             }
283         }
284     }
285 
286     /**
287      * Dump output for debugging.
288      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)289     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
290         pw.println("---- Dump of UwbSettingsStore ----");
291         synchronized (mLock) {
292             pw.println("Settings: " + mSettings);
293         }
294         pw.println("---- Dump of UwbSettingsStore ----");
295     }
296 
297     /**
298      * Base class to store string key and its default value.
299      *
300      * @param <T> Type of the value.
301      */
302     public static class Key<T> {
303         public final String key;
304         public final T defaultValue;
305 
Key(@onNull String key, T defaultValue)306         private Key(@NonNull String key, T defaultValue) {
307             this.key = key;
308             this.defaultValue = defaultValue;
309             sKeys.add(this);
310         }
311 
312         @Override
toString()313         public String toString() {
314             return "[Key " + key + ", DefaultValue: " + defaultValue + "]";
315         }
316     }
317 
writeToStoreFile()318     private void writeToStoreFile() {
319         try {
320             final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
321             final PersistableBundle bundleToWrite;
322             synchronized (mLock) {
323                 bundleToWrite = new PersistableBundle(mSettings);
324             }
325             bundleToWrite.putInt(VERSION_KEY, CURRENT_SETTINGS_STORE_DATA_VERSION);
326             bundleToWrite.writeToStream(outputStream);
327             FileUtils.writeToAtomicFile(mAtomicFile, outputStream.toByteArray());
328         } catch (IOException e) {
329             Log.e(TAG, "Write to store file failed", e);
330         }
331     }
332 
readFromStoreFile()333     private void readFromStoreFile() {
334         try {
335             final byte[] readData = FileUtils.readFromAtomicFile(mAtomicFile);
336             final ByteArrayInputStream inputStream = new ByteArrayInputStream(readData);
337             final PersistableBundle bundleRead = PersistableBundle.readFromStream(inputStream);
338             // Version unused for now. May be needed in the future for handling migrations.
339             bundleRead.remove(VERSION_KEY);
340             synchronized (mLock) {
341                 mSettings.putAll(bundleRead);
342             }
343         } catch (FileNotFoundException e) {
344             Log.w(TAG, "No store file to read");
345         } catch (IOException e) {
346             Log.e(TAG, "Read from store file failed", e);
347         }
348     }
349 }
350