• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SettingsManager(Context context)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      */
getDefaultPreferences()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      */
openPreferences(String scope)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      */
closePreferences(SharedPreferences preferences)151     protected void closePreferences(SharedPreferences preferences) {
152         synchronized (mLock) {
153             for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) {
154                 preferences.unregisterOnSharedPreferenceChangeListener(listener);
155             }
156         }
157     }
158 
getCameraSettingScope(String cameraIdValue)159     public static String getCameraSettingScope(String cameraIdValue) {
160         return CAMERA_SCOPE_PREFIX + cameraIdValue;
161     }
162 
getModuleSettingScope(String moduleScopeNamespace)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          */
onSettingChanged(SettingsManager settingsManager, String key)174         public void onSettingChanged(SettingsManager settingsManager, String key);
175     }
176 
getSharedPreferenceListener( final OnSettingChangedListener listener)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