1 /*
2  * Copyright (C) 2014 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 
22 import com.android.camera.app.AppController;
23 import com.android.camera.app.ModuleManagerImpl;
24 import com.android.camera.debug.Log;
25 import com.android.camera.util.ApiHelper;
26 import com.android.camera.util.Size;
27 import com.android.camera2.R;
28 import com.android.ex.camera2.portability.CameraAgentFactory;
29 import com.android.ex.camera2.portability.CameraDeviceInfo;
30 
31 import java.util.List;
32 import java.util.Map;
33 
34 /**
35  * Defines the general upgrade path for the app. Modules may define specific
36  * upgrade logic, but upgrading for preferences across modules, CameraActivity
37  * or application-wide can be added here.
38  */
39 public class AppUpgrader extends SettingsUpgrader {
40     private static final Log.Tag TAG = new Log.Tag("AppUpgrader");
41 
42     private static final String OLD_CAMERA_PREFERENCES_PREFIX = "_preferences_";
43     private static final String OLD_MODULE_PREFERENCES_PREFIX = "_preferences_module_";
44     private static final String OLD_GLOBAL_PREFERENCES_FILENAME = "_preferences_camera";
45     private static final String OLD_KEY_UPGRADE_VERSION = "pref_strict_upgrade_version";
46 
47     /**
48      * With this version everyone was forced to choose their location settings
49      * again.
50      */
51     private static final int FORCE_LOCATION_CHOICE_VERSION = 2;
52 
53     /**
54      * With this version, the camera size setting changed from a "small",
55      * "medium" and "default" to strings representing the actual resolutions,
56      * i.e. "1080x1776".
57      */
58     private static final int CAMERA_SIZE_SETTING_UPGRADE_VERSION = 3;
59 
60     /**
61      * With this version, the names of the files storing camera specific and
62      * module specific settings changed.
63      * <p>
64      * NOTE: changed this from 4 to 6 to re-run on latest Glacier upgrade.
65      * Initial upgraders to Glacier will run conversion once as of the change.
66      * When re-run for early dogfooders, values will get overwritten but will
67      * all work.
68      */
69     private static final int CAMERA_MODULE_SETTINGS_FILES_RENAMED_VERSION = 6;
70 
71     /**
72      * With this version, timelapse mode was removed and mode indices need to be
73      * resequenced.
74      */
75     private static final int CAMERA_SETTINGS_SELECTED_MODULE_INDEX = 5;
76 
77     /**
78      * With this version internal storage is changed to use only Strings, and
79      * a type conversion process should execute.
80      */
81     private static final int CAMERA_SETTINGS_STRINGS_UPGRADE = 5;
82 
83     /**
84      * With this version we needed to convert the artificial 16:9 high
85      * resolution size on the N5 since we stored it with a swapped width/height.
86      */
87     public static final int NEEDS_N5_16by9_RESOLUTION_SWAP = 7;
88     /**
89      * Increment this value whenever new AOSP UpgradeSteps need to be executed.
90      */
91     public static final int APP_UPGRADE_VERSION = 7;
92 
93     private final AppController mAppController;
94 
AppUpgrader(final AppController appController)95     public AppUpgrader(final AppController appController) {
96         super(Keys.KEY_UPGRADE_VERSION, APP_UPGRADE_VERSION);
97         mAppController = appController;
98     }
99 
100     @Override
getLastVersion(SettingsManager settingsManager)101     protected int getLastVersion(SettingsManager settingsManager) {
102         // Prior upgrade versions were stored in the default preferences as int
103         // and String. We create a new version location for migration to String.
104         // If we don't have a version persisted in the new location, check for
105         // the prior value from the old location. We expect the old value to be
106         // processed during {@link #upgradeTypesToStrings}.
107         SharedPreferences defaultPreferences = settingsManager.getDefaultPreferences();
108         if (defaultPreferences.contains(OLD_KEY_UPGRADE_VERSION)) {
109             Map<String, ?> allPrefs = defaultPreferences.getAll();
110             Object oldVersion = allPrefs.get(OLD_KEY_UPGRADE_VERSION);
111             defaultPreferences.edit().remove(OLD_KEY_UPGRADE_VERSION).apply();
112             if (oldVersion instanceof Integer) {
113                 return (Integer) oldVersion;
114             } else if (oldVersion instanceof String) {
115                 return SettingsManager.convertToInt((String) oldVersion);
116             }
117         }
118         return super.getLastVersion(settingsManager);
119     }
120 
121     @Override
upgrade(SettingsManager settingsManager, int lastVersion, int currentVersion)122     public void upgrade(SettingsManager settingsManager, int lastVersion, int currentVersion) {
123         Context context = mAppController.getAndroidContext();
124 
125         // Do strings upgrade first before 'earlier' upgrades, since they assume
126         // valid storage of values.
127         if (lastVersion < CAMERA_SETTINGS_STRINGS_UPGRADE) {
128             upgradeTypesToStrings(settingsManager);
129         }
130 
131         if (lastVersion < FORCE_LOCATION_CHOICE_VERSION) {
132             forceLocationChoice(settingsManager);
133         }
134 
135         if (lastVersion < CAMERA_SIZE_SETTING_UPGRADE_VERSION) {
136             CameraDeviceInfo infos = CameraAgentFactory
137                     .getAndroidCameraAgent(context, CameraAgentFactory.CameraApi.API_1)
138                     .getCameraDeviceInfo();
139             upgradeCameraSizeSetting(settingsManager, context, infos,
140                     SettingsUtil.CAMERA_FACING_FRONT);
141             upgradeCameraSizeSetting(settingsManager, context, infos,
142                     SettingsUtil.CAMERA_FACING_BACK);
143             // We changed size handling and aspect ratio placement, put user
144             // back into Camera mode this time to ensure they see the ratio
145             // chooser if applicable.
146             settingsManager.remove(SettingsManager.SCOPE_GLOBAL,
147                     Keys.KEY_STARTUP_MODULE_INDEX);
148             CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
149         }
150 
151         if (lastVersion < CAMERA_MODULE_SETTINGS_FILES_RENAMED_VERSION) {
152             upgradeCameraSettingsFiles(settingsManager, context);
153             upgradeModuleSettingsFiles(settingsManager, context,
154                     mAppController);
155         }
156 
157         if (lastVersion < CAMERA_SETTINGS_SELECTED_MODULE_INDEX) {
158             upgradeSelectedModeIndex(settingsManager, context);
159         }
160 
161         if (lastVersion < NEEDS_N5_16by9_RESOLUTION_SWAP) {
162             updateN516by9ResolutionIfNeeded(settingsManager);
163         }
164     }
165 
166     /**
167      * Converts settings that were stored in SharedPreferences as non-Strings,
168      * to Strings. This is necessary due to a SettingsManager API refactoring.
169      * Should only be executed if we detected a change in
170      * Keys.KEY_UPGRADE_VERSION type from int to string; rerunning this on
171      * string values will result in ClassCastExceptions when trying to retrieve
172      * an int or boolean as a String.
173      */
upgradeTypesToStrings(SettingsManager settingsManager)174     private void upgradeTypesToStrings(SettingsManager settingsManager) {
175         SharedPreferences defaultPreferences = settingsManager.getDefaultPreferences();
176         SharedPreferences oldGlobalPreferences =
177                 settingsManager.openPreferences(OLD_GLOBAL_PREFERENCES_FILENAME);
178 
179         // Location: boolean -> String, from default.
180         if (defaultPreferences.contains(Keys.KEY_RECORD_LOCATION)) {
181             boolean location = removeBoolean(defaultPreferences, Keys.KEY_RECORD_LOCATION);
182             settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION, location);
183         }
184 
185         // User selected aspect ratio: boolean -> String, from default.
186         if (defaultPreferences.contains(Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
187             boolean userSelectedAspectRatio = removeBoolean(defaultPreferences,
188                     Keys.KEY_USER_SELECTED_ASPECT_RATIO);
189             settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_USER_SELECTED_ASPECT_RATIO,
190                     userSelectedAspectRatio);
191         }
192 
193         // Manual exposure compensation: boolean -> String, from default.
194         if (defaultPreferences.contains(Keys.KEY_EXPOSURE_COMPENSATION_ENABLED)) {
195             boolean manualExposureCompensationEnabled = removeBoolean(defaultPreferences,
196                     Keys.KEY_EXPOSURE_COMPENSATION_ENABLED);
197             settingsManager.set(SettingsManager.SCOPE_GLOBAL,
198                     Keys.KEY_EXPOSURE_COMPENSATION_ENABLED, manualExposureCompensationEnabled);
199         }
200 
201         // Hint: boolean -> String, from default.
202         if (defaultPreferences.contains(Keys.KEY_CAMERA_FIRST_USE_HINT_SHOWN)) {
203             boolean hint = removeBoolean(defaultPreferences, Keys.KEY_CAMERA_FIRST_USE_HINT_SHOWN);
204             settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_FIRST_USE_HINT_SHOWN,
205                     hint);
206         }
207 
208         // Startup module index: Integer -> String, from default.
209         if (defaultPreferences.contains(Keys.KEY_STARTUP_MODULE_INDEX)) {
210             int startupModuleIndex = removeInteger(defaultPreferences,
211                     Keys.KEY_STARTUP_MODULE_INDEX);
212             settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_STARTUP_MODULE_INDEX,
213                     startupModuleIndex);
214         }
215 
216         // Last camera used module index: Integer -> String, from default.
217         if (defaultPreferences.contains(Keys.KEY_CAMERA_MODULE_LAST_USED)) {
218             int lastCameraUsedModuleIndex = removeInteger(defaultPreferences,
219                     Keys.KEY_CAMERA_MODULE_LAST_USED);
220             settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_MODULE_LAST_USED,
221                     lastCameraUsedModuleIndex);
222         }
223 
224         // Flash supported back camera setting: boolean -> String, from old
225         // global.
226         if (oldGlobalPreferences.contains(Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA)) {
227             boolean flashSupportedBackCamera = removeBoolean(oldGlobalPreferences,
228                     Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA);
229             if (flashSupportedBackCamera) {
230                 settingsManager.set(SettingsManager.SCOPE_GLOBAL,
231                         Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA, flashSupportedBackCamera);
232             }
233         }
234 
235         // Should show refocus viewer cling: boolean -> String, from default.
236         if (defaultPreferences.contains(Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
237             boolean shouldShowRefocusViewer = removeBoolean(defaultPreferences,
238                     Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING);
239             settingsManager.set(SettingsManager.SCOPE_GLOBAL,
240                     Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING, shouldShowRefocusViewer);
241         }
242 
243         // Should show settings button cling: boolean -> String, from default.
244         if (defaultPreferences.contains(Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING)) {
245             boolean shouldShowSettingsButtonCling = removeBoolean(defaultPreferences,
246                     Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING);
247             settingsManager.set(SettingsManager.SCOPE_GLOBAL,
248                     Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING, shouldShowSettingsButtonCling);
249         }
250 
251         // HDR plus on setting: String on/off -> String, from old global.
252         if (oldGlobalPreferences.contains(Keys.KEY_CAMERA_HDR_PLUS)) {
253             String hdrPlus = removeString(oldGlobalPreferences, Keys.KEY_CAMERA_HDR_PLUS);
254             if (OLD_SETTINGS_VALUE_ON.equals(hdrPlus)) {
255                 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, true);
256             }
257         }
258 
259         // HDR on setting: String on/off -> String, from old global.
260         if (oldGlobalPreferences.contains(Keys.KEY_CAMERA_HDR)) {
261             String hdrPlus = removeString(oldGlobalPreferences, Keys.KEY_CAMERA_HDR);
262             if (OLD_SETTINGS_VALUE_ON.equals(hdrPlus)) {
263                 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR, true);
264             }
265         }
266 
267         // Grid on setting: String on/off -> String, from old global.
268         if (oldGlobalPreferences.contains(Keys.KEY_CAMERA_GRID_LINES)) {
269             String hdrPlus = removeString(oldGlobalPreferences, Keys.KEY_CAMERA_GRID_LINES);
270             if (OLD_SETTINGS_VALUE_ON.equals(hdrPlus)) {
271                 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_GRID_LINES,
272                         true);
273             }
274         }
275     }
276 
277     /**
278      * Part of the AOSP upgrade path, forces the user to choose their location
279      * again if it was originally set to false.
280      */
forceLocationChoice(SettingsManager settingsManager)281     private void forceLocationChoice(SettingsManager settingsManager) {
282         SharedPreferences oldGlobalPreferences =
283                 settingsManager.openPreferences(OLD_GLOBAL_PREFERENCES_FILENAME);
284        // Show the location dialog on upgrade if
285         // (a) the user has never set this option (status quo).
286         // (b) the user opt'ed out previously.
287         if (settingsManager.isSet(SettingsManager.SCOPE_GLOBAL,
288                 Keys.KEY_RECORD_LOCATION)) {
289             // Location is set in the source file defined for this setting.
290             // Remove the setting if the value is false to launch the dialog.
291             if (!settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
292                     Keys.KEY_RECORD_LOCATION)) {
293                 settingsManager.remove(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION);
294             }
295         } else if (oldGlobalPreferences.contains(Keys.KEY_RECORD_LOCATION)) {
296             // Location is not set, check to see if we're upgrading from
297             // a different source file.
298             String location = removeString(oldGlobalPreferences, Keys.KEY_RECORD_LOCATION);
299             if (OLD_SETTINGS_VALUE_ON.equals(location)) {
300                     settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION,
301                             true);
302             }
303         }
304     }
305 
306     /**
307      * Part of the AOSP upgrade path, sets front and back picture sizes.
308      */
upgradeCameraSizeSetting(SettingsManager settingsManager, Context context, CameraDeviceInfo infos, SettingsUtil.CameraDeviceSelector facing)309     private void upgradeCameraSizeSetting(SettingsManager settingsManager,
310             Context context, CameraDeviceInfo infos,
311             SettingsUtil.CameraDeviceSelector facing) {
312         String key;
313         if (facing == SettingsUtil.CAMERA_FACING_FRONT) {
314             key = Keys.KEY_PICTURE_SIZE_FRONT;
315         } else if (facing == SettingsUtil.CAMERA_FACING_BACK) {
316             key = Keys.KEY_PICTURE_SIZE_BACK;
317         } else {
318             Log.w(TAG, "Ignoring attempt to upgrade size of unhandled camera facing direction");
319             return;
320         }
321 
322         // infos might be null if the underlying camera device is broken. In
323         // that case, just delete the old settings and force the user to
324         // reselect, it's the least evil solution given we want to only upgrade
325         // settings once.
326         if (infos == null) {
327             settingsManager.remove(SettingsManager.SCOPE_GLOBAL, key);
328             return;
329         }
330 
331         String pictureSize = settingsManager.getString(SettingsManager.SCOPE_GLOBAL, key);
332         int camera = SettingsUtil.getCameraId(infos, facing);
333         if (camera != -1) {
334             List<Size> supported = CameraPictureSizesCacher.getSizesForCamera(camera, context);
335             if (supported != null) {
336                 Size size = SettingsUtil.getPhotoSize(pictureSize, supported, camera);
337                 settingsManager.set(SettingsManager.SCOPE_GLOBAL, key,
338                         SettingsUtil.sizeToSettingString(size));
339             }
340         }
341     }
342 
343     /**
344      * Part of the AOSP upgrade path, copies all of the keys and values in a
345      * SharedPreferences file to another SharedPreferences file, as Strings.
346      * Settings that are not a known supported format (int/boolean/String)
347      * are dropped with warning.
348      *
349      * This will normally be run only once but was used both for upgrade version
350      * 4 and 6 -- in 6 we repair issues with previous runs of the upgrader. So
351      * we make sure to remove entries from destination if the source isn't valid
352      * like a null or unsupported type.
353      */
copyPreferences(SharedPreferences oldPrefs, SharedPreferences newPrefs)354     private void copyPreferences(SharedPreferences oldPrefs,
355             SharedPreferences newPrefs) {
356         Map<String, ?> entries = oldPrefs.getAll();
357         for (Map.Entry<String, ?> entry : entries.entrySet()) {
358             String key = entry.getKey();
359             Object value = entry.getValue();
360             if (value == null) {
361                 Log.w(TAG, "skipped upgrade and removing entry for null key " + key);
362                 newPrefs.edit().remove(key).apply();
363             } else if (value instanceof Boolean) {
364                 String boolValue = SettingsManager.convert((Boolean) value);
365                 newPrefs.edit().putString(key, boolValue).apply();
366             } else if (value instanceof Integer) {
367                 String intValue = SettingsManager.convert((Integer) value);
368                 newPrefs.edit().putString(key, intValue).apply();
369             } else if (value instanceof Long){
370                 // New SettingsManager only supports int values. Attempt to
371                 // recover any longs which happen to be present if they are
372                 // within int range.
373                 long longValue = (Long) value;
374                 if (longValue <= Integer.MAX_VALUE && longValue >= Integer.MIN_VALUE) {
375                     String intValue = SettingsManager.convert((int) longValue);
376                     newPrefs.edit().putString(key, intValue).apply();
377                 } else {
378                     Log.w(TAG, "skipped upgrade for out of bounds long key " +
379                             key + " : " + longValue);
380                 }
381             } else if (value instanceof String){
382                 newPrefs.edit().putString(key, (String) value).apply();
383             } else {
384                 Log.w(TAG,"skipped upgrade and removing entry for unrecognized "
385                         + "key type " + key + " : " + value.getClass());
386                 newPrefs.edit().remove(key).apply();
387             }
388         }
389     }
390 
391     /**
392      * Part of the AOSP upgrade path, copies all of the key and values in the
393      * old camera SharedPreferences files to new files.
394      */
upgradeCameraSettingsFiles(SettingsManager settingsManager, Context context)395     private void upgradeCameraSettingsFiles(SettingsManager settingsManager,
396             Context context) {
397         String[] cameraIds =
398                 context.getResources().getStringArray(R.array.camera_id_entryvalues);
399 
400         for (int i = 0; i < cameraIds.length; i++) {
401             SharedPreferences oldCameraPreferences =
402                     settingsManager.openPreferences(
403                             OLD_CAMERA_PREFERENCES_PREFIX + cameraIds[i]);
404             SharedPreferences newCameraPreferences =
405                     settingsManager.openPreferences(
406                             SettingsManager.getCameraSettingScope(cameraIds[i]));
407 
408             copyPreferences(oldCameraPreferences, newCameraPreferences);
409         }
410     }
411 
upgradeModuleSettingsFiles(SettingsManager settingsManager, Context context, AppController app)412     private void upgradeModuleSettingsFiles(SettingsManager settingsManager,
413             Context context, AppController app) {
414         int[] moduleIds = context.getResources().getIntArray(R.array.camera_modes);
415 
416         for (int i = 0; i < moduleIds.length; i++) {
417             String moduleId = Integer.toString(moduleIds[i]);
418             SharedPreferences oldModulePreferences =
419                     settingsManager.openPreferences(
420                             OLD_MODULE_PREFERENCES_PREFIX + moduleId);
421 
422             if (oldModulePreferences != null && oldModulePreferences.getAll().size() > 0) {
423                 ModuleManagerImpl.ModuleAgent agent =
424                         app.getModuleManager().getModuleAgent(moduleIds[i]);
425                 if (agent != null) {
426                     SharedPreferences newModulePreferences = settingsManager.openPreferences(
427                             SettingsManager.getModuleSettingScope(agent.getScopeNamespace()));
428 
429                     copyPreferences(oldModulePreferences, newModulePreferences);
430                 }
431             }
432         }
433     }
434 
435     /**
436      * The R.integer.camera_mode_* indices were cleaned up, resulting in
437      * removals and renaming of certain values. In particular camera_mode_gcam
438      * is now 5, not 6. We modify any persisted user settings that may refer to
439      * the old value.
440      */
upgradeSelectedModeIndex(SettingsManager settingsManager, Context context)441     private void upgradeSelectedModeIndex(SettingsManager settingsManager, Context context) {
442         int oldGcamIndex = 6; // from hardcoded previous mode index resource
443         int gcamIndex = context.getResources().getInteger(R.integer.camera_mode_gcam);
444 
445         int lastUsedCameraIndex = settingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
446                 Keys.KEY_CAMERA_MODULE_LAST_USED);
447         if (lastUsedCameraIndex == oldGcamIndex) {
448             settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_MODULE_LAST_USED,
449                     gcamIndex);
450         }
451 
452         int startupModuleIndex = settingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
453                 Keys.KEY_STARTUP_MODULE_INDEX);
454         if (startupModuleIndex == oldGcamIndex) {
455             settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_STARTUP_MODULE_INDEX,
456                     gcamIndex);
457         }
458     }
459 
460     /**
461      * A targeted fix for b/19693226.
462      * <p>
463      * Since the N5 doesn't natively support a high resolution 16:9 size we need
464      * to artificially add it and then crop the result from the high-resolution
465      * 4:3 size. In version 2.4 we unfortunately swapped the dimensions of
466      * ResolutionUtil#NEXUS_5_LARGE_16_BY_9_SIZE, which now causes a few issues
467      * in 2.5. If we detect this case, we will swap the dimensions here to make
468      * sure they are the right way around going forward.
469      */
updateN516by9ResolutionIfNeeded(SettingsManager settingsManager)470     private void updateN516by9ResolutionIfNeeded(SettingsManager settingsManager) {
471         if (!ApiHelper.IS_NEXUS_5) {
472             return;
473         }
474 
475         String pictureSize = settingsManager.getString(SettingsManager.SCOPE_GLOBAL,
476                 Keys.KEY_PICTURE_SIZE_BACK);
477         if ("1836x3264".equals(pictureSize)) {
478             Log.i(TAG, "Swapped dimensions on N5 16:9 resolution.");
479             settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_PICTURE_SIZE_BACK,
480                     "3264x1836");
481         }
482     }
483 }
484