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
17package com.android.camera.settings;
18
19import android.content.Context;
20import android.content.SharedPreferences;
21
22import com.android.camera.app.AppController;
23import com.android.camera.app.ModuleManagerImpl;
24import com.android.camera.debug.Log;
25import com.android.camera.util.ApiHelper;
26import com.android.camera.util.Size;
27import com.android.camera2.R;
28import com.android.ex.camera2.portability.CameraAgentFactory;
29import com.android.ex.camera2.portability.CameraDeviceInfo;
30
31import java.util.List;
32import 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 */
39public 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
95    public AppUpgrader(final AppController appController) {
96        super(Keys.KEY_UPGRADE_VERSION, APP_UPGRADE_VERSION);
97        mAppController = appController;
98    }
99
100    @Override
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
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        }
149
150        if (lastVersion < CAMERA_MODULE_SETTINGS_FILES_RENAMED_VERSION) {
151            upgradeCameraSettingsFiles(settingsManager, context);
152            upgradeModuleSettingsFiles(settingsManager, context,
153                    mAppController);
154        }
155
156        if (lastVersion < CAMERA_SETTINGS_SELECTED_MODULE_INDEX) {
157            upgradeSelectedModeIndex(settingsManager, context);
158        }
159
160        if (lastVersion < NEEDS_N5_16by9_RESOLUTION_SWAP) {
161            updateN516by9ResolutionIfNeeded(settingsManager);
162        }
163    }
164
165    /**
166     * Converts settings that were stored in SharedPreferences as non-Strings,
167     * to Strings. This is necessary due to a SettingsManager API refactoring.
168     * Should only be executed if we detected a change in
169     * Keys.KEY_UPGRADE_VERSION type from int to string; rerunning this on
170     * string values will result in ClassCastExceptions when trying to retrieve
171     * an int or boolean as a String.
172     */
173    private void upgradeTypesToStrings(SettingsManager settingsManager) {
174        SharedPreferences defaultPreferences = settingsManager.getDefaultPreferences();
175        SharedPreferences oldGlobalPreferences =
176                settingsManager.openPreferences(OLD_GLOBAL_PREFERENCES_FILENAME);
177
178        // Location: boolean -> String, from default.
179        if (defaultPreferences.contains(Keys.KEY_RECORD_LOCATION)) {
180            boolean location = removeBoolean(defaultPreferences, Keys.KEY_RECORD_LOCATION);
181            settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION, location);
182        }
183
184        // User selected aspect ratio: boolean -> String, from default.
185        if (defaultPreferences.contains(Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
186            boolean userSelectedAspectRatio = removeBoolean(defaultPreferences,
187                    Keys.KEY_USER_SELECTED_ASPECT_RATIO);
188            settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_USER_SELECTED_ASPECT_RATIO,
189                    userSelectedAspectRatio);
190        }
191
192        // Manual exposure compensation: boolean -> String, from default.
193        if (defaultPreferences.contains(Keys.KEY_EXPOSURE_COMPENSATION_ENABLED)) {
194            boolean manualExposureCompensationEnabled = removeBoolean(defaultPreferences,
195                    Keys.KEY_EXPOSURE_COMPENSATION_ENABLED);
196            settingsManager.set(SettingsManager.SCOPE_GLOBAL,
197                    Keys.KEY_EXPOSURE_COMPENSATION_ENABLED, manualExposureCompensationEnabled);
198        }
199
200        // Hint: boolean -> String, from default.
201        if (defaultPreferences.contains(Keys.KEY_CAMERA_FIRST_USE_HINT_SHOWN)) {
202            boolean hint = removeBoolean(defaultPreferences, Keys.KEY_CAMERA_FIRST_USE_HINT_SHOWN);
203            settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_FIRST_USE_HINT_SHOWN,
204                    hint);
205        }
206
207        // Startup module index: Integer -> String, from default.
208        if (defaultPreferences.contains(Keys.KEY_STARTUP_MODULE_INDEX)) {
209            int startupModuleIndex = removeInteger(defaultPreferences,
210                    Keys.KEY_STARTUP_MODULE_INDEX);
211            settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_STARTUP_MODULE_INDEX,
212                    startupModuleIndex);
213        }
214
215        // Last camera used module index: Integer -> String, from default.
216        if (defaultPreferences.contains(Keys.KEY_CAMERA_MODULE_LAST_USED)) {
217            int lastCameraUsedModuleIndex = removeInteger(defaultPreferences,
218                    Keys.KEY_CAMERA_MODULE_LAST_USED);
219            settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_MODULE_LAST_USED,
220                    lastCameraUsedModuleIndex);
221        }
222
223        // Flash supported back camera setting: boolean -> String, from old
224        // global.
225        if (oldGlobalPreferences.contains(Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA)) {
226            boolean flashSupportedBackCamera = removeBoolean(oldGlobalPreferences,
227                    Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA);
228            if (flashSupportedBackCamera) {
229                settingsManager.set(SettingsManager.SCOPE_GLOBAL,
230                        Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA, flashSupportedBackCamera);
231            }
232        }
233
234        // Should show refocus viewer cling: boolean -> String, from default.
235        if (defaultPreferences.contains(Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
236            boolean shouldShowRefocusViewer = removeBoolean(defaultPreferences,
237                    Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING);
238            settingsManager.set(SettingsManager.SCOPE_GLOBAL,
239                    Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING, shouldShowRefocusViewer);
240        }
241
242        // Should show settings button cling: boolean -> String, from default.
243        if (defaultPreferences.contains(Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING)) {
244            boolean shouldShowSettingsButtonCling = removeBoolean(defaultPreferences,
245                    Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING);
246            settingsManager.set(SettingsManager.SCOPE_GLOBAL,
247                    Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING, shouldShowSettingsButtonCling);
248        }
249
250        // HDR plus on setting: String on/off -> String, from old global.
251        if (oldGlobalPreferences.contains(Keys.KEY_CAMERA_HDR_PLUS)) {
252            String hdrPlus = removeString(oldGlobalPreferences, Keys.KEY_CAMERA_HDR_PLUS);
253            if (OLD_SETTINGS_VALUE_ON.equals(hdrPlus)) {
254                settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, true);
255            }
256        }
257
258        // HDR on setting: String on/off -> String, from old global.
259        if (oldGlobalPreferences.contains(Keys.KEY_CAMERA_HDR)) {
260            String hdrPlus = removeString(oldGlobalPreferences, Keys.KEY_CAMERA_HDR);
261            if (OLD_SETTINGS_VALUE_ON.equals(hdrPlus)) {
262                settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR, true);
263            }
264        }
265
266        // Grid on setting: String on/off -> String, from old global.
267        if (oldGlobalPreferences.contains(Keys.KEY_CAMERA_GRID_LINES)) {
268            String hdrPlus = removeString(oldGlobalPreferences, Keys.KEY_CAMERA_GRID_LINES);
269            if (OLD_SETTINGS_VALUE_ON.equals(hdrPlus)) {
270                settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_GRID_LINES,
271                        true);
272            }
273        }
274    }
275
276    /**
277     * Part of the AOSP upgrade path, forces the user to choose their location
278     * again if it was originally set to false.
279     */
280    private void forceLocationChoice(SettingsManager settingsManager) {
281        SharedPreferences oldGlobalPreferences =
282                settingsManager.openPreferences(OLD_GLOBAL_PREFERENCES_FILENAME);
283       // Show the location dialog on upgrade if
284        // (a) the user has never set this option (status quo).
285        // (b) the user opt'ed out previously.
286        if (settingsManager.isSet(SettingsManager.SCOPE_GLOBAL,
287                Keys.KEY_RECORD_LOCATION)) {
288            // Location is set in the source file defined for this setting.
289            // Remove the setting if the value is false to launch the dialog.
290            if (!settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
291                    Keys.KEY_RECORD_LOCATION)) {
292                settingsManager.remove(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION);
293            }
294        } else if (oldGlobalPreferences.contains(Keys.KEY_RECORD_LOCATION)) {
295            // Location is not set, check to see if we're upgrading from
296            // a different source file.
297            String location = removeString(oldGlobalPreferences, Keys.KEY_RECORD_LOCATION);
298            if (OLD_SETTINGS_VALUE_ON.equals(location)) {
299                    settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION,
300                            true);
301            }
302        }
303    }
304
305    /**
306     * Part of the AOSP upgrade path, sets front and back picture sizes.
307     */
308    private void upgradeCameraSizeSetting(SettingsManager settingsManager,
309            Context context, CameraDeviceInfo infos,
310            SettingsUtil.CameraDeviceSelector facing) {
311        String key;
312        if (facing == SettingsUtil.CAMERA_FACING_FRONT) {
313            key = Keys.KEY_PICTURE_SIZE_FRONT;
314        } else if (facing == SettingsUtil.CAMERA_FACING_BACK) {
315            key = Keys.KEY_PICTURE_SIZE_BACK;
316        } else {
317            Log.w(TAG, "Ignoring attempt to upgrade size of unhandled camera facing direction");
318            return;
319        }
320
321        // infos might be null if the underlying camera device is broken. In
322        // that case, just delete the old settings and force the user to
323        // reselect, it's the least evil solution given we want to only upgrade
324        // settings once.
325        if (infos == null) {
326            settingsManager.remove(SettingsManager.SCOPE_GLOBAL, key);
327            return;
328        }
329
330        String pictureSize = settingsManager.getString(SettingsManager.SCOPE_GLOBAL, key);
331        int camera = SettingsUtil.getCameraId(infos, facing);
332        if (camera != -1) {
333            List<Size> supported = CameraPictureSizesCacher.getSizesForCamera(camera, context);
334            if (supported != null) {
335                Size size = SettingsUtil.getPhotoSize(pictureSize, supported, camera);
336                settingsManager.set(SettingsManager.SCOPE_GLOBAL, key,
337                        SettingsUtil.sizeToSettingString(size));
338            }
339        }
340    }
341
342    /**
343     * Part of the AOSP upgrade path, copies all of the keys and values in a
344     * SharedPreferences file to another SharedPreferences file, as Strings.
345     * Settings that are not a known supported format (int/boolean/String)
346     * are dropped with warning.
347     *
348     * This will normally be run only once but was used both for upgrade version
349     * 4 and 6 -- in 6 we repair issues with previous runs of the upgrader. So
350     * we make sure to remove entries from destination if the source isn't valid
351     * like a null or unsupported type.
352     */
353    private void copyPreferences(SharedPreferences oldPrefs,
354            SharedPreferences newPrefs) {
355        Map<String, ?> entries = oldPrefs.getAll();
356        for (Map.Entry<String, ?> entry : entries.entrySet()) {
357            String key = entry.getKey();
358            Object value = entry.getValue();
359            if (value == null) {
360                Log.w(TAG, "skipped upgrade and removing entry for null key " + key);
361                newPrefs.edit().remove(key).apply();
362            } else if (value instanceof Boolean) {
363                String boolValue = SettingsManager.convert((Boolean) value);
364                newPrefs.edit().putString(key, boolValue).apply();
365            } else if (value instanceof Integer) {
366                String intValue = SettingsManager.convert((Integer) value);
367                newPrefs.edit().putString(key, intValue).apply();
368            } else if (value instanceof Long){
369                // New SettingsManager only supports int values. Attempt to
370                // recover any longs which happen to be present if they are
371                // within int range.
372                long longValue = (Long) value;
373                if (longValue <= Integer.MAX_VALUE && longValue >= Integer.MIN_VALUE) {
374                    String intValue = SettingsManager.convert((int) longValue);
375                    newPrefs.edit().putString(key, intValue).apply();
376                } else {
377                    Log.w(TAG, "skipped upgrade for out of bounds long key " +
378                            key + " : " + longValue);
379                }
380            } else if (value instanceof String){
381                newPrefs.edit().putString(key, (String) value).apply();
382            } else {
383                Log.w(TAG,"skipped upgrade and removing entry for unrecognized "
384                        + "key type " + key + " : " + value.getClass());
385                newPrefs.edit().remove(key).apply();
386            }
387        }
388    }
389
390    /**
391     * Part of the AOSP upgrade path, copies all of the key and values in the
392     * old camera SharedPreferences files to new files.
393     */
394    private void upgradeCameraSettingsFiles(SettingsManager settingsManager,
395            Context context) {
396        String[] cameraIds =
397                context.getResources().getStringArray(R.array.camera_id_entryvalues);
398
399        for (int i = 0; i < cameraIds.length; i++) {
400            SharedPreferences oldCameraPreferences =
401                    settingsManager.openPreferences(
402                            OLD_CAMERA_PREFERENCES_PREFIX + cameraIds[i]);
403            SharedPreferences newCameraPreferences =
404                    settingsManager.openPreferences(
405                            SettingsManager.getCameraSettingScope(cameraIds[i]));
406
407            copyPreferences(oldCameraPreferences, newCameraPreferences);
408        }
409    }
410
411    private void upgradeModuleSettingsFiles(SettingsManager settingsManager,
412            Context context, AppController app) {
413        int[] moduleIds = context.getResources().getIntArray(R.array.camera_modes);
414
415        for (int i = 0; i < moduleIds.length; i++) {
416            String moduleId = Integer.toString(moduleIds[i]);
417            SharedPreferences oldModulePreferences =
418                    settingsManager.openPreferences(
419                            OLD_MODULE_PREFERENCES_PREFIX + moduleId);
420
421            if (oldModulePreferences != null && oldModulePreferences.getAll().size() > 0) {
422                ModuleManagerImpl.ModuleAgent agent =
423                        app.getModuleManager().getModuleAgent(moduleIds[i]);
424                if (agent != null) {
425                    SharedPreferences newModulePreferences = settingsManager.openPreferences(
426                            SettingsManager.getModuleSettingScope(agent.getScopeNamespace()));
427
428                    copyPreferences(oldModulePreferences, newModulePreferences);
429                }
430            }
431        }
432    }
433
434    /**
435     * The R.integer.camera_mode_* indices were cleaned up, resulting in
436     * removals and renaming of certain values. In particular camera_mode_gcam
437     * is now 5, not 6. We modify any persisted user settings that may refer to
438     * the old value.
439     */
440    private void upgradeSelectedModeIndex(SettingsManager settingsManager, Context context) {
441        int oldGcamIndex = 6; // from hardcoded previous mode index resource
442        int gcamIndex = context.getResources().getInteger(R.integer.camera_mode_gcam);
443
444        int lastUsedCameraIndex = settingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
445                Keys.KEY_CAMERA_MODULE_LAST_USED);
446        if (lastUsedCameraIndex == oldGcamIndex) {
447            settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_MODULE_LAST_USED,
448                    gcamIndex);
449        }
450
451        int startupModuleIndex = settingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
452                Keys.KEY_STARTUP_MODULE_INDEX);
453        if (startupModuleIndex == oldGcamIndex) {
454            settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_STARTUP_MODULE_INDEX,
455                    gcamIndex);
456        }
457    }
458
459    /**
460     * A targeted fix for b/19693226.
461     * <p>
462     * Since the N5 doesn't natively support a high resolution 16:9 size we need
463     * to artificially add it and then crop the result from the high-resolution
464     * 4:3 size. In version 2.4 we unfortunately swapped the dimensions of
465     * ResolutionUtil#NEXUS_5_LARGE_16_BY_9_SIZE, which now causes a few issues
466     * in 2.5. If we detect this case, we will swap the dimensions here to make
467     * sure they are the right way around going forward.
468     */
469    private void updateN516by9ResolutionIfNeeded(SettingsManager settingsManager) {
470        if (!ApiHelper.IS_NEXUS_5) {
471            return;
472        }
473
474        String pictureSize = settingsManager.getString(SettingsManager.SCOPE_GLOBAL,
475                Keys.KEY_PICTURE_SIZE_BACK);
476        if ("1836x3264".equals(pictureSize)) {
477            Log.i(TAG, "Swapped dimensions on N5 16:9 resolution.");
478            settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_PICTURE_SIZE_BACK,
479                    "3264x1836");
480        }
481    }
482}
483