2 * Copyright (C) 2013 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.camera.settings;
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
22 import android.preference.PreferenceManager;
24 import com.android.camera.debug.Log;
26 import java.util.ArrayList;
27 import java.util.List;
29 import javax.annotation.Nullable;
30 import javax.annotation.concurrent.ThreadSafe;
34 * SettingsManager class provides an api for getting and setting SharedPreferences
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.
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.
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.
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.
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.
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.
76 public class SettingsManager {
77 private static final Log.Tag TAG = new Log.Tag("SettingsManager");
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();
86 public static final String MODULE_SCOPE_PREFIX = "_preferences_module_";
87 public static final String CAMERA_SCOPE_PREFIX = "_preferences_camera_";
90 * A List of OnSettingChangedListener's, maintained to compare to new
91 * listeners and prevent duplicate registering.
93 private final List<OnSettingChangedListener> mListeners =
94 new ArrayList<OnSettingChangedListener>();
97 * A List of OnSharedPreferenceChangeListener's, maintained to hold pointers
98 * to actually registered listeners, so they can be unregistered.
100 private final List<OnSharedPreferenceChangeListener> mSharedPreferenceListeners =
101 new ArrayList<OnSharedPreferenceChangeListener>();
103 public SettingsManager(Context context) {
104 mLock = new Object();
106 mPackageName = mContext.getPackageName();
108 mDefaultPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
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
117 public SharedPreferences getDefaultPreferences() {
118 synchronized (mLock) {
119 return mDefaultPreferences;
124 * Open a SharedPreferences file by custom scope.
125 * Also registers any known SharedPreferenceListeners on this
126 * SharedPreferences instance.
128 protected SharedPreferences openPreferences(String scope) {
129 synchronized (mLock) {
130 SharedPreferences preferences;
131 preferences = mContext.getSharedPreferences(
132 mPackageName + scope, Context.MODE_PRIVATE);
134 for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) {
135 preferences.registerOnSharedPreferenceChangeListener(listener);
142 * Close a SharedPreferences file by custom scope.
143 * The file isn't explicitly closed (the SharedPreferences API makes
144 * this unnecessary), so the real work is to unregister any known
145 * SharedPreferenceListeners from this SharedPreferences instance.
147 * It's important to do this as camera and modules change, because
148 * we don't want old SharedPreferences listeners executing on
149 * cameras/modules they are not compatible with.
151 protected void closePreferences(SharedPreferences preferences) {
152 synchronized (mLock) {
153 for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) {
154 preferences.unregisterOnSharedPreferenceChangeListener(listener);
159 public static String getCameraSettingScope(String cameraIdValue) {
160 return CAMERA_SCOPE_PREFIX + cameraIdValue;
163 public static String getModuleSettingScope(String moduleScopeNamespace) {
164 return CAMERA_SCOPE_PREFIX + moduleScopeNamespace;
168 * Interface with Camera Device Settings and Modules.
170 public interface OnSettingChangedListener {
172 * Called every time a SharedPreference has been changed.
174 public void onSettingChanged(SettingsManager settingsManager, String key);
177 private OnSharedPreferenceChangeListener getSharedPreferenceListener(
178 final OnSettingChangedListener listener) {
179 return new OnSharedPreferenceChangeListener() {
181 public void onSharedPreferenceChanged(
182 SharedPreferences sharedPreferences, String key) {
183 listener.onSettingChanged(SettingsManager.this, key);
189 * Add an OnSettingChangedListener to the SettingsManager, which will
190 * execute onSettingsChanged when any SharedPreference has been updated.
192 public void addListener(final OnSettingChangedListener listener) {
193 synchronized (mLock) {
194 if (listener == null) {
195 throw new IllegalArgumentException("OnSettingChangedListener cannot be null.");
198 if (mListeners.contains(listener)) {
202 mListeners.add(listener);
203 OnSharedPreferenceChangeListener sharedPreferenceListener =
204 getSharedPreferenceListener(listener);
205 mSharedPreferenceListeners.add(sharedPreferenceListener);
206 mDefaultPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceListener);
208 if (mCustomPreferences != null) {
209 mCustomPreferences.registerOnSharedPreferenceChangeListener(
210 sharedPreferenceListener);
212 Log.v(TAG, "listeners: " + mListeners);
217 * Remove a specific SettingsListener. This should be done in onPause if a
218 * listener has been set.
220 public void removeListener(OnSettingChangedListener listener) {
221 synchronized (mLock) {
222 if (listener == null) {
223 throw new IllegalArgumentException();
226 if (!mListeners.contains(listener)) {
230 int index = mListeners.indexOf(listener);
231 mListeners.remove(listener);
233 OnSharedPreferenceChangeListener sharedPreferenceListener =
234 mSharedPreferenceListeners.get(index);
235 mSharedPreferenceListeners.remove(index);
236 mDefaultPreferences.unregisterOnSharedPreferenceChangeListener(
237 sharedPreferenceListener);
239 if (mCustomPreferences != null) {
240 mCustomPreferences.unregisterOnSharedPreferenceChangeListener(
241 sharedPreferenceListener);
247 * Remove all OnSharedPreferenceChangedListener's. This should be done in
250 public void removeAllListeners() {
251 synchronized (mLock) {
252 for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) {
253 mDefaultPreferences.unregisterOnSharedPreferenceChangeListener(listener);
255 if (mCustomPreferences != null) {
256 mCustomPreferences.unregisterOnSharedPreferenceChangeListener(listener);
259 mSharedPreferenceListeners.clear();
264 /** This scope stores and retrieves settings from
265 default preferences. */
266 public static final String SCOPE_GLOBAL = "default_scope";
269 * Returns the SharedPreferences file matching the scope
272 * Camera and module preferences files are cached,
273 * until the camera id or module id changes, then the listeners
274 * are unregistered and a new file is opened.
276 private SharedPreferences getPreferencesFromScope(String scope) {
277 synchronized (mLock) {
278 if (scope.equals(SCOPE_GLOBAL)) {
279 return mDefaultPreferences;
282 if (mCustomPreferences != null) {
283 closePreferences(mCustomPreferences);
285 mCustomPreferences = openPreferences(scope);
286 return mCustomPreferences;
291 * Set default and valid values for a setting, for a String default and
292 * a set of String possible values that are already defined.
293 * This is not required.
295 public void setDefaults(String key, String defaultValue, String[] possibleValues) {
296 synchronized (mLock) {
297 mDefaultsStore.storeDefaults(key, defaultValue, possibleValues);
302 * Set default and valid values for a setting, for an Integer default and
303 * a set of Integer possible values that are already defined.
304 * This is not required.
306 public void setDefaults(String key, int defaultValue, int[] possibleValues) {
307 synchronized (mLock) {
308 String defaultValueString = Integer.toString(defaultValue);
309 String[] possibleValuesString = new String[possibleValues.length];
310 for (int i = 0; i < possibleValues.length; i++) {
311 possibleValuesString[i] = Integer.toString(possibleValues[i]);
313 mDefaultsStore.storeDefaults(key, defaultValueString, possibleValuesString);
318 * Set default and valid values for a setting, for a boolean default.
319 * The set of boolean possible values is always { false, true }.
320 * This is not required.
322 public void setDefaults(String key, boolean defaultValue) {
323 synchronized (mLock) {
324 String defaultValueString = defaultValue ? "1" : "0";
325 String[] possibleValues = {"0", "1"};
326 mDefaultsStore.storeDefaults(key, defaultValueString, possibleValues);
331 * Retrieve a default from the DefaultsStore as a String.
333 public String getStringDefault(String key) {
334 synchronized (mLock) {
335 return mDefaultsStore.getDefaultValue(key);
340 * Retrieve a default from the DefaultsStore as an Integer.
342 public Integer getIntegerDefault(String key) {
343 synchronized (mLock) {
344 String defaultValueString = mDefaultsStore.getDefaultValue(key);
345 return defaultValueString == null ? 0 : Integer.parseInt(defaultValueString);
350 * Retrieve a default from the DefaultsStore as a boolean.
352 public boolean getBooleanDefault(String key) {
353 synchronized (mLock) {
354 String defaultValueString = mDefaultsStore.getDefaultValue(key);
355 return defaultValueString == null ? false :
356 (Integer.parseInt(defaultValueString) != 0);
361 * Retrieve a setting's value as a String, manually specifiying
364 public String getString(String scope, String key, String defaultValue) {
365 synchronized (mLock) {
366 SharedPreferences preferences = getPreferencesFromScope(scope);
368 return preferences.getString(key, defaultValue);
369 } catch (ClassCastException e) {
370 Log.w(TAG, "existing preference with invalid type, removing and returning default", e);
371 preferences.edit().remove(key).apply();
378 * Retrieve a setting's value as a String, using the default value
379 * stored in the DefaultsStore.
382 public String getString(String scope, String key) {
383 synchronized (mLock) {
384 return getString(scope, key, getStringDefault(key));
389 * Retrieve a setting's value as an Integer, manually specifying
392 public int getInteger(String scope, String key, Integer defaultValue) {
393 synchronized (mLock) {
394 String defaultValueString = Integer.toString(defaultValue);
395 String value = getString(scope, key, defaultValueString);
396 return convertToInt(value);
401 * Retrieve a setting's value as an Integer, converting the default value
402 * stored in the DefaultsStore.
404 public int getInteger(String scope, String key) {
405 synchronized (mLock) {
406 return getInteger(scope, key, getIntegerDefault(key));
411 * Retrieve a setting's value as a boolean, manually specifiying
414 public boolean getBoolean(String scope, String key, boolean defaultValue) {
415 synchronized (mLock) {
416 String defaultValueString = defaultValue ? "1" : "0";
417 String value = getString(scope, key, defaultValueString);
418 return convertToBoolean(value);
423 * Retrieve a setting's value as a boolean, converting the default value
424 * stored in the DefaultsStore.
426 public boolean getBoolean(String scope, String key) {
427 synchronized (mLock) {
428 return getBoolean(scope, key, getBooleanDefault(key));
433 * If possible values are stored for this key, return the
434 * index into that list of the currently set value.
436 * For example, if a set of possible values is [2,3,5],
437 * and the current value set of this key is 3, this method
440 * If possible values are not stored for this key, throw
441 * an IllegalArgumentException.
443 public int getIndexOfCurrentValue(String scope, String key) {
444 synchronized (mLock) {
445 String[] possibleValues = mDefaultsStore.getPossibleValues(key);
446 if (possibleValues == null || possibleValues.length == 0) {
447 throw new IllegalArgumentException(
448 "No possible values for scope=" + scope + " key=" + key);
451 String value = getString(scope, key);
452 for (int i = 0; i < possibleValues.length; i++) {
453 if (value.equals(possibleValues[i])) {
457 throw new IllegalStateException("Current value for scope=" + scope + " key="
458 + key + " not in list of possible values");
463 * Store a setting's value using a String value. No conversion
464 * occurs before this value is stored in SharedPreferences.
466 public void set(String scope, String key, String value) {
467 synchronized (mLock) {
468 SharedPreferences preferences = getPreferencesFromScope(scope);
469 preferences.edit().putString(key, value).apply();
474 * Store a setting's value using an Integer value. Type conversion
475 * to String occurs before this value is stored in SharedPreferences.
477 public void set(String scope, String key, int value) {
478 synchronized (mLock) {
479 set(scope, key, convert(value));
484 * Store a setting's value using a boolean value. Type conversion
485 * to an Integer and then to a String occurs before this value is
486 * stored in SharedPreferences.
488 public void set(String scope, String key, boolean value) {
489 synchronized (mLock) {
490 set(scope, key, convert(value));
495 * Set a setting to the default value stored in the DefaultsStore.
497 public void setToDefault(String scope, String key) {
498 synchronized (mLock) {
499 set(scope, key, getStringDefault(key));
504 * If a set of possible values is defined, set the current value
505 * of a setting to the possible value found at the given index.
507 * For example, if the possible values for a key are [2,3,5],
508 * and the index given to this method is 2, then this method would
509 * store the value 5 in SharedPreferences for the key.
511 * If the index is out of the bounds of the range of possible values,
512 * or there are no possible values for this key, then this
513 * method throws an exception.
515 public void setValueByIndex(String scope, String key, int index) {
516 synchronized (mLock) {
517 String[] possibleValues = mDefaultsStore.getPossibleValues(key);
518 if (possibleValues.length == 0) {
519 throw new IllegalArgumentException(
520 "No possible values for scope=" + scope + " key=" + key);
523 if (index >= 0 && index < possibleValues.length) {
524 set(scope, key, possibleValues[index]);
526 throw new IndexOutOfBoundsException("For possible values of scope=" + scope
533 * Check that a setting has some value stored.
535 public boolean isSet(String scope, String key) {
536 synchronized (mLock) {
537 SharedPreferences preferences = getPreferencesFromScope(scope);
538 return preferences.contains(key);
543 * Check whether a settings's value is currently set to the
546 public boolean isDefault(String scope, String key) {
547 synchronized (mLock) {
548 String defaultValue = getStringDefault(key);
549 String value = getString(scope, key);
550 return value == null ? false : value.equals(defaultValue);
557 public void remove(String scope, String key) {
558 synchronized (mLock) {
559 SharedPreferences preferences = getPreferencesFromScope(scope);
560 preferences.edit().remove(key).apply();
565 * Package private conversion method to turn ints into preferred
566 * String storage format.
568 * @param value int to be stored in Settings
569 * @return String which represents the int
571 static String convert(int value) {
572 return Integer.toString(value);
576 * Package private conversion method to turn String storage format into
579 * @param value String to be converted to int
580 * @return int value of stored String
582 static int convertToInt(String value) {
583 return Integer.parseInt(value);
587 * Package private conversion method to turn String storage format into
590 * @param value String to be converted to boolean
591 * @return boolean value of stored String
593 static boolean convertToBoolean(String value) {
594 return Integer.parseInt(value) != 0;
599 * Package private conversion method to turn booleans into preferred
600 * String storage format.
602 * @param value boolean to be stored in Settings
603 * @return String which represents the boolean
605 static String convert(boolean value) {
606 return value ? "1" : "0";