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         try {
    324             return preferences.getString(key, defaultValue);
    325         } catch (ClassCastException e) {
    326             Log.w(TAG, "existing preference with invalid type, removing and returning default", e);
    327             preferences.edit().remove(key).apply();
    328             return defaultValue;
    329         }
    330     }
    331 
    332     /**
    333      * Retrieve a setting's value as a String, using the default value
    334      * stored in the DefaultsStore.
    335      */
    336     public String getString(String scope, String key) {
    337         return getString(scope, key, getStringDefault(key));
    338     }
    339 
    340     /**
    341      * Retrieve a setting's value as an Integer, manually specifying
    342      * a default value.
    343      */
    344     public Integer getInteger(String scope, String key, Integer defaultValue) {
    345         String defaultValueString = Integer.toString(defaultValue);
    346         String value = getString(scope, key, defaultValueString);
    347         return convertToInt(value);
    348     }
    349 
    350     /**
    351      * Retrieve a setting's value as an Integer, converting the default value
    352      * stored in the DefaultsStore.
    353      */
    354     public Integer getInteger(String scope, String key) {
    355         return getInteger(scope, key, getIntegerDefault(key));
    356     }
    357 
    358     /**
    359      * Retrieve a setting's value as a boolean, manually specifiying
    360      * a default value.
    361      */
    362     public boolean getBoolean(String scope, String key, boolean defaultValue) {
    363         String defaultValueString = defaultValue ? "1" : "0";
    364         String value = getString(scope, key, defaultValueString);
    365         return convertToBoolean(value);
    366     }
    367 
    368     /**
    369      * Retrieve a setting's value as a boolean, converting the default value
    370      * stored in the DefaultsStore.
    371      */
    372     public boolean getBoolean(String scope, String key) {
    373         return getBoolean(scope, key, getBooleanDefault(key));
    374     }
    375 
    376     /**
    377      * Retrieve a setting's value as a {@link Size}. Returns <code>null</code>
    378      * if value could not be parsed as a size.
    379      */
    380     public Size getSize(String scope, String key) {
    381         String strValue = getString(scope, key);
    382         if (strValue == null) {
    383             return null;
    384         }
    385 
    386         String[] widthHeight = strValue.split("x");
    387         if (widthHeight.length != 2) {
    388             return null;
    389         }
    390 
    391         try {
    392             int width = Integer.parseInt(widthHeight[0]);
    393             int height = Integer.parseInt(widthHeight[1]);
    394             return new Size(width, height);
    395         } catch (NumberFormatException ex) {
    396             return null;
    397         }
    398     }
    399 
    400     /**
    401      * If possible values are stored for this key, return the
    402      * index into that list of the currently set value.
    403      *
    404      * For example, if a set of possible values is [2,3,5],
    405      * and the current value set of this key is 3, this method
    406      * returns 1.
    407      *
    408      * If possible values are not stored for this key, throw
    409      * an IllegalArgumentException.
    410      */
    411     public int getIndexOfCurrentValue(String scope, String key) {
    412         String[] possibleValues = mDefaultsStore.getPossibleValues(key);
    413         if (possibleValues == null || possibleValues.length == 0) {
    414             throw new IllegalArgumentException(
    415                 "No possible values for scope=" + scope + " key=" + key);
    416         }
    417 
    418         String value = getString(scope, key);
    419         for (int i = 0; i < possibleValues.length; i++) {
    420             if (value.equals(possibleValues[i])) {
    421                 return i;
    422             }
    423         }
    424         throw new IllegalStateException("Current value for scope=" + scope + " key="
    425                                         + key + " not in list of possible values");
    426     }
    427 
    428     /**
    429      * Store a setting's value using a String value.  No conversion
    430      * occurs before this value is stored in SharedPreferences.
    431      */
    432     public void set(String scope, String key, String value) {
    433         SharedPreferences preferences = getPreferencesFromScope(scope);
    434         preferences.edit().putString(key, value).apply();
    435     }
    436 
    437     /**
    438      * Store a setting's value using an Integer value.  Type conversion
    439      * to String occurs before this value is stored in SharedPreferences.
    440      */
    441     public void set(String scope, String key, int value) {
    442         set(scope, key, convert(value));
    443     }
    444 
    445     /**
    446      * Store a setting's value using a boolean value.  Type conversion
    447      * to an Integer and then to a String occurs before this value is
    448      * stored in SharedPreferences.
    449      */
    450     public void set(String scope, String key, boolean value) {
    451         set(scope, key, convert(value));
    452     }
    453 
    454     /**
    455      * Set a setting to the default value stored in the DefaultsStore.
    456      */
    457     public void setToDefault(String scope, String key) {
    458         set(scope, key, getStringDefault(key));
    459     }
    460 
    461     /**
    462      * If a set of possible values is defined, set the current value
    463      * of a setting to the possible value found at the given index.
    464      *
    465      * For example, if the possible values for a key are [2,3,5],
    466      * and the index given to this method is 2, then this method would
    467      * store the value 5 in SharedPreferences for the key.
    468      *
    469      * If the index is out of the bounds of the range of possible values,
    470      * or there are no possible values for this key, then this
    471      * method throws an exception.
    472      */
    473     public void setValueByIndex(String scope, String key, int index) {
    474         String[] possibleValues = mDefaultsStore.getPossibleValues(key);
    475         if (possibleValues.length == 0) {
    476             throw new IllegalArgumentException(
    477                 "No possible values for scope=" + scope + " key=" + key);
    478         }
    479 
    480         if (index >= 0 && index < possibleValues.length) {
    481             set(scope, key, possibleValues[index]);
    482         } else {
    483             throw new IndexOutOfBoundsException("For possible values of scope=" + scope
    484                                                 + " key=" + key);
    485         }
    486     }
    487 
    488     /**
    489      * Check that a setting has some value stored.
    490      */
    491     public boolean isSet(String scope, String key) {
    492         SharedPreferences preferences = getPreferencesFromScope(scope);
    493         return preferences.contains(key);
    494     }
    495 
    496     /**
    497      * Check whether a settings's value is currently set to the
    498      * default value.
    499      */
    500     public boolean isDefault(String scope, String key) {
    501         String defaultValue = getStringDefault(key);
    502         String value = getString(scope, key);
    503         return value == null ? false : value.equals(defaultValue);
    504     }
    505 
    506     /**
    507      * Remove a setting.
    508      */
    509     public void remove(String scope, String key) {
    510         SharedPreferences preferences = getPreferencesFromScope(scope);
    511         preferences.edit().remove(key).apply();
    512     }
    513 
    514     /**
    515      * Package private conversion method to turn ints into preferred
    516      * String storage format.
    517      *
    518      * @param value int to be stored in Settings
    519      * @return String which represents the int
    520      */
    521     static String convert(int value) {
    522         return Integer.toString(value);
    523     }
    524 
    525     /**
    526      * Package private conversion method to turn String storage format into
    527      * ints.
    528      *
    529      * @param value String to be converted to int
    530      * @return int value of stored String
    531      */
    532     static int convertToInt(String value) {
    533         return Integer.parseInt(value);
    534     }
    535 
    536     /**
    537      * Package private conversion method to turn String storage format into
    538      * booleans.
    539      *
    540      * @param value String to be converted to boolean
    541      * @return boolean value of stored String
    542      */
    543     static boolean convertToBoolean(String value) {
    544         return Integer.parseInt(value) != 0;
    545     }
    546 
    547 
    548     /**
    549      * Package private conversion method to turn booleans into preferred
    550      * String storage format.
    551      *
    552      * @param value boolean to be stored in Settings
    553      * @return String which represents the boolean
    554      */
    555     static String convert(boolean value) {
    556         return value ? "1" : "0";
    557     }
    558 }
    559