OSDN Git Service

Implement return to hdr plus after switch to front facing camera.
[android-x86/packages-apps-Camera2.git] / src / com / android / camera / settings / SettingsManager.java
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 import android.util.Log;
24 import android.util.SparseArray;
25
26 import com.android.camera.ListPreference;
27 import com.android.camera.app.AppController;
28 import com.android.camera.app.LocationManager;
29 import com.android.camera.util.CameraUtil;
30 import com.android.camera.util.SettingsHelper;
31 import com.android.camera2.R;
32
33 import java.util.ArrayList;
34 import java.util.List;
35
36 /**
37  * SettingsManager class provides an api for getting and setting both global and
38  * local SharedPreferences.
39  */
40 public class SettingsManager {
41     private static final String TAG = "SettingsManager";
42
43     private final Context mContext;
44     private final SharedPreferences mDefaultSettings;
45     private final SettingsCache mSettingsCache;
46     private SharedPreferences mGlobalSettings;
47     private SharedPreferences mCameraSettings;
48     private final SparseArray<SharedPreferences> mModuleSettings = new SparseArray<SharedPreferences>();
49     private SettingsCapabilities mCapabilities;
50
51     private int mCameraId = -1;
52     private final AppController mAppController;
53
54     /**
55      * General settings upgrade model:
56      *
57      *  On app upgrade, there are three main ways Settings can be stale/incorrect:
58      *  (1) if the type of a setting has changed.
59      *  (2) if a set value is no longer a member of the set of possible values.
60      *  (3) if the SharedPreferences backing file has changed for a setting.
61      *
62      *  Recovery strategies:
63      *  (1) catch the ClassCastException or NumberFormatException and try to do a
64      *      type conversion, or reset the setting to whatever default is valid.
65      *  (2) sanitize sets, and reset to default if set value is no longer valid.
66      *  (3) use the default value by virtue of the setting not yet being in the
67      *      new file.
68      *
69      * Special cases:
70      *
71      *  There are some settings which shouldn't be reset to default on upgrade if
72      *  possible.  We provide a callback which is executed only on strict upgrade.
73      *  This callback does special case upgrades to a subset of the settings.  This
74      *  contrasts  with the general upgrade strategies, which happen lazily, once a
75      *  setting is used.
76      *
77      * Removing obsolete key/value pairs:
78      *
79      *  This can be done in the strict upgrade callback.  The strict upgrade callback
80      *  should be idempotent, so it is important to leave removal code in the upgrade
81      *  callback so the key/value pairs are removed even if a user skips a version.
82      */
83     public interface StrictUpgradeCallback {
84         /**
85          * Will be executed in the SettingsManager constructor if the strict
86          * upgrade version counter has changed.
87          */
88         public void upgrade(SettingsManager settingsManager, int version);
89     }
90
91     /**
92      * Increment this value whenever a new StrictUpgradeCallback needs to
93      * be executed.  This defines upgrade behavior that should be executed
94      * strictly on app upgrades, when the upgrade behavior differs from the general,
95      * lazy upgrade strategies.
96      */
97     private static final int STRICT_UPGRADE_VERSION = 1;
98
99     /**
100      * A List of OnSettingChangedListener's, maintained to compare to new
101      * listeners and prevent duplicate registering.
102      */
103     private final List<OnSettingChangedListener> mListeners = new ArrayList<OnSettingChangedListener>();
104
105     /**
106      * A List of OnSharedPreferenceChangeListener's, maintained to hold pointers
107      * to actually registered listeners, so they can be unregistered.
108      */
109     private final List<OnSharedPreferenceChangeListener> mSharedPreferenceListeners = new ArrayList<OnSharedPreferenceChangeListener>();
110
111     public SettingsManager(Context context, AppController app, int nCameras,
112                            StrictUpgradeCallback upgradeCallback) {
113         mContext = context;
114         mAppController = app;
115
116         SettingsCache.ExtraSettings extraSettings = new SettingsHelper();
117         mSettingsCache = new SettingsCache(mContext, extraSettings);
118
119         mDefaultSettings = PreferenceManager.getDefaultSharedPreferences(context);
120         initGlobal();
121
122         if (upgradeCallback != null) {
123             // Check for a strict version upgrade.
124             int version = getInt(SETTING_STRICT_UPGRADE_VERSION);
125             if (STRICT_UPGRADE_VERSION != version) {
126                 upgradeCallback.upgrade(this, STRICT_UPGRADE_VERSION);
127             }
128             setInt(SETTING_STRICT_UPGRADE_VERSION, STRICT_UPGRADE_VERSION);
129         }
130     }
131
132     /**
133      * Initialize global SharedPreferences.
134      */
135     private void initGlobal() {
136         String globalKey = mContext.getPackageName() + "_preferences_camera";
137         mGlobalSettings = mContext.getSharedPreferences(globalKey, Context.MODE_PRIVATE);
138     }
139
140     /**
141      * Load and cache a module specific SharedPreferences.
142      */
143     public SharedPreferences getModulePreferences(int modeIndex) {
144         SharedPreferences sharedPreferences = mModuleSettings.get(modeIndex);
145         if (sharedPreferences == null) {
146             String moduleKey = mContext.getPackageName() + "_preferences_module_" + modeIndex;
147             sharedPreferences = mContext.getSharedPreferences(moduleKey, Context.MODE_PRIVATE);
148             mModuleSettings.put(modeIndex, sharedPreferences);
149         }
150         return sharedPreferences;
151     }
152
153     /**
154      * Initialize SharedPreferences for other cameras.
155      */
156     public void changeCamera(int cameraId, SettingsCapabilities capabilities) {
157         mCapabilities = capabilities;
158         mSettingsCache.setCapabilities(mCapabilities);
159
160         if (cameraId == mCameraId) {
161             return;
162         }
163
164         // We've changed camera ids, that means we need to flush the
165         // settings cache of settings dependent on SettingsCapabilities.
166         mSettingsCache.flush();
167
168         // Cache the camera id so we don't need to reload preferences
169         // if we're using the same camera.
170         mCameraId = cameraId;
171
172         String cameraKey = mContext.getPackageName() + "_preferences_" + cameraId;
173         mCameraSettings = mContext.getSharedPreferences(
174                 cameraKey, Context.MODE_PRIVATE);
175
176         for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) {
177             mCameraSettings.registerOnSharedPreferenceChangeListener(listener);
178         }
179     }
180
181     /**
182      * Interface with Camera Parameters and Modules.
183      */
184     public interface OnSettingChangedListener {
185         /**
186          * Called every time a SharedPreference has been changed.
187          */
188         public void onSettingChanged(SettingsManager settingsManager, int setting);
189     }
190
191     private OnSharedPreferenceChangeListener getSharedPreferenceListener(
192             final OnSettingChangedListener listener) {
193         return new OnSharedPreferenceChangeListener() {
194             @Override
195             public void onSharedPreferenceChanged(
196                     SharedPreferences sharedPreferences, String key) {
197                 Integer settingId = mSettingsCache.getId(key);
198                 if (settingId != null) {
199                     listener.onSettingChanged(SettingsManager.this, settingId);
200                 } else {
201                     Log.w(TAG, "Setting id from key=" + key + " is null");
202                 }
203             }
204         };
205     }
206
207     /**
208      * Add an OnSettingChangedListener to the SettingsManager, which will
209      * execute onSettingsChanged when any SharedPreference has been updated.
210      */
211     public void addListener(final OnSettingChangedListener listener) {
212         if (listener == null) {
213             throw new IllegalArgumentException("OnSettingChangedListener cannot be null.");
214         }
215
216         if (mListeners.contains(listener)) {
217             return;
218         }
219
220         mListeners.add(listener);
221         OnSharedPreferenceChangeListener sharedPreferenceListener =
222                 getSharedPreferenceListener(listener);
223         mSharedPreferenceListeners.add(sharedPreferenceListener);
224
225         if (mGlobalSettings != null) {
226             mGlobalSettings.registerOnSharedPreferenceChangeListener(sharedPreferenceListener);
227         }
228
229         if (mCameraSettings != null) {
230             mCameraSettings.registerOnSharedPreferenceChangeListener(sharedPreferenceListener);
231         }
232
233         if (mDefaultSettings != null) {
234             mDefaultSettings.registerOnSharedPreferenceChangeListener(sharedPreferenceListener);
235         }
236     }
237
238     /**
239      * Remove a specific SettingsListener. This should be done in onPause if a
240      * listener has been set.
241      */
242     public void removeListener(OnSettingChangedListener listener) {
243         if (listener == null) {
244             throw new IllegalArgumentException();
245         }
246
247         if (!mListeners.contains(listener)) {
248             return;
249         }
250
251         int index = mListeners.indexOf(listener);
252         mListeners.remove(listener);
253
254         // Get the reference to the actual OnSharedPreferenceChangeListener
255         // that was registered.
256         OnSharedPreferenceChangeListener sharedPreferenceListener =
257                 mSharedPreferenceListeners.get(index);
258         mSharedPreferenceListeners.remove(index);
259
260         if (mGlobalSettings != null) {
261             mGlobalSettings.unregisterOnSharedPreferenceChangeListener(
262                     sharedPreferenceListener);
263         }
264
265         if (mCameraSettings != null) {
266             mCameraSettings.unregisterOnSharedPreferenceChangeListener(
267                     sharedPreferenceListener);
268         }
269
270         if (mDefaultSettings != null) {
271             mDefaultSettings.unregisterOnSharedPreferenceChangeListener(
272                     sharedPreferenceListener);
273         }
274     }
275
276     /**
277      * Remove all OnSharedPreferenceChangedListener's. This should be done in
278      * onDestroy.
279      */
280     public void removeAllListeners() {
281         for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) {
282             if (mGlobalSettings != null) {
283                 mGlobalSettings.unregisterOnSharedPreferenceChangeListener(listener);
284             }
285
286             if (mCameraSettings != null) {
287                 mCameraSettings.unregisterOnSharedPreferenceChangeListener(listener);
288             }
289
290             if (mDefaultSettings != null) {
291                 mDefaultSettings.unregisterOnSharedPreferenceChangeListener(listener);
292             }
293         }
294         mSharedPreferenceListeners.clear();
295         mListeners.clear();
296     }
297
298     /**
299      * SettingsCapabilities defines constraints around settings that need to be
300      * queried from external sources, like the camera parameters. This interface
301      * is camera api agnostic.
302      */
303     public interface SettingsCapabilities {
304         /**
305          * Returns a dynamically calculated list of exposure values, based on
306          * the min and max exposure compensation supported by the camera device.
307          */
308         public String[] getSupportedExposureValues();
309
310         /**
311          * Returns a list of camera ids based on the number of cameras available
312          * on the device.
313          */
314         public String[] getSupportedCameraIds();
315     }
316
317     /**
318      * Get the camera id for which the SettingsManager has loaded camera
319      * specific preferences.
320      */
321     public int getRegisteredCameraId() {
322         return mCameraId;
323     }
324
325     // Manage individual settings.
326     public static final String VALUE_NONE = "none";
327     public static final String VALUE_ON = "on";
328     public static final String VALUE_OFF = "off";
329
330     public static final String TYPE_STRING = "string";
331     public static final String TYPE_BOOLEAN = "boolean";
332     public static final String TYPE_INTEGER = "integer";
333
334     public static final String SOURCE_DEFAULT = "default";
335     public static final String SOURCE_GLOBAL = "global";
336     public static final String SOURCE_CAMERA = "camera";
337     public static final String SOURCE_MODULE = "module";
338
339     public static final boolean FLUSH_ON = true;
340     public static final boolean FLUSH_OFF = false;
341
342     // For quick lookup from id to Setting.
343     public static final int SETTING_RECORD_LOCATION = 0;
344     public static final int SETTING_VIDEO_QUALITY = 1;
345     public static final int SETTING_VIDEO_TIME_LAPSE_FRAME_INTERVAL = 2;
346     public static final int SETTING_PICTURE_SIZE = 3;
347     public static final int SETTING_JPEG_QUALITY = 4;
348     public static final int SETTING_FOCUS_MODE = 5;
349     public static final int SETTING_FLASH_MODE = 6;
350     public static final int SETTING_VIDEOCAMERA_FLASH_MODE = 7;
351     public static final int SETTING_WHITE_BALANCE = 8;
352     public static final int SETTING_SCENE_MODE = 9;
353     public static final int SETTING_EXPOSURE = 10;
354     public static final int SETTING_TIMER = 11;
355     public static final int SETTING_TIMER_SOUND_EFFECTS = 12;
356     public static final int SETTING_VIDEO_EFFECT = 13;
357     public static final int SETTING_CAMERA_ID = 14;
358     public static final int SETTING_CAMERA_HDR = 15;
359     public static final int SETTING_CAMERA_HDR_PLUS = 16;
360     public static final int SETTING_CAMERA_FIRST_USE_HINT_SHOWN = 17;
361     public static final int SETTING_VIDEO_FIRST_USE_HINT_SHOWN = 18;
362     public static final int SETTING_STARTUP_MODULE_INDEX = 19;
363     public static final int SETTING_SHIMMY_REMAINING_PLAY_TIMES_INDEX = 20;
364     public static final int SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX = 21;
365     public static final int SETTING_CAMERA_PANO_ORIENTATION = 22;
366     public static final int SETTING_CAMERA_GRID_LINES = 23;
367     public static final int SETTING_RELEASE_DIALOG_LAST_SHOWN_VERSION = 24;
368     public static final int SETTING_FLASH_SUPPORTED_BACK_CAMERA = 25;
369     public static final int SETTING_STRICT_UPGRADE_VERSION = 26;
370     public static final int SETTING_FILMSTRIP_PEEK_ANIM_REMAINING_PLAY_TIMES_INDEX = 27;
371     // A boolean for requesting to return to HDR plus
372     // as soon as possible, if a user requests a setting/mode option
373     // that forces them to leave HDR plus.
374     public static final int SETTING_REQUEST_RETURN_HDR_PLUS = 28;
375
376     // Shared preference keys.
377     public static final String KEY_RECORD_LOCATION = "pref_camera_recordlocation_key";
378     public static final String KEY_VIDEO_QUALITY = "pref_video_quality_key";
379     public static final String KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL =
380             "pref_video_time_lapse_frame_interval_key";
381     public static final String KEY_PICTURE_SIZE = "pref_camera_picturesize_key";
382     public static final String KEY_JPEG_QUALITY = "pref_camera_jpegquality_key";
383     public static final String KEY_FOCUS_MODE = "pref_camera_focusmode_key";
384     public static final String KEY_FLASH_MODE = "pref_camera_flashmode_key";
385     public static final String KEY_VIDEOCAMERA_FLASH_MODE = "pref_camera_video_flashmode_key";
386     public static final String KEY_WHITE_BALANCE = "pref_camera_whitebalance_key";
387     public static final String KEY_SCENE_MODE = "pref_camera_scenemode_key";
388     public static final String KEY_EXPOSURE = "pref_camera_exposure_key";
389     public static final String KEY_TIMER = "pref_camera_timer_key";
390     public static final String KEY_TIMER_SOUND_EFFECTS = "pref_camera_timer_sound_key";
391     public static final String KEY_VIDEO_EFFECT = "pref_video_effect_key";
392     public static final String KEY_CAMERA_ID = "pref_camera_id_key";
393     public static final String KEY_CAMERA_HDR = "pref_camera_hdr_key";
394     public static final String KEY_CAMERA_HDR_PLUS = "pref_camera_hdr_plus_key";
395     public static final String KEY_CAMERA_FIRST_USE_HINT_SHOWN =
396             "pref_camera_first_use_hint_shown_key";
397     public static final String KEY_VIDEO_FIRST_USE_HINT_SHOWN =
398             "pref_video_first_use_hint_shown_key";
399     public static final String KEY_STARTUP_MODULE_INDEX = "camera.startup_module";
400     public static final String KEY_SHIMMY_REMAINING_PLAY_TIMES =
401             "pref_shimmy_remaining_play_times";
402     public static final String KEY_CAMERA_MODULE_LAST_USED =
403             "pref_camera_module_last_used_index";
404     public static final String KEY_CAMERA_PANO_ORIENTATION = "pref_camera_pano_orientation";
405     public static final String KEY_CAMERA_GRID_LINES = "pref_camera_grid_lines";
406     public static final String KEY_RELEASE_DIALOG_LAST_SHOWN_VERSION =
407             "pref_release_dialog_last_shown_version";
408     public static final String KEY_FLASH_SUPPORTED_BACK_CAMERA =
409             "pref_flash_supported_back_camera";
410     public static final String KEY_STRICT_UPGRADE_VERSION = "pref_strict_upgrade_version";
411     public static final String KEY_FILMSTRIP_PEEK_ANIM_REMAINING_PLAY_TIMES =
412             "pref_filmstrip_peek_anim_remaining_play_times";
413     public static final String KEY_REQUEST_RETURN_HDR_PLUS = "pref_request_return_hdr_plus";
414
415     public static final int WHITE_BALANCE_DEFAULT_INDEX = 2;
416
417     /**
418      * Defines a simple class for holding a the spec of a setting. This spec is
419      * used by the generic api methods to query and update a setting.
420      */
421     public static class Setting {
422         private final String mSource;
423         private final String mType;
424         private final String mDefault;
425         private final String mKey;
426         private final String[] mValues;
427         private final boolean mFlushOnCameraChange;
428
429         /**
430          * A constructor used to store a setting's profile.
431          */
432         Setting(String source, String type, String defaultValue, String key,
433                 String[] values, boolean flushOnCameraChange) {
434             mSource = source;
435             mType = type;
436             mDefault = defaultValue;
437             mKey = key;
438             mValues = values;
439             mFlushOnCameraChange = flushOnCameraChange;
440         }
441
442         /**
443          * Returns the id of a SharedPreferences instance from which this
444          * Setting may be found. Possible values are {@link #SOURCE_DEFAULT},
445          * {@link #SOURCE_GLOBAL}, {@link #SOURCE_CAMERA}.
446          */
447         public String getSource() {
448             return mSource;
449         }
450
451         /**
452          * Returns the type of the setting stored in SharedPreferences. Possible
453          * values are {@link #TYPE_STRING}, {@link #TYPE_INTEGER},
454          * {@link #TYPE_BOOLEAN}.
455          */
456         public String getType() {
457             return mType;
458         }
459
460         /**
461          * Returns the default value of this setting.
462          */
463         public String getDefault() {
464             return mDefault;
465         }
466
467         /**
468          * Returns the SharedPreferences key for this setting.
469          */
470         public String getKey() {
471             return mKey;
472         }
473
474         /**
475          * Returns an array of possible String values for this setting. If this
476          * setting is not of type {@link #TYPE_STRING}, or it's not possible to
477          * generate the string values, this should return null;
478          */
479         public String[] getStringValues() {
480             return mValues;
481         }
482
483         /**
484          * Returns whether the setting should be flushed from the cache when the
485          * camera device has changed.
486          */
487         public boolean isFlushedOnCameraChanged() {
488             return mFlushOnCameraChange;
489         }
490     }
491
492     /**
493      * Get the SharedPreferences needed to query/update the setting.
494      */
495     public SharedPreferences getSettingSource(Setting setting) {
496         String source = setting.getSource();
497         if (source.equals(SOURCE_DEFAULT)) {
498             return mDefaultSettings;
499         }
500         if (source.equals(SOURCE_GLOBAL)) {
501             return mGlobalSettings;
502         }
503         if (source.equals(SOURCE_CAMERA)) {
504             return mCameraSettings;
505         }
506         if (source.equals(SOURCE_MODULE)) {
507             int modeIndex = CameraUtil.getCameraModeParentModeId(
508                 mAppController.getCurrentModuleIndex(), mAppController.getAndroidContext());
509             return getModulePreferences(modeIndex);
510         }
511         return null;
512     }
513
514     /**
515      * Based on Setting id, finds the index of a Setting's String value in an
516      * array of possible String values. If the Setting is not of type String,
517      * this returns -1.
518      * <p>
519      * TODO: make this a supported api call for all types.
520      * </p>
521      */
522     public int getStringValueIndex(int id) {
523         Setting setting = mSettingsCache.get(id);
524         if (setting == null || !TYPE_STRING.equals(setting.getType())) {
525             return -1;
526         }
527         return getStringValueIndex(setting.getStringValues(), get(id));
528     }
529
530     private int getStringValueIndex(String[] possibleValues, String value) {
531         if (value != null) {
532             if (possibleValues != null) {
533                 for (int i = 0; i < possibleValues.length; i++) {
534                     if (value.equals(possibleValues[i])) {
535                         return i;
536                     }
537                 }
538             }
539         }
540         return -1;
541     }
542
543     /**
544      * Based on Setting id, sets a Setting's String value using the index into
545      * an array of possible String values. Fails to set a value if the index is
546      * out of bounds or the Setting is not of type String.
547      *
548      * @return Whether the value was set.
549      */
550     public boolean setStringValueIndex(int id, int index) {
551         Setting setting = mSettingsCache.get(id);
552         if (setting == null || setting.getType() != TYPE_STRING) {
553             return false;
554         }
555
556         String[] possibleValues = setting.getStringValues();
557         if (possibleValues != null) {
558             if (index >= 0 && index < possibleValues.length) {
559                 set(id, possibleValues[index]);
560                 return true;
561             }
562         }
563         return false;
564     }
565
566     /**
567      * Returns whether this Setting was last set as a String.
568      */
569     private boolean isString(int id) {
570         Setting setting = mSettingsCache.get(id);
571         SharedPreferences preferences = getSettingSource(setting);
572         try {
573             preferences.getString(setting.getKey(), null);
574             return true;
575         } catch (ClassCastException e) {
576             return false;
577         }
578     }
579
580     /**
581      * Returns whether this Setting was last set as a boolean.
582      */
583     private boolean isBoolean(int id) {
584         Setting setting = mSettingsCache.get(id);
585         SharedPreferences preferences = getSettingSource(setting);
586         try {
587             preferences.getBoolean(setting.getKey(), false);
588             return true;
589         } catch (ClassCastException e) {
590             return false;
591         }
592     }
593
594     /**
595      * Returns whether this Setting was last set as an Integer.
596      */
597     private boolean isInteger(int id) {
598         Setting setting = mSettingsCache.get(id);
599         SharedPreferences preferences = getSettingSource(setting);
600         try {
601             preferences.getInt(setting.getKey(), 0);
602             return true;
603         } catch (NumberFormatException e) {
604             return false;
605         }
606     }
607
608     /**
609      * Recover a Setting by converting it to a String if the type
610      * is known and the type conversion is successful, otherwise
611      * reset to the default.
612      */
613     private String recoverToString(int id) {
614         String value;
615         try {
616             if (isBoolean(id)) {
617                 value = (getBoolean(id) ? VALUE_ON : VALUE_OFF);
618             } else if (isInteger(id)) {
619                 value = Integer.toString(getInt(id));
620             } else {
621                 throw new Exception();
622             }
623         } catch (Exception e) {
624             value = mSettingsCache.get(id).getDefault();
625         }
626         set(id, value);
627         return value;
628     }
629
630     /**
631      * Recover a Setting by converting it to a boolean if the type
632      * is known and the type conversion is successful, otherwise
633      * reset to the default.
634      */
635     private boolean recoverToBoolean(int id) {
636         boolean value;
637         try {
638             if (isString(id)) {
639                 value = VALUE_ON.equals(get(id));
640             } else if (isInteger(id)) {
641                 value = getInt(id) != 0;
642             } else {
643                 throw new Exception();
644             }
645         } catch (Exception e) {
646             value = VALUE_ON.equals(mSettingsCache.get(id).getDefault());
647         }
648         setBoolean(id, value);
649         return value;
650     }
651
652     /**
653      * Recover a Setting by converting it to an Integer if the type
654      * is known and the type conversion is successful, otherwise
655      * reset to the default.
656      */
657     private int recoverToInteger(int id) {
658         int value;
659         try {
660             if (isString(id)) {
661                 value = Integer.parseInt(get(id));
662             } else if (isBoolean(id)) {
663                 value = getBoolean(id) ? 1 : 0;
664             } else {
665                 throw new Exception();
666             }
667         } catch (Exception e) {
668             value = Integer.parseInt(mSettingsCache.get(id).getDefault());
669         }
670         setInt(id, value);
671         return value;
672     }
673
674     /**
675      * Check if a String value is in the set of possible values for a Setting.
676      * We only keep track of possible values for String types for now.
677      */
678     private String sanitize(Setting setting, String value) {
679         if (setting.getStringValues() != null &&
680                 getStringValueIndex(setting.getStringValues(), value) < 0) {
681             // If the set of possible values is not empty, and the value
682             // is not in the set of possible values, use the default, because
683             // the set of possible values probably changed.
684             return setting.getDefault();
685         }
686         return value;
687     }
688
689     /**
690      * Get a Setting's String value based on Setting id.
691      */
692     // TODO: rename to something more descriptive.
693     public String get(int id) {
694         Setting setting = mSettingsCache.get(id);
695         if (!TYPE_STRING.equals(setting.getType())) {
696             // Incorrect use of the api, the defaults will
697             // probably be defined as the wrong type, no recovery.
698             throw new IllegalArgumentException(
699                 "Trying to get String when Setting id=" + id
700                 + " is defined as a " + setting.getType());
701         }
702
703         SharedPreferences preferences = getSettingSource(setting);
704         if (preferences != null) {
705             try {
706                 String value = preferences.getString(setting.getKey(), setting.getDefault());
707                 return sanitize(setting, value);
708             } catch (ClassCastException e) {
709                 // If the api defines this Setting as a String, but the
710                 // last set saved it as a different type, try to recover
711                 // the value, but if impossible reset to default.
712                 return recoverToString(id);
713             }
714         } else {
715             throw new IllegalStateException(
716                 "Setting source=" + setting.getSource() + " is unitialized.");
717         }
718     }
719
720     /**
721      * Get a Setting's boolean value based on Setting id.
722      */
723     public boolean getBoolean(int id) {
724         Setting setting = mSettingsCache.get(id);
725         if (!TYPE_BOOLEAN.equals(setting.getType())) {
726             // Incorrect use of the api, the defaults will
727             // probably be defined as the wrong type, no recovery.
728             throw new IllegalArgumentException(
729                 "Trying to get boolean when Setting id=" + id
730                 + " is defined as a " + setting.getType());
731         }
732
733         SharedPreferences preferences = getSettingSource(setting);
734         boolean defaultValue = VALUE_ON.equals(setting.getDefault());
735         if (preferences != null) {
736             try {
737                 return preferences.getBoolean(setting.getKey(), defaultValue);
738             } catch (ClassCastException e) {
739                 // If the api defines this Setting as a boolean, but the
740                 // last set saved it as a different type, try to recover
741                 // the value, but if impossible reset to default.
742                 return recoverToBoolean(id);
743             }
744         } else {
745             throw new IllegalStateException(
746                 "Setting source=" + setting.getSource() + " is unitialized.");
747         }
748     }
749
750     /**
751      * Get a Setting's int value based on Setting id.
752      */
753     public int getInt(int id) {
754         Setting setting = mSettingsCache.get(id);
755         if (!TYPE_INTEGER.equals(setting.getType())) {
756             // Incorrect use of the api, the defaults will
757             // probably be defined as the wrong type, no recovery.
758             throw new IllegalArgumentException(
759                 "Trying to get Integer when Setting id=" + id
760                 + " is defined as a " + setting.getType());
761         }
762
763         SharedPreferences preferences = getSettingSource(setting);
764         int defaultValue = Integer.parseInt(setting.getDefault());
765         if (preferences != null) {
766             try {
767                 return preferences.getInt(setting.getKey(), defaultValue);
768             } catch (NumberFormatException e) {
769                 // If the api defines this Setting as an Integer, but the
770                 // last set saved it as a different type, try to recover
771                 // the value, but if impossible reset to default.
772                 return recoverToInteger(id);
773             }
774         } else {
775             throw new IllegalStateException(
776                 "Setting source=" + setting.getSource() + " is unitialized.");
777         }
778     }
779
780     /**
781      * Set a Setting with a String value based on Setting id.
782      */
783     // TODO: rename to something more descriptive.
784     public void set(int id, String value) {
785         Setting setting = mSettingsCache.get(id);
786         value = sanitize(setting, value);
787         SharedPreferences preferences = getSettingSource(setting);
788         if (preferences != null) {
789             preferences.edit().putString(setting.getKey(), value).apply();
790         } else {
791             throw new IllegalStateException(
792                 "Setting source=" + setting.getSource() + " is unitialized.");
793         }
794     }
795
796     /**
797      * Set a Setting with a boolean value based on Setting id.
798      */
799     public void setBoolean(int id, boolean value) {
800         Setting setting = mSettingsCache.get(id);
801         SharedPreferences preferences = getSettingSource(setting);
802         if (preferences != null) {
803             preferences.edit().putBoolean(setting.getKey(), value).apply();
804         } else {
805             throw new IllegalStateException(
806                 "Setting source=" + setting.getSource() + " is unitialized.");
807         }
808     }
809
810     /**
811      * Set a Setting with an int value based on Setting id.
812      */
813     public void setInt(int id, int value) {
814         Setting setting = mSettingsCache.get(id);
815         SharedPreferences preferences = getSettingSource(setting);
816         if (preferences != null) {
817             preferences.edit().putInt(setting.getKey(), value).apply();
818         } else {
819             throw new IllegalStateException(
820                 "Setting source=" + setting.getSource() + " is unitialized.");
821         }
822     }
823
824     /**
825      * Check if a Setting has ever been set based on Setting id.
826      */
827     public boolean isSet(int id) {
828         Setting setting = mSettingsCache.get(id);
829         SharedPreferences preferences = getSettingSource(setting);
830         if (preferences != null) {
831             return preferences.contains(setting.getKey());
832         } else {
833             throw new IllegalStateException(
834                 "Setting source=" + setting.getSource() + " is unitialized.");
835         }
836     }
837
838     /**
839      * Set a Setting to its default value based on Setting id.
840      */
841     public void setDefault(int id) {
842         Setting setting = mSettingsCache.get(id);
843         SharedPreferences preferences = getSettingSource(setting);
844         if (preferences != null) {
845             preferences.edit().putString(setting.getKey(), setting.getDefault());
846         } else {
847             throw new IllegalStateException(
848                 "Setting source=" + setting.getSource() + " is unitialized.");
849         }
850     }
851
852     /**
853      * Check if a Setting is set to its default value.
854      */
855     public boolean isDefault(int id) {
856         Setting setting = mSettingsCache.get(id);
857         SharedPreferences preferences = getSettingSource(setting);
858         if (preferences != null) {
859             String type = setting.getType();
860             if (TYPE_STRING.equals(type)) {
861                 String value = get(id);
862                 return (value.equals(setting.getDefault()));
863             } else if (TYPE_BOOLEAN.equals(type)) {
864                 boolean value = getBoolean(id);
865                 boolean defaultValue = VALUE_ON.equals(setting.getDefault());
866                 return (value == defaultValue);
867             } else if (TYPE_INTEGER.equals(type)) {
868                 int value = getInt(id);
869                 int defaultValue = Integer.parseInt(setting.getDefault());
870                 return (value == defaultValue);
871             } else {
872                 throw new IllegalArgumentException("Type " + type + " is not known.");
873             }
874         } else {
875             throw new IllegalStateException(
876                 "Setting source=" + setting.getSource() + " is unitialized.");
877         }
878     }
879
880     /**
881      * Remove a Setting from SharedPreferences.
882      */
883     public void remove(int id) {
884         Setting setting = mSettingsCache.get(id);
885         SharedPreferences preferences = getSettingSource(setting);
886         if (preferences != null) {
887             preferences.edit().remove(setting.getKey()).apply();
888         } else {
889             throw new IllegalStateException(
890                 "Setting source=" + setting.getSource() + " is unitialized.");
891         }
892     }
893
894     public static Setting getLocationSetting(Context context) {
895         String defaultValue = context.getString(R.string.setting_none_value);
896         String[] values = null;
897         return new Setting(SOURCE_DEFAULT, TYPE_BOOLEAN, defaultValue, KEY_RECORD_LOCATION,
898                 values, FLUSH_OFF);
899     }
900
901     public static Setting getPictureSizeSetting(Context context) {
902         String defaultValue = null;
903         String[] values = context.getResources().getStringArray(
904                 R.array.pref_camera_picturesize_entryvalues);
905         return new Setting(SOURCE_DEFAULT, TYPE_STRING, defaultValue, KEY_PICTURE_SIZE,
906                 values, FLUSH_OFF);
907     }
908
909     public static Setting getDefaultCameraIdSetting(Context context,
910             SettingsCapabilities capabilities) {
911         String defaultValue = context.getString(R.string.pref_camera_id_default);
912         String[] values = null;
913         if (capabilities != null) {
914             values = capabilities.getSupportedCameraIds();
915         }
916         return new Setting(SOURCE_MODULE, TYPE_STRING, defaultValue, KEY_CAMERA_ID,
917                 values, FLUSH_ON);
918     }
919
920     public static Setting getWhiteBalanceSetting(Context context) {
921         String defaultValue = context.getString(R.string.pref_camera_whitebalance_default);
922         String[] values = context.getResources().getStringArray(
923                 R.array.pref_camera_whitebalance_entryvalues);
924         return new Setting(SOURCE_CAMERA, TYPE_STRING, defaultValue, KEY_WHITE_BALANCE,
925                 values, FLUSH_OFF);
926     }
927
928     public static Setting getHdrSetting(Context context) {
929         String defaultValue = context.getString(R.string.pref_camera_hdr_default);
930         String[] values = context.getResources().getStringArray(
931                 R.array.pref_camera_hdr_entryvalues);
932         return new Setting(SOURCE_GLOBAL, TYPE_STRING, defaultValue, KEY_CAMERA_HDR,
933                 values, FLUSH_OFF);
934     }
935
936     public static Setting getHdrPlusSetting(Context context) {
937         String defaultValue = context.getString(R.string.pref_camera_hdr_plus_default);
938         String[] values = context.getResources().getStringArray(
939                 R.array.pref_camera_hdr_plus_entryvalues);
940         return new Setting(SOURCE_GLOBAL, TYPE_STRING, defaultValue, KEY_CAMERA_HDR_PLUS,
941                 values, FLUSH_OFF);
942     }
943
944     public static Setting getSceneModeSetting(Context context) {
945         String defaultValue = context.getString(R.string.pref_camera_scenemode_default);
946         String[] values = context.getResources().getStringArray(
947                 R.array.pref_camera_scenemode_entryvalues);
948         return new Setting(SOURCE_CAMERA, TYPE_STRING, defaultValue, KEY_SCENE_MODE,
949                 values, FLUSH_OFF);
950     }
951
952     public static Setting getFlashSetting(Context context) {
953         String defaultValue = context.getString(R.string.pref_camera_flashmode_default);
954         String[] values = context.getResources().getStringArray(
955                 R.array.pref_camera_flashmode_entryvalues);
956         return new Setting(SOURCE_CAMERA, TYPE_STRING, defaultValue, KEY_FLASH_MODE,
957                 values, FLUSH_OFF);
958     }
959
960     public static Setting getExposureSetting(Context context,
961             SettingsCapabilities capabilities) {
962         String defaultValue = context.getString(R.string.pref_exposure_default);
963         String[] values = null;
964         if (capabilities != null) {
965             values = capabilities.getSupportedExposureValues();
966         }
967         return new Setting(SOURCE_CAMERA, TYPE_STRING, defaultValue, KEY_EXPOSURE,
968                 values, FLUSH_ON);
969     }
970
971     public static Setting getHintSetting(Context context) {
972         String defaultValue = context.getString(R.string.setting_on_value);
973         String[] values = null;
974         return new Setting(SOURCE_GLOBAL, TYPE_BOOLEAN, defaultValue,
975                 KEY_CAMERA_FIRST_USE_HINT_SHOWN, values, FLUSH_OFF);
976     }
977
978     public static Setting getFocusModeSetting(Context context) {
979         String defaultValue = null;
980         String[] values = context.getResources().getStringArray(
981                 R.array.pref_camera_focusmode_entryvalues);
982         return new Setting(SOURCE_CAMERA, TYPE_STRING, defaultValue, KEY_FOCUS_MODE,
983                 values, FLUSH_OFF);
984     }
985
986     public static Setting getTimerSetting(Context context) {
987         String defaultValue = context.getString(R.string.pref_camera_timer_default);
988         String[] values = null; // TODO: get the values dynamically.
989         return new Setting(SOURCE_GLOBAL, TYPE_STRING, defaultValue, KEY_TIMER,
990                 values, FLUSH_OFF);
991     }
992
993     public static Setting getTimerSoundSetting(Context context) {
994         String defaultValue = context.getString(R.string.pref_camera_timer_sound_default);
995         String[] values = context.getResources().getStringArray(
996                 R.array.pref_camera_timer_sound_entryvalues);
997         return new Setting(SOURCE_GLOBAL, TYPE_STRING, defaultValue, KEY_TIMER_SOUND_EFFECTS,
998                 values, FLUSH_OFF);
999     }
1000
1001     public static Setting getVideoQualitySetting(Context context) {
1002         String defaultValue = context.getString(R.string.pref_video_quality_default);
1003         String[] values = context.getResources().getStringArray(
1004                 R.array.pref_video_quality_entryvalues);
1005         return new Setting(SOURCE_DEFAULT, TYPE_STRING, defaultValue, KEY_VIDEO_QUALITY,
1006                 values, FLUSH_OFF);
1007     }
1008
1009     public static Setting getTimeLapseFrameIntervalSetting(Context context) {
1010         String defaultValue = context.getString(
1011                 R.string.pref_video_time_lapse_frame_interval_default);
1012         String[] values = context.getResources().getStringArray(
1013                 R.array.pref_video_time_lapse_frame_interval_entryvalues);
1014         return new Setting(SOURCE_GLOBAL, TYPE_STRING, defaultValue,
1015                 KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL, values, FLUSH_OFF);
1016     }
1017
1018     public static Setting getJpegQualitySetting(Context context) {
1019         String defaultValue = context.getString(
1020                 R.string.pref_camera_jpeg_quality_normal);
1021         String[] values = context.getResources().getStringArray(
1022                 R.array.pref_camera_jpeg_quality_entryvalues);
1023         return new Setting(SOURCE_CAMERA, TYPE_STRING, defaultValue, KEY_JPEG_QUALITY,
1024                 values, FLUSH_OFF);
1025     }
1026
1027     public static Setting getVideoFlashSetting(Context context) {
1028         String defaultValue = context.getString(R.string.pref_camera_video_flashmode_default);
1029         String[] values = context.getResources().getStringArray(
1030                 R.array.pref_camera_video_flashmode_entryvalues);
1031         return new Setting(SOURCE_CAMERA, TYPE_STRING, defaultValue,
1032                 KEY_VIDEOCAMERA_FLASH_MODE, values, FLUSH_OFF);
1033     }
1034
1035     public static Setting getVideoEffectSetting(Context context) {
1036         String defaultValue = context.getString(R.string.pref_video_effect_default);
1037         String[] values = context.getResources().getStringArray(
1038                 R.array.pref_video_effect_entryvalues);
1039         return new Setting(SOURCE_GLOBAL, TYPE_STRING, defaultValue, KEY_VIDEO_EFFECT,
1040                 values, FLUSH_OFF);
1041     }
1042
1043     public static Setting getHintVideoSetting(Context context) {
1044         String defaultValue = context.getString(R.string.setting_on_value);
1045         String[] values = null;
1046         return new Setting(SOURCE_GLOBAL, TYPE_STRING, defaultValue,
1047                 KEY_VIDEO_FIRST_USE_HINT_SHOWN, values, FLUSH_OFF);
1048     }
1049
1050     public static Setting getStartupModuleSetting(Context context) {
1051         String defaultValue = context.getString(R.string.pref_camera_startup_index_default);
1052         String[] values = null;
1053         return new Setting(SOURCE_DEFAULT, TYPE_INTEGER, defaultValue,
1054                 KEY_STARTUP_MODULE_INDEX, values, FLUSH_OFF);
1055     }
1056
1057     public static Setting getShimmyRemainingTimesSetting(Context context) {
1058         String defaultValue = context.getString(R.string.pref_shimmy_play_times);
1059         return new Setting(SOURCE_DEFAULT, TYPE_INTEGER, defaultValue,
1060                 KEY_SHIMMY_REMAINING_PLAY_TIMES, null, FLUSH_OFF);
1061     }
1062
1063     public static Setting getLastUsedCameraModule(Context context) {
1064         String defaultValue = Integer.toString(context.getResources()
1065                 .getInteger(R.integer.camera_mode_photo));
1066         return new Setting(SOURCE_DEFAULT, TYPE_INTEGER, defaultValue,
1067                 KEY_CAMERA_MODULE_LAST_USED, null, FLUSH_OFF);
1068     }
1069
1070     public static Setting getPanoOrientationSetting(Context context) {
1071         String defaultValue = context.getString(R.string.pano_orientation_horizontal);
1072         String[] values = context.getResources().getStringArray(
1073                 R.array.pref_camera_pano_orientation_entryvalues);
1074         return new Setting(SOURCE_GLOBAL, TYPE_STRING, defaultValue,
1075                 KEY_CAMERA_PANO_ORIENTATION, values, FLUSH_OFF);
1076     }
1077
1078     public static Setting getGridLinesSetting(Context context) {
1079         String defaultValue = context.getString(R.string.setting_off_value);
1080         String[] values = context.getResources().getStringArray(
1081                 R.array.pref_camera_gridlines_entryvalues);
1082         return new Setting(SOURCE_GLOBAL, TYPE_STRING, defaultValue,
1083                 KEY_CAMERA_GRID_LINES, values, FLUSH_OFF);
1084     }
1085
1086     public static Setting getReleaseDialogLastShownVersionSetting(Context context) {
1087         return new Setting(SOURCE_DEFAULT, TYPE_STRING, null,
1088                 KEY_RELEASE_DIALOG_LAST_SHOWN_VERSION, null, FLUSH_OFF);
1089     }
1090
1091     public static Setting getFlashSupportedBackCameraSetting(Context context) {
1092         String defaultValue = context.getString(R.string.setting_none_value);
1093         return new Setting(SOURCE_GLOBAL, TYPE_BOOLEAN, defaultValue,
1094                 KEY_FLASH_SUPPORTED_BACK_CAMERA, null, FLUSH_OFF);
1095     }
1096
1097     public static Setting getStrictUpgradeVersionSetting(Context context) {
1098         String defaultValue = "0";
1099         return new Setting(SOURCE_DEFAULT, TYPE_INTEGER, defaultValue,
1100                 KEY_STRICT_UPGRADE_VERSION, null, FLUSH_OFF);
1101     }
1102
1103     public static Setting getPeekAnimRemainingTimesSetting(Context context) {
1104         String defaultValue = context.getString(R.string.pref_filmstrip_peek_anim_play_times);
1105         return new Setting(SOURCE_DEFAULT, TYPE_INTEGER, defaultValue,
1106                 KEY_FILMSTRIP_PEEK_ANIM_REMAINING_PLAY_TIMES, null, FLUSH_OFF);
1107     }
1108
1109     public static Setting getRequestReturnHdrPlusSetting(Context context) {
1110         String defaultValue = context.getString(R.string.setting_none_value);
1111         return new Setting(SOURCE_MODULE, TYPE_BOOLEAN, VALUE_OFF,
1112                 KEY_REQUEST_RETURN_HDR_PLUS, null, FLUSH_OFF);
1113     }
1114
1115     // Utilities.
1116
1117     /**
1118      * Returns whether the camera has been set to back facing in settings.
1119      */
1120     public boolean isCameraBackFacing() {
1121         String cameraFacing = get(SETTING_CAMERA_ID);
1122         String backFacing = mContext.getString(R.string.pref_camera_id_default);
1123         return (Integer.parseInt(cameraFacing) == Integer.parseInt(backFacing));
1124     }
1125
1126     /**
1127      * Returns whether hdr plus mode is set on.
1128      */
1129     public boolean isHdrPlusOn() {
1130         String hdrOn = get(SettingsManager.SETTING_CAMERA_HDR);
1131         return hdrOn.equals(SettingsManager.VALUE_ON);
1132     }
1133
1134     /**
1135      * Returns whether the app should return to hdr plus mode if possible.
1136      */
1137     public boolean requestsReturnToHdrPlus() {
1138         return getBoolean(SettingsManager.SETTING_REQUEST_RETURN_HDR_PLUS);
1139     }
1140
1141     /**
1142      * Returns whether grid lines are set on.
1143      */
1144     public boolean areGridLinesOn() {
1145         String gridLinesOn = get(SettingsManager.SETTING_CAMERA_GRID_LINES);
1146         return gridLinesOn.equals(SettingsManager.VALUE_ON);
1147     }
1148
1149     /**
1150      * Returns whether pano orientation is horizontal.
1151      */
1152     public boolean isPanoOrientationHorizontal() {
1153         String orientation = get(SettingsManager.SETTING_CAMERA_PANO_ORIENTATION);
1154         String horizontal = mContext.getString(R.string.pano_orientation_horizontal);
1155         return orientation.equals(horizontal);
1156     }
1157
1158     // TODO: refactor this into a separate utils module.
1159
1160     /**
1161      * Get a String value from first the ListPreference, and if not found from
1162      * the SettingsManager. This is a wrapper that adds backwards compatibility
1163      * to views that rely on PreferenceGroups.
1164      */
1165     public String getValueFromPreference(ListPreference pref) {
1166         String value = pref.getValue();
1167         if (value == null) {
1168             Integer id = mSettingsCache.getId(pref.getKey());
1169             if (id == null) {
1170                 return null;
1171             }
1172             value = get(id);
1173         }
1174         return value;
1175     }
1176
1177     /**
1178      * Set a String value first from the ListPreference, and if unable from the
1179      * SettingsManager. This is a wrapper that adds backwards compatibility to
1180      * views that rely on PreferenceGroups.
1181      */
1182     public void setValueFromPreference(ListPreference pref, String value) {
1183         boolean set = pref.setValue(value);
1184         if (!set) {
1185             Integer id = mSettingsCache.getId(pref.getKey());
1186             if (id != null) {
1187                 set(id, value);
1188             }
1189         }
1190     }
1191
1192     /**
1193      * Set a String value first from the ListPreference based on a
1194      * ListPreference index, and if unable use the ListPreference key to set the
1195      * value using the SettingsManager. This is a wrapper that adds backwards
1196      * compatibility to views that rely on PreferenceGroups.
1197      */
1198     public void setValueIndexFromPreference(ListPreference pref, int index) {
1199         boolean set = pref.setValueIndex(index);
1200         if (!set) {
1201             Integer id = mSettingsCache.getId(pref.getKey());
1202             if (id != null) {
1203                 String value = pref.getValueAtIndex(index);
1204                 set(id, value);
1205             }
1206         }
1207     }
1208
1209     /**
1210      * Sets the settings for whether location recording should be enabled or
1211      * not. Also makes sure to pass on the change to the location manager.
1212      */
1213     public void setLocation(boolean on, LocationManager locationManager) {
1214         setBoolean(SettingsManager.SETTING_RECORD_LOCATION, on);
1215         locationManager.recordLocation(on);
1216     }
1217
1218     /**
1219      * Reads the current location recording settings and passes it on to the
1220      * given location manager.
1221      */
1222     public void syncLocationManager(LocationManager locationManager) {
1223         boolean value = getBoolean(SettingsManager.SETTING_RECORD_LOCATION);
1224         locationManager.recordLocation(value);
1225     }
1226 }