/*
|
* Copyright (C) 2013 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
package com.android.camera.settings;
|
|
import android.content.Context;
|
import android.content.SharedPreferences;
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
import android.preference.PreferenceManager;
|
|
import com.android.camera.debug.Log;
|
|
import java.util.ArrayList;
|
import java.util.List;
|
|
import javax.annotation.Nullable;
|
import javax.annotation.concurrent.ThreadSafe;
|
|
|
/**
|
* SettingsManager class provides an api for getting and setting SharedPreferences
|
* values.
|
*
|
* Types
|
*
|
* This API simplifies settings type management by storing all settings values
|
* in SharedPreferences as Strings. To do this, the API to converts boolean and
|
* Integer values to Strings when those values are stored, making the conversion
|
* back to a boolean or Integer also consistent and simple.
|
*
|
* This also enables the user to safely get settings values as three different types,
|
* as it's convenient: String, Integer, and boolean values. Integers and boolean
|
* can always be trivially converted to one another, but Strings cannot always be
|
* parsed as Integers. In this case, if the user stores a String value that cannot
|
* be parsed to an Integer yet they try to retrieve it as an Integer, the API throws
|
* a meaningful exception to the user.
|
*
|
* Scope
|
*
|
* This API introduces the concept of "scope" for a setting, which is the generality
|
* of a setting. The most general settings, that can be accessed acrossed the
|
* entire application, have a scope of SCOPE_GLOBAL. They are stored in the default
|
* SharedPreferences file.
|
*
|
* A setting that is local to a third party module or subset of the application has
|
* a custom scope. The specific module can define whatever scope (String) argument
|
* they want, and the settings saved with that scope can only be seen by that third
|
* party module. Scope is a general concept that helps protect settings values
|
* from being clobbered in different contexts.
|
*
|
* Keys and Defaults
|
*
|
* This API allows you to store your SharedPreferences keys and default values
|
* outside the SettingsManager, because these values are either passed into
|
* the API or stored in a cache when the user sets defaults.
|
*
|
* For any setting, it is optional to store a default or set of possible values,
|
* unless you plan on using the getIndexOfCurrentValue and setValueByIndex,
|
* methods, which rely on an index into the set of possible values.
|
*
|
*/
|
@ThreadSafe
|
public class SettingsManager {
|
private static final Log.Tag TAG = new Log.Tag("SettingsManager");
|
|
private final Object mLock;
|
private final Context mContext;
|
private final String mPackageName;
|
private final SharedPreferences mDefaultPreferences;
|
private SharedPreferences mCustomPreferences;
|
private final DefaultsStore mDefaultsStore = new DefaultsStore();
|
|
public static final String MODULE_SCOPE_PREFIX = "_preferences_module_";
|
public static final String CAMERA_SCOPE_PREFIX = "_preferences_camera_";
|
|
/**
|
* A List of OnSettingChangedListener's, maintained to compare to new
|
* listeners and prevent duplicate registering.
|
*/
|
private final List<OnSettingChangedListener> mListeners =
|
new ArrayList<OnSettingChangedListener>();
|
|
/**
|
* A List of OnSharedPreferenceChangeListener's, maintained to hold pointers
|
* to actually registered listeners, so they can be unregistered.
|
*/
|
private final List<OnSharedPreferenceChangeListener> mSharedPreferenceListeners =
|
new ArrayList<OnSharedPreferenceChangeListener>();
|
|
public SettingsManager(Context context) {
|
mLock = new Object();
|
mContext = context;
|
mPackageName = mContext.getPackageName();
|
|
mDefaultPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
|
}
|
|
/**
|
* Get the SettingsManager's default preferences. This is useful
|
* to third party modules as they are defining their upgrade paths,
|
* since most third party modules will use either SCOPE_GLOBAL or a
|
* custom scope.
|
*/
|
public SharedPreferences getDefaultPreferences() {
|
synchronized (mLock) {
|
return mDefaultPreferences;
|
}
|
}
|
|
/**
|
* Open a SharedPreferences file by custom scope.
|
* Also registers any known SharedPreferenceListeners on this
|
* SharedPreferences instance.
|
*/
|
protected SharedPreferences openPreferences(String scope) {
|
synchronized (mLock) {
|
SharedPreferences preferences;
|
preferences = mContext.getSharedPreferences(
|
mPackageName + scope, Context.MODE_PRIVATE);
|
|
for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) {
|
preferences.registerOnSharedPreferenceChangeListener(listener);
|
}
|
return preferences;
|
}
|
}
|
|
/**
|
* Close a SharedPreferences file by custom scope.
|
* The file isn't explicitly closed (the SharedPreferences API makes
|
* this unnecessary), so the real work is to unregister any known
|
* SharedPreferenceListeners from this SharedPreferences instance.
|
*
|
* It's important to do this as camera and modules change, because
|
* we don't want old SharedPreferences listeners executing on
|
* cameras/modules they are not compatible with.
|
*/
|
protected void closePreferences(SharedPreferences preferences) {
|
synchronized (mLock) {
|
for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) {
|
preferences.unregisterOnSharedPreferenceChangeListener(listener);
|
}
|
}
|
}
|
|
public static String getCameraSettingScope(String cameraIdValue) {
|
return CAMERA_SCOPE_PREFIX + cameraIdValue;
|
}
|
|
public static String getModuleSettingScope(String moduleScopeNamespace) {
|
return CAMERA_SCOPE_PREFIX + moduleScopeNamespace;
|
}
|
|
/**
|
* Interface with Camera Device Settings and Modules.
|
*/
|
public interface OnSettingChangedListener {
|
/**
|
* Called every time a SharedPreference has been changed.
|
*/
|
public void onSettingChanged(SettingsManager settingsManager, String key);
|
}
|
|
private OnSharedPreferenceChangeListener getSharedPreferenceListener(
|
final OnSettingChangedListener listener) {
|
return new OnSharedPreferenceChangeListener() {
|
@Override
|
public void onSharedPreferenceChanged(
|
SharedPreferences sharedPreferences, String key) {
|
listener.onSettingChanged(SettingsManager.this, key);
|
}
|
};
|
}
|
|
/**
|
* Add an OnSettingChangedListener to the SettingsManager, which will
|
* execute onSettingsChanged when any SharedPreference has been updated.
|
*/
|
public void addListener(final OnSettingChangedListener listener) {
|
synchronized (mLock) {
|
if (listener == null) {
|
throw new IllegalArgumentException("OnSettingChangedListener cannot be null.");
|
}
|
|
if (mListeners.contains(listener)) {
|
return;
|
}
|
|
mListeners.add(listener);
|
OnSharedPreferenceChangeListener sharedPreferenceListener =
|
getSharedPreferenceListener(listener);
|
mSharedPreferenceListeners.add(sharedPreferenceListener);
|
mDefaultPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceListener);
|
|
if (mCustomPreferences != null) {
|
mCustomPreferences.registerOnSharedPreferenceChangeListener(
|
sharedPreferenceListener);
|
}
|
Log.v(TAG, "listeners: " + mListeners);
|
}
|
}
|
|
/**
|
* Remove a specific SettingsListener. This should be done in onPause if a
|
* listener has been set.
|
*/
|
public void removeListener(OnSettingChangedListener listener) {
|
synchronized (mLock) {
|
if (listener == null) {
|
throw new IllegalArgumentException();
|
}
|
|
if (!mListeners.contains(listener)) {
|
return;
|
}
|
|
int index = mListeners.indexOf(listener);
|
mListeners.remove(listener);
|
|
OnSharedPreferenceChangeListener sharedPreferenceListener =
|
mSharedPreferenceListeners.get(index);
|
mSharedPreferenceListeners.remove(index);
|
mDefaultPreferences.unregisterOnSharedPreferenceChangeListener(
|
sharedPreferenceListener);
|
|
if (mCustomPreferences != null) {
|
mCustomPreferences.unregisterOnSharedPreferenceChangeListener(
|
sharedPreferenceListener);
|
}
|
}
|
}
|
|
/**
|
* Remove all OnSharedPreferenceChangedListener's. This should be done in
|
* onDestroy.
|
*/
|
public void removeAllListeners() {
|
synchronized (mLock) {
|
for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) {
|
mDefaultPreferences.unregisterOnSharedPreferenceChangeListener(listener);
|
|
if (mCustomPreferences != null) {
|
mCustomPreferences.unregisterOnSharedPreferenceChangeListener(listener);
|
}
|
}
|
mSharedPreferenceListeners.clear();
|
mListeners.clear();
|
}
|
}
|
|
/** This scope stores and retrieves settings from
|
default preferences. */
|
public static final String SCOPE_GLOBAL = "default_scope";
|
|
/**
|
* Returns the SharedPreferences file matching the scope
|
* argument.
|
*
|
* Camera and module preferences files are cached,
|
* until the camera id or module id changes, then the listeners
|
* are unregistered and a new file is opened.
|
*/
|
private SharedPreferences getPreferencesFromScope(String scope) {
|
synchronized (mLock) {
|
if (scope.equals(SCOPE_GLOBAL)) {
|
return mDefaultPreferences;
|
}
|
|
if (mCustomPreferences != null) {
|
closePreferences(mCustomPreferences);
|
}
|
mCustomPreferences = openPreferences(scope);
|
return mCustomPreferences;
|
}
|
}
|
|
/**
|
* Set default and valid values for a setting, for a String default and
|
* a set of String possible values that are already defined.
|
* This is not required.
|
*/
|
public void setDefaults(String key, String defaultValue, String[] possibleValues) {
|
synchronized (mLock) {
|
mDefaultsStore.storeDefaults(key, defaultValue, possibleValues);
|
}
|
}
|
|
/**
|
* Set default and valid values for a setting, for an Integer default and
|
* a set of Integer possible values that are already defined.
|
* This is not required.
|
*/
|
public void setDefaults(String key, int defaultValue, int[] possibleValues) {
|
synchronized (mLock) {
|
String defaultValueString = Integer.toString(defaultValue);
|
String[] possibleValuesString = new String[possibleValues.length];
|
for (int i = 0; i < possibleValues.length; i++) {
|
possibleValuesString[i] = Integer.toString(possibleValues[i]);
|
}
|
mDefaultsStore.storeDefaults(key, defaultValueString, possibleValuesString);
|
}
|
}
|
|
/**
|
* Set default and valid values for a setting, for a boolean default.
|
* The set of boolean possible values is always { false, true }.
|
* This is not required.
|
*/
|
public void setDefaults(String key, boolean defaultValue) {
|
synchronized (mLock) {
|
String defaultValueString = defaultValue ? "1" : "0";
|
String[] possibleValues = {"0", "1"};
|
mDefaultsStore.storeDefaults(key, defaultValueString, possibleValues);
|
}
|
}
|
|
/**
|
* Retrieve a default from the DefaultsStore as a String.
|
*/
|
public String getStringDefault(String key) {
|
synchronized (mLock) {
|
return mDefaultsStore.getDefaultValue(key);
|
}
|
}
|
|
/**
|
* Retrieve a default from the DefaultsStore as an Integer.
|
*/
|
public Integer getIntegerDefault(String key) {
|
synchronized (mLock) {
|
String defaultValueString = mDefaultsStore.getDefaultValue(key);
|
return defaultValueString == null ? 0 : Integer.parseInt(defaultValueString);
|
}
|
}
|
|
/**
|
* Retrieve a default from the DefaultsStore as a boolean.
|
*/
|
public boolean getBooleanDefault(String key) {
|
synchronized (mLock) {
|
String defaultValueString = mDefaultsStore.getDefaultValue(key);
|
return defaultValueString == null ? false :
|
(Integer.parseInt(defaultValueString) != 0);
|
}
|
}
|
|
/**
|
* Retrieve a setting's value as a String, manually specifiying
|
* a default value.
|
*/
|
public String getString(String scope, String key, String defaultValue) {
|
synchronized (mLock) {
|
SharedPreferences preferences = getPreferencesFromScope(scope);
|
try {
|
return preferences.getString(key, defaultValue);
|
} catch (ClassCastException e) {
|
Log.w(TAG, "existing preference with invalid type, removing and returning default", e);
|
preferences.edit().remove(key).apply();
|
return defaultValue;
|
}
|
}
|
}
|
|
/**
|
* Retrieve a setting's value as a String, using the default value
|
* stored in the DefaultsStore.
|
*/
|
@Nullable
|
public String getString(String scope, String key) {
|
synchronized (mLock) {
|
return getString(scope, key, getStringDefault(key));
|
}
|
}
|
|
/**
|
* Retrieve a setting's value as an Integer, manually specifying
|
* a default value.
|
*/
|
public int getInteger(String scope, String key, Integer defaultValue) {
|
synchronized (mLock) {
|
String defaultValueString = Integer.toString(defaultValue);
|
String value = getString(scope, key, defaultValueString);
|
return convertToInt(value);
|
}
|
}
|
|
/**
|
* Retrieve a setting's value as an Integer, converting the default value
|
* stored in the DefaultsStore.
|
*/
|
public int getInteger(String scope, String key) {
|
synchronized (mLock) {
|
return getInteger(scope, key, getIntegerDefault(key));
|
}
|
}
|
|
/**
|
* Retrieve a setting's value as a boolean, manually specifiying
|
* a default value.
|
*/
|
public boolean getBoolean(String scope, String key, boolean defaultValue) {
|
synchronized (mLock) {
|
String defaultValueString = defaultValue ? "1" : "0";
|
String value = getString(scope, key, defaultValueString);
|
return convertToBoolean(value);
|
}
|
}
|
|
/**
|
* Retrieve a setting's value as a boolean, converting the default value
|
* stored in the DefaultsStore.
|
*/
|
public boolean getBoolean(String scope, String key) {
|
synchronized (mLock) {
|
return getBoolean(scope, key, getBooleanDefault(key));
|
}
|
}
|
|
/**
|
* If possible values are stored for this key, return the
|
* index into that list of the currently set value.
|
*
|
* For example, if a set of possible values is [2,3,5],
|
* and the current value set of this key is 3, this method
|
* returns 1.
|
*
|
* If possible values are not stored for this key, throw
|
* an IllegalArgumentException.
|
*/
|
public int getIndexOfCurrentValue(String scope, String key) {
|
synchronized (mLock) {
|
String[] possibleValues = mDefaultsStore.getPossibleValues(key);
|
if (possibleValues == null || possibleValues.length == 0) {
|
throw new IllegalArgumentException(
|
"No possible values for scope=" + scope + " key=" + key);
|
}
|
|
String value = getString(scope, key);
|
for (int i = 0; i < possibleValues.length; i++) {
|
if (value.equals(possibleValues[i])) {
|
return i;
|
}
|
}
|
throw new IllegalStateException("Current value for scope=" + scope + " key="
|
+ key + " not in list of possible values");
|
}
|
}
|
|
/**
|
* Store a setting's value using a String value. No conversion
|
* occurs before this value is stored in SharedPreferences.
|
*/
|
public void set(String scope, String key, String value) {
|
synchronized (mLock) {
|
SharedPreferences preferences = getPreferencesFromScope(scope);
|
preferences.edit().putString(key, value).apply();
|
}
|
}
|
|
/**
|
* Store a setting's value using an Integer value. Type conversion
|
* to String occurs before this value is stored in SharedPreferences.
|
*/
|
public void set(String scope, String key, int value) {
|
synchronized (mLock) {
|
set(scope, key, convert(value));
|
}
|
}
|
|
/**
|
* Store a setting's value using a boolean value. Type conversion
|
* to an Integer and then to a String occurs before this value is
|
* stored in SharedPreferences.
|
*/
|
public void set(String scope, String key, boolean value) {
|
synchronized (mLock) {
|
set(scope, key, convert(value));
|
}
|
}
|
|
/**
|
* Set a setting to the default value stored in the DefaultsStore.
|
*/
|
public void setToDefault(String scope, String key) {
|
synchronized (mLock) {
|
set(scope, key, getStringDefault(key));
|
}
|
}
|
|
/**
|
* If a set of possible values is defined, set the current value
|
* of a setting to the possible value found at the given index.
|
*
|
* For example, if the possible values for a key are [2,3,5],
|
* and the index given to this method is 2, then this method would
|
* store the value 5 in SharedPreferences for the key.
|
*
|
* If the index is out of the bounds of the range of possible values,
|
* or there are no possible values for this key, then this
|
* method throws an exception.
|
*/
|
public void setValueByIndex(String scope, String key, int index) {
|
synchronized (mLock) {
|
String[] possibleValues = mDefaultsStore.getPossibleValues(key);
|
if (possibleValues.length == 0) {
|
throw new IllegalArgumentException(
|
"No possible values for scope=" + scope + " key=" + key);
|
}
|
|
if (index >= 0 && index < possibleValues.length) {
|
set(scope, key, possibleValues[index]);
|
} else {
|
throw new IndexOutOfBoundsException("For possible values of scope=" + scope
|
+ " key=" + key);
|
}
|
}
|
}
|
|
/**
|
* Check that a setting has some value stored.
|
*/
|
public boolean isSet(String scope, String key) {
|
synchronized (mLock) {
|
SharedPreferences preferences = getPreferencesFromScope(scope);
|
return preferences.contains(key);
|
}
|
}
|
|
/**
|
* Check whether a settings's value is currently set to the
|
* default value.
|
*/
|
public boolean isDefault(String scope, String key) {
|
synchronized (mLock) {
|
String defaultValue = getStringDefault(key);
|
String value = getString(scope, key);
|
return value == null ? false : value.equals(defaultValue);
|
}
|
}
|
|
/**
|
* Remove a setting.
|
*/
|
public void remove(String scope, String key) {
|
synchronized (mLock) {
|
SharedPreferences preferences = getPreferencesFromScope(scope);
|
preferences.edit().remove(key).apply();
|
}
|
}
|
|
/**
|
* Package private conversion method to turn ints into preferred
|
* String storage format.
|
*
|
* @param value int to be stored in Settings
|
* @return String which represents the int
|
*/
|
static String convert(int value) {
|
return Integer.toString(value);
|
}
|
|
/**
|
* Package private conversion method to turn String storage format into
|
* ints.
|
*
|
* @param value String to be converted to int
|
* @return int value of stored String
|
*/
|
static int convertToInt(String value) {
|
return Integer.parseInt(value);
|
}
|
|
/**
|
* Package private conversion method to turn String storage format into
|
* booleans.
|
*
|
* @param value String to be converted to boolean
|
* @return boolean value of stored String
|
*/
|
static boolean convertToBoolean(String value) {
|
return Integer.parseInt(value) != 0;
|
}
|
|
|
/**
|
* Package private conversion method to turn booleans into preferred
|
* String storage format.
|
*
|
* @param value boolean to be stored in Settings
|
* @return String which represents the boolean
|
*/
|
static String convert(boolean value) {
|
return value ? "1" : "0";
|
}
|
}
|