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