• 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 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 
SettingsManager(Context context)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      */
getDefaultPreferences()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      */
openPreferences(String scope)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      */
closePreferences(SharedPreferences preferences)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          */
onSettingChanged(SettingsManager settingsManager, String key)151 	public void onSettingChanged(SettingsManager settingsManager, String key);
152     }
153 
getSharedPreferenceListener( final OnSettingChangedListener listener)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