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 
     26 import java.util.ArrayList;
     27 import java.util.List;
     28 
     29 import javax.annotation.Nullable;
     30 import javax.annotation.concurrent.ThreadSafe;
     31 
     32 
     33 /**
     34  * SettingsManager class provides an api for getting and setting SharedPreferences
     35  * values.
     36  *
     37  * Types
     38  *
     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.
     43  *
     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.
     50  *
     51  * Scope
     52  *
     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.
     57  *
     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.
     63  *
     64  * Keys and Defaults
     65  *
     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.
     69  *
     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.
     73  *
     74  */
     75 @ThreadSafe
     76 public class SettingsManager {
     77     private static final Log.Tag TAG = new Log.Tag("SettingsManager");
     78 
     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();
     85 
     86     public static final String MODULE_SCOPE_PREFIX = "_preferences_module_";
     87     public static final String CAMERA_SCOPE_PREFIX = "_preferences_camera_";
     88 
     89     /**
     90      * A List of OnSettingChangedListener's, maintained to compare to new
     91      * listeners and prevent duplicate registering.
     92      */
     93     private final List<OnSettingChangedListener> mListeners =
     94         new ArrayList<OnSettingChangedListener>();
     95 
     96     /**
     97      * A List of OnSharedPreferenceChangeListener's, maintained to hold pointers
     98      * to actually registered listeners, so they can be unregistered.
     99      */
    100     private final List<OnSharedPreferenceChangeListener> mSharedPreferenceListeners =
    101         new ArrayList<OnSharedPreferenceChangeListener>();
    102 
    103     public SettingsManager(Context context) {
    104         mLock = new Object();
    105         mContext = context;
    106         mPackageName = mContext.getPackageName();
    107 
    108         mDefaultPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
    109     }
    110 
    111     /**
    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
    115      * custom scope.
    116      */
    117     public SharedPreferences getDefaultPreferences() {
    118         synchronized (mLock) {
    119             return mDefaultPreferences;
    120         }
    121     }
    122 
    123     /**
    124      * Open a SharedPreferences file by custom scope.
    125      * Also registers any known SharedPreferenceListeners on this
    126      * SharedPreferences instance.
    127      */
    128     protected SharedPreferences openPreferences(String scope) {
    129         synchronized (mLock) {
    130             SharedPreferences preferences;
    131             preferences = mContext.getSharedPreferences(
    132                     mPackageName + scope, Context.MODE_PRIVATE);
    133 
    134             for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) {
    135                 preferences.registerOnSharedPreferenceChangeListener(listener);
    136             }
    137             return preferences;
    138         }
    139     }
    140 
    141     /**
    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.
    146      *
    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.
    150      */
    151     protected void closePreferences(SharedPreferences preferences) {
    152         synchronized (mLock) {
    153             for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) {
    154                 preferences.unregisterOnSharedPreferenceChangeListener(listener);
    155             }
    156         }
    157     }
    158 
    159     public static String getCameraSettingScope(String cameraIdValue) {
    160         return CAMERA_SCOPE_PREFIX + cameraIdValue;
    161     }
    162 
    163     public static String getModuleSettingScope(String moduleScopeNamespace) {
    164         return CAMERA_SCOPE_PREFIX + moduleScopeNamespace;
    165     }
    166 
    167     /**
    168      * Interface with Camera Device Settings and Modules.
    169      */
    170     public interface OnSettingChangedListener {
    171         /**
    172          * Called every time a SharedPreference has been changed.
    173          */
    174         public void onSettingChanged(SettingsManager settingsManager, String key);
    175     }
    176 
    177     private OnSharedPreferenceChangeListener getSharedPreferenceListener(
    178             final OnSettingChangedListener listener) {
    179         return new OnSharedPreferenceChangeListener() {
    180             @Override
    181             public void onSharedPreferenceChanged(
    182                     SharedPreferences sharedPreferences, String key) {
    183                 listener.onSettingChanged(SettingsManager.this, key);
    184             }
    185         };
    186     }
    187 
    188     /**
    189      * Add an OnSettingChangedListener to the SettingsManager, which will
    190      * execute onSettingsChanged when any SharedPreference has been updated.
    191      */
    192     public void addListener(final OnSettingChangedListener listener) {
    193         synchronized (mLock) {
    194             if (listener == null) {
    195                 throw new IllegalArgumentException("OnSettingChangedListener cannot be null.");
    196             }
    197 
    198             if (mListeners.contains(listener)) {
    199                 return;
    200             }
    201 
    202             mListeners.add(listener);
    203             OnSharedPreferenceChangeListener sharedPreferenceListener =
    204                     getSharedPreferenceListener(listener);
    205             mSharedPreferenceListeners.add(sharedPreferenceListener);
    206             mDefaultPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceListener);
    207 
    208             if (mCustomPreferences != null) {
    209                 mCustomPreferences.registerOnSharedPreferenceChangeListener(
    210                         sharedPreferenceListener);
    211             }
    212             Log.v(TAG, "listeners: " + mListeners);
    213         }
    214     }
    215 
    216     /**
    217      * Remove a specific SettingsListener. This should be done in onPause if a
    218      * listener has been set.
    219      */
    220     public void removeListener(OnSettingChangedListener listener) {
    221         synchronized (mLock) {
    222             if (listener == null) {
    223                 throw new IllegalArgumentException();
    224             }
    225 
    226             if (!mListeners.contains(listener)) {
    227                 return;
    228             }
    229 
    230             int index = mListeners.indexOf(listener);
    231             mListeners.remove(listener);
    232 
    233             OnSharedPreferenceChangeListener sharedPreferenceListener =
    234                     mSharedPreferenceListeners.get(index);
    235             mSharedPreferenceListeners.remove(index);
    236             mDefaultPreferences.unregisterOnSharedPreferenceChangeListener(
    237                     sharedPreferenceListener);
    238 
    239             if (mCustomPreferences != null) {
    240                 mCustomPreferences.unregisterOnSharedPreferenceChangeListener(
    241                         sharedPreferenceListener);
    242             }
    243         }
    244     }
    245 
    246     /**
    247      * Remove all OnSharedPreferenceChangedListener's. This should be done in
    248      * onDestroy.
    249      */
    250     public void removeAllListeners() {
    251         synchronized (mLock) {
    252             for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) {
    253                 mDefaultPreferences.unregisterOnSharedPreferenceChangeListener(listener);
    254 
    255                 if (mCustomPreferences != null) {
    256                     mCustomPreferences.unregisterOnSharedPreferenceChangeListener(listener);
    257                 }
    258             }
    259             mSharedPreferenceListeners.clear();
    260             mListeners.clear();
    261         }
    262     }
    263 
    264     /** This scope stores and retrieves settings from
    265         default preferences. */
    266     public static final String SCOPE_GLOBAL = "default_scope";
    267 
    268     /**
    269      * Returns the SharedPreferences file matching the scope
    270      * argument.
    271      *
    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.
    275      */
    276     private SharedPreferences getPreferencesFromScope(String scope) {
    277         synchronized (mLock) {
    278             if (scope.equals(SCOPE_GLOBAL)) {
    279                 return mDefaultPreferences;
    280             }
    281 
    282             if (mCustomPreferences != null) {
    283                 closePreferences(mCustomPreferences);
    284             }
    285             mCustomPreferences = openPreferences(scope);
    286             return mCustomPreferences;
    287         }
    288     }
    289 
    290     /**
    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.
    294      */
    295     public void setDefaults(String key, String defaultValue, String[] possibleValues) {
    296         synchronized (mLock) {
    297             mDefaultsStore.storeDefaults(key, defaultValue, possibleValues);
    298         }
    299     }
    300 
    301     /**
    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.
    305      */
    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]);
    312             }
    313             mDefaultsStore.storeDefaults(key, defaultValueString, possibleValuesString);
    314         }
    315     }
    316 
    317     /**
    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.
    321      */
    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);
    327         }
    328     }
    329 
    330     /**
    331      * Retrieve a default from the DefaultsStore as a String.
    332      */
    333     public String getStringDefault(String key) {
    334         synchronized (mLock) {
    335             return mDefaultsStore.getDefaultValue(key);
    336         }
    337     }
    338 
    339     /**
    340      * Retrieve a default from the DefaultsStore as an Integer.
    341      */
    342     public Integer getIntegerDefault(String key) {
    343         synchronized (mLock) {
    344             String defaultValueString = mDefaultsStore.getDefaultValue(key);
    345             return defaultValueString == null ? 0 : Integer.parseInt(defaultValueString);
    346         }
    347     }
    348 
    349     /**
    350      * Retrieve a default from the DefaultsStore as a boolean.
    351      */
    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);
    357         }
    358     }
    359 
    360     /**
    361      * Retrieve a setting's value as a String, manually specifiying
    362      * a default value.
    363      */
    364     public String getString(String scope, String key, String defaultValue) {
    365         synchronized (mLock) {
    366             SharedPreferences preferences = getPreferencesFromScope(scope);
    367             try {
    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();
    372                 return defaultValue;
    373             }
    374         }
    375     }
    376 
    377     /**
    378      * Retrieve a setting's value as a String, using the default value
    379      * stored in the DefaultsStore.
    380      */
    381     @Nullable
    382     public String getString(String scope, String key) {
    383         synchronized (mLock) {
    384             return getString(scope, key, getStringDefault(key));
    385         }
    386     }
    387 
    388     /**
    389      * Retrieve a setting's value as an Integer, manually specifying
    390      * a default value.
    391      */
    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);
    397         }
    398     }
    399 
    400     /**
    401      * Retrieve a setting's value as an Integer, converting the default value
    402      * stored in the DefaultsStore.
    403      */
    404     public int getInteger(String scope, String key) {
    405         synchronized (mLock) {
    406             return getInteger(scope, key, getIntegerDefault(key));
    407         }
    408     }
    409 
    410     /**
    411      * Retrieve a setting's value as a boolean, manually specifiying
    412      * a default value.
    413      */
    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);
    419         }
    420     }
    421 
    422     /**
    423      * Retrieve a setting's value as a boolean, converting the default value
    424      * stored in the DefaultsStore.
    425      */
    426     public boolean getBoolean(String scope, String key) {
    427         synchronized (mLock) {
    428             return getBoolean(scope, key, getBooleanDefault(key));
    429         }
    430     }
    431 
    432     /**
    433      * If possible values are stored for this key, return the
    434      * index into that list of the currently set value.
    435      *
    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
    438      * returns 1.
    439      *
    440      * If possible values are not stored for this key, throw
    441      * an IllegalArgumentException.
    442      */
    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);
    449             }
    450 
    451             String value = getString(scope, key);
    452             for (int i = 0; i < possibleValues.length; i++) {
    453                 if (value.equals(possibleValues[i])) {
    454                     return i;
    455                 }
    456             }
    457             throw new IllegalStateException("Current value for scope=" + scope + " key="
    458                     + key + " not in list of possible values");
    459         }
    460     }
    461 
    462     /**
    463      * Store a setting's value using a String value.  No conversion
    464      * occurs before this value is stored in SharedPreferences.
    465      */
    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();
    470         }
    471     }
    472 
    473     /**
    474      * Store a setting's value using an Integer value.  Type conversion
    475      * to String occurs before this value is stored in SharedPreferences.
    476      */
    477     public void set(String scope, String key, int value) {
    478         synchronized (mLock) {
    479             set(scope, key, convert(value));
    480         }
    481     }
    482 
    483     /**
    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.
    487      */
    488     public void set(String scope, String key, boolean value) {
    489         synchronized (mLock) {
    490             set(scope, key, convert(value));
    491         }
    492     }
    493 
    494     /**
    495      * Set a setting to the default value stored in the DefaultsStore.
    496      */
    497     public void setToDefault(String scope, String key) {
    498         synchronized (mLock) {
    499             set(scope, key, getStringDefault(key));
    500         }
    501     }
    502 
    503     /**
    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.
    506      *
    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.
    510      *
    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.
    514      */
    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);
    521             }
    522 
    523             if (index >= 0 && index < possibleValues.length) {
    524                 set(scope, key, possibleValues[index]);
    525             } else {
    526                 throw new IndexOutOfBoundsException("For possible values of scope=" + scope
    527                         + " key=" + key);
    528             }
    529         }
    530     }
    531 
    532     /**
    533      * Check that a setting has some value stored.
    534      */
    535     public boolean isSet(String scope, String key) {
    536         synchronized (mLock) {
    537             SharedPreferences preferences = getPreferencesFromScope(scope);
    538             return preferences.contains(key);
    539         }
    540     }
    541 
    542     /**
    543      * Check whether a settings's value is currently set to the
    544      * default value.
    545      */
    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);
    551         }
    552     }
    553 
    554     /**
    555      * Remove a setting.
    556      */
    557     public void remove(String scope, String key) {
    558         synchronized (mLock) {
    559             SharedPreferences preferences = getPreferencesFromScope(scope);
    560             preferences.edit().remove(key).apply();
    561         }
    562     }
    563 
    564     /**
    565      * Package private conversion method to turn ints into preferred
    566      * String storage format.
    567      *
    568      * @param value int to be stored in Settings
    569      * @return String which represents the int
    570      */
    571     static String convert(int value) {
    572         return Integer.toString(value);
    573     }
    574 
    575     /**
    576      * Package private conversion method to turn String storage format into
    577      * ints.
    578      *
    579      * @param value String to be converted to int
    580      * @return int value of stored String
    581      */
    582     static int convertToInt(String value) {
    583         return Integer.parseInt(value);
    584     }
    585 
    586     /**
    587      * Package private conversion method to turn String storage format into
    588      * booleans.
    589      *
    590      * @param value String to be converted to boolean
    591      * @return boolean value of stored String
    592      */
    593     static boolean convertToBoolean(String value) {
    594         return Integer.parseInt(value) != 0;
    595     }
    596 
    597 
    598     /**
    599      * Package private conversion method to turn booleans into preferred
    600      * String storage format.
    601      *
    602      * @param value boolean to be stored in Settings
    603      * @return String which represents the boolean
    604      */
    605     static String convert(boolean value) {
    606         return value ? "1" : "0";
    607     }
    608 }
    609