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