blob: 6d90832a426c7f867acc428ae2c54f2726abe66f [file] [log] [blame]
/*
* 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 com.android.camera.util.Size;
import java.util.ArrayList;
import java.util.List;
/**
* 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.
*
*/
public class SettingsManager {
private static final Log.Tag TAG = new Log.Tag("SettingsManager");
private final Context mContext;
private final String mPackageName;
private final SharedPreferences mDefaultPreferences;
private SharedPreferences mCustomPreferences;
private final DefaultsStore mDefaultsStore = new DefaultsStore();
/**
* 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) {
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() {
return mDefaultPreferences;
}
/**
* Open a SharedPreferences file by custom scope.
* Also registers any known SharedPreferenceListeners on this
* SharedPreferences instance.
*/
protected SharedPreferences openPreferences(String scope) {
SharedPreferences 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) {
for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) {
preferences.unregisterOnSharedPreferenceChangeListener(listener);
}
}
/**
* 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) {
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) {
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() {
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) {
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) {
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) {
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) {
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) {
return mDefaultsStore.getDefaultValue(key);
}
/**
* Retrieve a default from the DefaultsStore as an Integer.
*/
public Integer getIntegerDefault(String key) {
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) {
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) {
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.
*/
public String getString(String scope, String key) {
return getString(scope, key, getStringDefault(key));
}
/**
* Retrieve a setting's value as an Integer, manually specifying
* a default value.
*/
public Integer getInteger(String scope, String key, Integer defaultValue) {
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 Integer getInteger(String scope, String key) {
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) {
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) {
return getBoolean(scope, key, getBooleanDefault(key));
}
/**
* Retrieve a setting's value as a {@link Size}. Returns <code>null</code>
* if value could not be parsed as a size.
*/
public Size getSize(String scope, String key) {
String strValue = getString(scope, key);
if (strValue == null) {
return null;
}
String[] widthHeight = strValue.split("x");
if (widthHeight.length != 2) {
return null;
}
try {
int width = Integer.parseInt(widthHeight[0]);
int height = Integer.parseInt(widthHeight[1]);
return new Size(width, height);
} catch (NumberFormatException ex) {
return null;
}
}
/**
* 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) {
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) {
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) {
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) {
set(scope, key, convert(value));
}
/**
* Set a setting to the default value stored in the DefaultsStore.
*/
public void setToDefault(String scope, String key) {
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) {
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) {
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) {
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) {
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";
}
}