1 /* 2 * Copyright (C) 2013 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.camera.settings; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 22 import android.preference.PreferenceManager; 23 24 import com.android.camera.debug.Log; 25 26 import java.util.ArrayList; 27 import java.util.List; 28 29 import javax.annotation.Nullable; 30 import javax.annotation.concurrent.ThreadSafe; 31 32 33 /** 34 * SettingsManager class provides an api for getting and setting SharedPreferences 35 * values. 36 * 37 * Types 38 * 39 * This API simplifies settings type management by storing all settings values 40 * in SharedPreferences as Strings. To do this, the API to converts boolean and 41 * Integer values to Strings when those values are stored, making the conversion 42 * back to a boolean or Integer also consistent and simple. 43 * 44 * This also enables the user to safely get settings values as three different types, 45 * as it's convenient: String, Integer, and boolean values. Integers and boolean 46 * can always be trivially converted to one another, but Strings cannot always be 47 * parsed as Integers. In this case, if the user stores a String value that cannot 48 * be parsed to an Integer yet they try to retrieve it as an Integer, the API throws 49 * a meaningful exception to the user. 50 * 51 * Scope 52 * 53 * This API introduces the concept of "scope" for a setting, which is the generality 54 * of a setting. The most general settings, that can be accessed acrossed the 55 * entire application, have a scope of SCOPE_GLOBAL. They are stored in the default 56 * SharedPreferences file. 57 * 58 * A setting that is local to a third party module or subset of the application has 59 * a custom scope. The specific module can define whatever scope (String) argument 60 * they want, and the settings saved with that scope can only be seen by that third 61 * party module. Scope is a general concept that helps protect settings values 62 * from being clobbered in different contexts. 63 * 64 * Keys and Defaults 65 * 66 * This API allows you to store your SharedPreferences keys and default values 67 * outside the SettingsManager, because these values are either passed into 68 * the API or stored in a cache when the user sets defaults. 69 * 70 * For any setting, it is optional to store a default or set of possible values, 71 * unless you plan on using the getIndexOfCurrentValue and setValueByIndex, 72 * methods, which rely on an index into the set of possible values. 73 * 74 */ 75 @ThreadSafe 76 public class SettingsManager { 77 private static final Log.Tag TAG = new Log.Tag("SettingsManager"); 78 79 private final Object mLock; 80 private final Context mContext; 81 private final String mPackageName; 82 private final SharedPreferences mDefaultPreferences; 83 private SharedPreferences mCustomPreferences; 84 private final DefaultsStore mDefaultsStore = new DefaultsStore(); 85 86 public static final String MODULE_SCOPE_PREFIX = "_preferences_module_"; 87 public static final String CAMERA_SCOPE_PREFIX = "_preferences_camera_"; 88 89 /** 90 * A List of OnSettingChangedListener's, maintained to compare to new 91 * listeners and prevent duplicate registering. 92 */ 93 private final List<OnSettingChangedListener> mListeners = 94 new ArrayList<OnSettingChangedListener>(); 95 96 /** 97 * A List of OnSharedPreferenceChangeListener's, maintained to hold pointers 98 * to actually registered listeners, so they can be unregistered. 99 */ 100 private final List<OnSharedPreferenceChangeListener> mSharedPreferenceListeners = 101 new ArrayList<OnSharedPreferenceChangeListener>(); 102 SettingsManager(Context context)103 public SettingsManager(Context context) { 104 mLock = new Object(); 105 mContext = context; 106 mPackageName = mContext.getPackageName(); 107 108 mDefaultPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); 109 } 110 111 /** 112 * Get the SettingsManager's default preferences. This is useful 113 * to third party modules as they are defining their upgrade paths, 114 * since most third party modules will use either SCOPE_GLOBAL or a 115 * custom scope. 116 */ getDefaultPreferences()117 public SharedPreferences getDefaultPreferences() { 118 synchronized (mLock) { 119 return mDefaultPreferences; 120 } 121 } 122 123 /** 124 * Open a SharedPreferences file by custom scope. 125 * Also registers any known SharedPreferenceListeners on this 126 * SharedPreferences instance. 127 */ openPreferences(String scope)128 protected SharedPreferences openPreferences(String scope) { 129 synchronized (mLock) { 130 SharedPreferences preferences; 131 // For external camera, scope could have "/" separator which is a invalid path 132 // for the shared preference. 133 String validScope = scope.replaceAll("/", "_"); 134 preferences = mContext.getSharedPreferences( 135 mPackageName + validScope, Context.MODE_PRIVATE); 136 137 for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) { 138 preferences.registerOnSharedPreferenceChangeListener(listener); 139 } 140 return preferences; 141 } 142 } 143 144 /** 145 * Close a SharedPreferences file by custom scope. 146 * The file isn't explicitly closed (the SharedPreferences API makes 147 * this unnecessary), so the real work is to unregister any known 148 * SharedPreferenceListeners from this SharedPreferences instance. 149 * 150 * It's important to do this as camera and modules change, because 151 * we don't want old SharedPreferences listeners executing on 152 * cameras/modules they are not compatible with. 153 */ closePreferences(SharedPreferences preferences)154 protected void closePreferences(SharedPreferences preferences) { 155 synchronized (mLock) { 156 for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) { 157 preferences.unregisterOnSharedPreferenceChangeListener(listener); 158 } 159 } 160 } 161 getCameraSettingScope(String cameraIdValue)162 public static String getCameraSettingScope(String cameraIdValue) { 163 return CAMERA_SCOPE_PREFIX + cameraIdValue; 164 } 165 getModuleSettingScope(String moduleScopeNamespace)166 public static String getModuleSettingScope(String moduleScopeNamespace) { 167 return CAMERA_SCOPE_PREFIX + moduleScopeNamespace; 168 } 169 170 /** 171 * Interface with Camera Device Settings and Modules. 172 */ 173 public interface OnSettingChangedListener { 174 /** 175 * Called every time a SharedPreference has been changed. 176 */ onSettingChanged(SettingsManager settingsManager, String key)177 public void onSettingChanged(SettingsManager settingsManager, String key); 178 } 179 getSharedPreferenceListener( final OnSettingChangedListener listener)180 private OnSharedPreferenceChangeListener getSharedPreferenceListener( 181 final OnSettingChangedListener listener) { 182 return new OnSharedPreferenceChangeListener() { 183 @Override 184 public void onSharedPreferenceChanged( 185 SharedPreferences sharedPreferences, String key) { 186 listener.onSettingChanged(SettingsManager.this, key); 187 } 188 }; 189 } 190 191 /** 192 * Add an OnSettingChangedListener to the SettingsManager, which will 193 * execute onSettingsChanged when any SharedPreference has been updated. 194 */ 195 public void addListener(final OnSettingChangedListener listener) { 196 synchronized (mLock) { 197 if (listener == null) { 198 throw new IllegalArgumentException("OnSettingChangedListener cannot be null."); 199 } 200 201 if (mListeners.contains(listener)) { 202 return; 203 } 204 205 mListeners.add(listener); 206 OnSharedPreferenceChangeListener sharedPreferenceListener = 207 getSharedPreferenceListener(listener); 208 mSharedPreferenceListeners.add(sharedPreferenceListener); 209 mDefaultPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceListener); 210 211 if (mCustomPreferences != null) { 212 mCustomPreferences.registerOnSharedPreferenceChangeListener( 213 sharedPreferenceListener); 214 } 215 Log.v(TAG, "listeners: " + mListeners); 216 } 217 } 218 219 /** 220 * Remove a specific SettingsListener. This should be done in onPause if a 221 * listener has been set. 222 */ 223 public void removeListener(OnSettingChangedListener listener) { 224 synchronized (mLock) { 225 if (listener == null) { 226 throw new IllegalArgumentException(); 227 } 228 229 if (!mListeners.contains(listener)) { 230 return; 231 } 232 233 int index = mListeners.indexOf(listener); 234 mListeners.remove(listener); 235 236 OnSharedPreferenceChangeListener sharedPreferenceListener = 237 mSharedPreferenceListeners.get(index); 238 mSharedPreferenceListeners.remove(index); 239 mDefaultPreferences.unregisterOnSharedPreferenceChangeListener( 240 sharedPreferenceListener); 241 242 if (mCustomPreferences != null) { 243 mCustomPreferences.unregisterOnSharedPreferenceChangeListener( 244 sharedPreferenceListener); 245 } 246 } 247 } 248 249 /** 250 * Remove all OnSharedPreferenceChangedListener's. This should be done in 251 * onDestroy. 252 */ 253 public void removeAllListeners() { 254 synchronized (mLock) { 255 for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) { 256 mDefaultPreferences.unregisterOnSharedPreferenceChangeListener(listener); 257 258 if (mCustomPreferences != null) { 259 mCustomPreferences.unregisterOnSharedPreferenceChangeListener(listener); 260 } 261 } 262 mSharedPreferenceListeners.clear(); 263 mListeners.clear(); 264 } 265 } 266 267 /** This scope stores and retrieves settings from 268 default preferences. */ 269 public static final String SCOPE_GLOBAL = "default_scope"; 270 271 /** 272 * Returns the SharedPreferences file matching the scope 273 * argument. 274 * 275 * Camera and module preferences files are cached, 276 * until the camera id or module id changes, then the listeners 277 * are unregistered and a new file is opened. 278 */ 279 private SharedPreferences getPreferencesFromScope(String scope) { 280 synchronized (mLock) { 281 if (scope.equals(SCOPE_GLOBAL)) { 282 return mDefaultPreferences; 283 } 284 285 if (mCustomPreferences != null) { 286 closePreferences(mCustomPreferences); 287 } 288 mCustomPreferences = openPreferences(scope); 289 return mCustomPreferences; 290 } 291 } 292 293 /** 294 * Set default and valid values for a setting, for a String default and 295 * a set of String possible values that are already defined. 296 * This is not required. 297 */ 298 public void setDefaults(String key, String defaultValue, String[] possibleValues) { 299 synchronized (mLock) { 300 mDefaultsStore.storeDefaults(key, defaultValue, possibleValues); 301 } 302 } 303 304 /** 305 * Set default and valid values for a setting, for an Integer default and 306 * a set of Integer possible values that are already defined. 307 * This is not required. 308 */ 309 public void setDefaults(String key, int defaultValue, int[] possibleValues) { 310 synchronized (mLock) { 311 String defaultValueString = Integer.toString(defaultValue); 312 String[] possibleValuesString = new String[possibleValues.length]; 313 for (int i = 0; i < possibleValues.length; i++) { 314 possibleValuesString[i] = Integer.toString(possibleValues[i]); 315 } 316 mDefaultsStore.storeDefaults(key, defaultValueString, possibleValuesString); 317 } 318 } 319 320 /** 321 * Set default and valid values for a setting, for a boolean default. 322 * The set of boolean possible values is always { false, true }. 323 * This is not required. 324 */ 325 public void setDefaults(String key, boolean defaultValue) { 326 synchronized (mLock) { 327 String defaultValueString = defaultValue ? "1" : "0"; 328 String[] possibleValues = {"0", "1"}; 329 mDefaultsStore.storeDefaults(key, defaultValueString, possibleValues); 330 } 331 } 332 333 /** 334 * Retrieve a default from the DefaultsStore as a String. 335 */ 336 public String getStringDefault(String key) { 337 synchronized (mLock) { 338 return mDefaultsStore.getDefaultValue(key); 339 } 340 } 341 342 /** 343 * Retrieve a default from the DefaultsStore as an Integer. 344 */ 345 public Integer getIntegerDefault(String key) { 346 synchronized (mLock) { 347 String defaultValueString = mDefaultsStore.getDefaultValue(key); 348 return defaultValueString == null ? 0 : Integer.parseInt(defaultValueString); 349 } 350 } 351 352 /** 353 * Retrieve a default from the DefaultsStore as a boolean. 354 */ 355 public boolean getBooleanDefault(String key) { 356 synchronized (mLock) { 357 String defaultValueString = mDefaultsStore.getDefaultValue(key); 358 return defaultValueString == null ? false : 359 (Integer.parseInt(defaultValueString) != 0); 360 } 361 } 362 363 /** 364 * Retrieve a setting's value as a String, manually specifiying 365 * a default value. 366 */ 367 public String getString(String scope, String key, String defaultValue) { 368 synchronized (mLock) { 369 SharedPreferences preferences = getPreferencesFromScope(scope); 370 try { 371 return preferences.getString(key, defaultValue); 372 } catch (ClassCastException e) { 373 Log.w(TAG, "existing preference with invalid type, removing and returning default", e); 374 preferences.edit().remove(key).apply(); 375 return defaultValue; 376 } 377 } 378 } 379 380 /** 381 * Retrieve a setting's value as a String, using the default value 382 * stored in the DefaultsStore. 383 */ 384 @Nullable 385 public String getString(String scope, String key) { 386 synchronized (mLock) { 387 return getString(scope, key, getStringDefault(key)); 388 } 389 } 390 391 /** 392 * Retrieve a setting's value as an Integer, manually specifying 393 * a default value. 394 */ 395 public int getInteger(String scope, String key, Integer defaultValue) { 396 synchronized (mLock) { 397 String defaultValueString = Integer.toString(defaultValue); 398 String value = getString(scope, key, defaultValueString); 399 return convertToInt(value); 400 } 401 } 402 403 /** 404 * Retrieve a setting's value as an Integer, converting the default value 405 * stored in the DefaultsStore. 406 */ 407 public int getInteger(String scope, String key) { 408 synchronized (mLock) { 409 return getInteger(scope, key, getIntegerDefault(key)); 410 } 411 } 412 413 /** 414 * Retrieve a setting's value as a boolean, manually specifiying 415 * a default value. 416 */ 417 public boolean getBoolean(String scope, String key, boolean defaultValue) { 418 synchronized (mLock) { 419 String defaultValueString = defaultValue ? "1" : "0"; 420 String value = getString(scope, key, defaultValueString); 421 return convertToBoolean(value); 422 } 423 } 424 425 /** 426 * Retrieve a setting's value as a boolean, converting the default value 427 * stored in the DefaultsStore. 428 */ 429 public boolean getBoolean(String scope, String key) { 430 synchronized (mLock) { 431 return getBoolean(scope, key, getBooleanDefault(key)); 432 } 433 } 434 435 /** 436 * If possible values are stored for this key, return the 437 * index into that list of the currently set value. 438 * 439 * For example, if a set of possible values is [2,3,5], 440 * and the current value set of this key is 3, this method 441 * returns 1. 442 * 443 * If possible values are not stored for this key, throw 444 * an IllegalArgumentException. 445 */ 446 public int getIndexOfCurrentValue(String scope, String key) { 447 synchronized (mLock) { 448 String[] possibleValues = mDefaultsStore.getPossibleValues(key); 449 if (possibleValues == null || possibleValues.length == 0) { 450 throw new IllegalArgumentException( 451 "No possible values for scope=" + scope + " key=" + key); 452 } 453 454 String value = getString(scope, key); 455 for (int i = 0; i < possibleValues.length; i++) { 456 if (value.equals(possibleValues[i])) { 457 return i; 458 } 459 } 460 throw new IllegalStateException("Current value for scope=" + scope + " key=" 461 + key + " not in list of possible values"); 462 } 463 } 464 465 /** 466 * Store a setting's value using a String value. No conversion 467 * occurs before this value is stored in SharedPreferences. 468 */ 469 public void set(String scope, String key, String value) { 470 synchronized (mLock) { 471 SharedPreferences preferences = getPreferencesFromScope(scope); 472 preferences.edit().putString(key, value).apply(); 473 } 474 } 475 476 /** 477 * Store a setting's value using an Integer value. Type conversion 478 * to String occurs before this value is stored in SharedPreferences. 479 */ 480 public void set(String scope, String key, int value) { 481 synchronized (mLock) { 482 set(scope, key, convert(value)); 483 } 484 } 485 486 /** 487 * Store a setting's value using a boolean value. Type conversion 488 * to an Integer and then to a String occurs before this value is 489 * stored in SharedPreferences. 490 */ 491 public void set(String scope, String key, boolean value) { 492 synchronized (mLock) { 493 set(scope, key, convert(value)); 494 } 495 } 496 497 /** 498 * Set a setting to the default value stored in the DefaultsStore. 499 */ 500 public void setToDefault(String scope, String key) { 501 synchronized (mLock) { 502 set(scope, key, getStringDefault(key)); 503 } 504 } 505 506 /** 507 * If a set of possible values is defined, set the current value 508 * of a setting to the possible value found at the given index. 509 * 510 * For example, if the possible values for a key are [2,3,5], 511 * and the index given to this method is 2, then this method would 512 * store the value 5 in SharedPreferences for the key. 513 * 514 * If the index is out of the bounds of the range of possible values, 515 * or there are no possible values for this key, then this 516 * method throws an exception. 517 */ 518 public void setValueByIndex(String scope, String key, int index) { 519 synchronized (mLock) { 520 String[] possibleValues = mDefaultsStore.getPossibleValues(key); 521 if (possibleValues.length == 0) { 522 throw new IllegalArgumentException( 523 "No possible values for scope=" + scope + " key=" + key); 524 } 525 526 if (index >= 0 && index < possibleValues.length) { 527 set(scope, key, possibleValues[index]); 528 } else { 529 throw new IndexOutOfBoundsException("For possible values of scope=" + scope 530 + " key=" + key); 531 } 532 } 533 } 534 535 /** 536 * Check that a setting has some value stored. 537 */ 538 public boolean isSet(String scope, String key) { 539 synchronized (mLock) { 540 SharedPreferences preferences = getPreferencesFromScope(scope); 541 return preferences.contains(key); 542 } 543 } 544 545 /** 546 * Check whether a settings's value is currently set to the 547 * default value. 548 */ 549 public boolean isDefault(String scope, String key) { 550 synchronized (mLock) { 551 String defaultValue = getStringDefault(key); 552 String value = getString(scope, key); 553 return value == null ? false : value.equals(defaultValue); 554 } 555 } 556 557 /** 558 * Remove a setting. 559 */ 560 public void remove(String scope, String key) { 561 synchronized (mLock) { 562 SharedPreferences preferences = getPreferencesFromScope(scope); 563 preferences.edit().remove(key).apply(); 564 } 565 } 566 567 /** 568 * Package private conversion method to turn ints into preferred 569 * String storage format. 570 * 571 * @param value int to be stored in Settings 572 * @return String which represents the int 573 */ 574 static String convert(int value) { 575 return Integer.toString(value); 576 } 577 578 /** 579 * Package private conversion method to turn String storage format into 580 * ints. 581 * 582 * @param value String to be converted to int 583 * @return int value of stored String 584 */ 585 static int convertToInt(String value) { 586 return Integer.parseInt(value); 587 } 588 589 /** 590 * Package private conversion method to turn String storage format into 591 * booleans. 592 * 593 * @param value String to be converted to boolean 594 * @return boolean value of stored String 595 */ 596 static boolean convertToBoolean(String value) { 597 return Integer.parseInt(value) != 0; 598 } 599 600 601 /** 602 * Package private conversion method to turn booleans into preferred 603 * String storage format. 604 * 605 * @param value boolean to be stored in Settings 606 * @return String which represents the boolean 607 */ 608 static String convert(boolean value) { 609 return value ? "1" : "0"; 610 } 611 } 612