/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.camera.settings; import android.content.Context; import android.content.SharedPreferences; import com.android.camera.app.AppController; import com.android.camera.app.ModuleManagerImpl; import com.android.camera.debug.Log; import com.android.camera.util.ApiHelper; import com.android.camera.util.Size; import com.android.camera2.R; import com.android.ex.camera2.portability.CameraAgentFactory; import com.android.ex.camera2.portability.CameraDeviceInfo; import java.util.List; import java.util.Map; /** * Defines the general upgrade path for the app. Modules may define specific * upgrade logic, but upgrading for preferences across modules, CameraActivity * or application-wide can be added here. */ public class AppUpgrader extends SettingsUpgrader { private static final Log.Tag TAG = new Log.Tag("AppUpgrader"); private static final String OLD_CAMERA_PREFERENCES_PREFIX = "_preferences_"; private static final String OLD_MODULE_PREFERENCES_PREFIX = "_preferences_module_"; private static final String OLD_GLOBAL_PREFERENCES_FILENAME = "_preferences_camera"; private static final String OLD_KEY_UPGRADE_VERSION = "pref_strict_upgrade_version"; /** * With this version everyone was forced to choose their location settings * again. */ private static final int FORCE_LOCATION_CHOICE_VERSION = 2; /** * With this version, the camera size setting changed from a "small", * "medium" and "default" to strings representing the actual resolutions, * i.e. "1080x1776". */ private static final int CAMERA_SIZE_SETTING_UPGRADE_VERSION = 3; /** * With this version, the names of the files storing camera specific and * module specific settings changed. *

* NOTE: changed this from 4 to 6 to re-run on latest Glacier upgrade. * Initial upgraders to Glacier will run conversion once as of the change. * When re-run for early dogfooders, values will get overwritten but will * all work. */ private static final int CAMERA_MODULE_SETTINGS_FILES_RENAMED_VERSION = 6; /** * With this version, timelapse mode was removed and mode indices need to be * resequenced. */ private static final int CAMERA_SETTINGS_SELECTED_MODULE_INDEX = 5; /** * With this version internal storage is changed to use only Strings, and * a type conversion process should execute. */ private static final int CAMERA_SETTINGS_STRINGS_UPGRADE = 5; /** * With this version we needed to convert the artificial 16:9 high * resolution size on the N5 since we stored it with a swapped width/height. */ public static final int NEEDS_N5_16by9_RESOLUTION_SWAP = 7; /** * Increment this value whenever new AOSP UpgradeSteps need to be executed. */ public static final int APP_UPGRADE_VERSION = 7; private final AppController mAppController; public AppUpgrader(final AppController appController) { super(Keys.KEY_UPGRADE_VERSION, APP_UPGRADE_VERSION); mAppController = appController; } @Override protected int getLastVersion(SettingsManager settingsManager) { // Prior upgrade versions were stored in the default preferences as int // and String. We create a new version location for migration to String. // If we don't have a version persisted in the new location, check for // the prior value from the old location. We expect the old value to be // processed during {@link #upgradeTypesToStrings}. SharedPreferences defaultPreferences = settingsManager.getDefaultPreferences(); if (defaultPreferences.contains(OLD_KEY_UPGRADE_VERSION)) { Map allPrefs = defaultPreferences.getAll(); Object oldVersion = allPrefs.get(OLD_KEY_UPGRADE_VERSION); defaultPreferences.edit().remove(OLD_KEY_UPGRADE_VERSION).apply(); if (oldVersion instanceof Integer) { return (Integer) oldVersion; } else if (oldVersion instanceof String) { return SettingsManager.convertToInt((String) oldVersion); } } return super.getLastVersion(settingsManager); } @Override public void upgrade(SettingsManager settingsManager, int lastVersion, int currentVersion) { Context context = mAppController.getAndroidContext(); // Do strings upgrade first before 'earlier' upgrades, since they assume // valid storage of values. if (lastVersion < CAMERA_SETTINGS_STRINGS_UPGRADE) { upgradeTypesToStrings(settingsManager); } if (lastVersion < FORCE_LOCATION_CHOICE_VERSION) { forceLocationChoice(settingsManager); } if (lastVersion < CAMERA_SIZE_SETTING_UPGRADE_VERSION) { CameraDeviceInfo infos = CameraAgentFactory .getAndroidCameraAgent(context, CameraAgentFactory.CameraApi.API_1) .getCameraDeviceInfo(); upgradeCameraSizeSetting(settingsManager, context, infos, SettingsUtil.CAMERA_FACING_FRONT); upgradeCameraSizeSetting(settingsManager, context, infos, SettingsUtil.CAMERA_FACING_BACK); // We changed size handling and aspect ratio placement, put user // back into Camera mode this time to ensure they see the ratio // chooser if applicable. settingsManager.remove(SettingsManager.SCOPE_GLOBAL, Keys.KEY_STARTUP_MODULE_INDEX); } if (lastVersion < CAMERA_MODULE_SETTINGS_FILES_RENAMED_VERSION) { upgradeCameraSettingsFiles(settingsManager, context); upgradeModuleSettingsFiles(settingsManager, context, mAppController); } if (lastVersion < CAMERA_SETTINGS_SELECTED_MODULE_INDEX) { upgradeSelectedModeIndex(settingsManager, context); } if (lastVersion < NEEDS_N5_16by9_RESOLUTION_SWAP) { updateN516by9ResolutionIfNeeded(settingsManager); } } /** * Converts settings that were stored in SharedPreferences as non-Strings, * to Strings. This is necessary due to a SettingsManager API refactoring. * Should only be executed if we detected a change in * Keys.KEY_UPGRADE_VERSION type from int to string; rerunning this on * string values will result in ClassCastExceptions when trying to retrieve * an int or boolean as a String. */ private void upgradeTypesToStrings(SettingsManager settingsManager) { SharedPreferences defaultPreferences = settingsManager.getDefaultPreferences(); SharedPreferences oldGlobalPreferences = settingsManager.openPreferences(OLD_GLOBAL_PREFERENCES_FILENAME); // Location: boolean -> String, from default. if (defaultPreferences.contains(Keys.KEY_RECORD_LOCATION)) { boolean location = removeBoolean(defaultPreferences, Keys.KEY_RECORD_LOCATION); settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION, location); } // User selected aspect ratio: boolean -> String, from default. if (defaultPreferences.contains(Keys.KEY_USER_SELECTED_ASPECT_RATIO)) { boolean userSelectedAspectRatio = removeBoolean(defaultPreferences, Keys.KEY_USER_SELECTED_ASPECT_RATIO); settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_USER_SELECTED_ASPECT_RATIO, userSelectedAspectRatio); } // Manual exposure compensation: boolean -> String, from default. if (defaultPreferences.contains(Keys.KEY_EXPOSURE_COMPENSATION_ENABLED)) { boolean manualExposureCompensationEnabled = removeBoolean(defaultPreferences, Keys.KEY_EXPOSURE_COMPENSATION_ENABLED); settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_EXPOSURE_COMPENSATION_ENABLED, manualExposureCompensationEnabled); } // Hint: boolean -> String, from default. if (defaultPreferences.contains(Keys.KEY_CAMERA_FIRST_USE_HINT_SHOWN)) { boolean hint = removeBoolean(defaultPreferences, Keys.KEY_CAMERA_FIRST_USE_HINT_SHOWN); settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_FIRST_USE_HINT_SHOWN, hint); } // Startup module index: Integer -> String, from default. if (defaultPreferences.contains(Keys.KEY_STARTUP_MODULE_INDEX)) { int startupModuleIndex = removeInteger(defaultPreferences, Keys.KEY_STARTUP_MODULE_INDEX); settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_STARTUP_MODULE_INDEX, startupModuleIndex); } // Last camera used module index: Integer -> String, from default. if (defaultPreferences.contains(Keys.KEY_CAMERA_MODULE_LAST_USED)) { int lastCameraUsedModuleIndex = removeInteger(defaultPreferences, Keys.KEY_CAMERA_MODULE_LAST_USED); settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_MODULE_LAST_USED, lastCameraUsedModuleIndex); } // Flash supported back camera setting: boolean -> String, from old // global. if (oldGlobalPreferences.contains(Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA)) { boolean flashSupportedBackCamera = removeBoolean(oldGlobalPreferences, Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA); if (flashSupportedBackCamera) { settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA, flashSupportedBackCamera); } } // Should show refocus viewer cling: boolean -> String, from default. if (defaultPreferences.contains(Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) { boolean shouldShowRefocusViewer = removeBoolean(defaultPreferences, Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING); settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING, shouldShowRefocusViewer); } // Should show settings button cling: boolean -> String, from default. if (defaultPreferences.contains(Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING)) { boolean shouldShowSettingsButtonCling = removeBoolean(defaultPreferences, Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING); settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING, shouldShowSettingsButtonCling); } // HDR plus on setting: String on/off -> String, from old global. if (oldGlobalPreferences.contains(Keys.KEY_CAMERA_HDR_PLUS)) { String hdrPlus = removeString(oldGlobalPreferences, Keys.KEY_CAMERA_HDR_PLUS); if (OLD_SETTINGS_VALUE_ON.equals(hdrPlus)) { settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, true); } } // HDR on setting: String on/off -> String, from old global. if (oldGlobalPreferences.contains(Keys.KEY_CAMERA_HDR)) { String hdrPlus = removeString(oldGlobalPreferences, Keys.KEY_CAMERA_HDR); if (OLD_SETTINGS_VALUE_ON.equals(hdrPlus)) { settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR, true); } } // Grid on setting: String on/off -> String, from old global. if (oldGlobalPreferences.contains(Keys.KEY_CAMERA_GRID_LINES)) { String hdrPlus = removeString(oldGlobalPreferences, Keys.KEY_CAMERA_GRID_LINES); if (OLD_SETTINGS_VALUE_ON.equals(hdrPlus)) { settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_GRID_LINES, true); } } } /** * Part of the AOSP upgrade path, forces the user to choose their location * again if it was originally set to false. */ private void forceLocationChoice(SettingsManager settingsManager) { SharedPreferences oldGlobalPreferences = settingsManager.openPreferences(OLD_GLOBAL_PREFERENCES_FILENAME); // Show the location dialog on upgrade if // (a) the user has never set this option (status quo). // (b) the user opt'ed out previously. if (settingsManager.isSet(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION)) { // Location is set in the source file defined for this setting. // Remove the setting if the value is false to launch the dialog. if (!settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION)) { settingsManager.remove(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION); } } else if (oldGlobalPreferences.contains(Keys.KEY_RECORD_LOCATION)) { // Location is not set, check to see if we're upgrading from // a different source file. String location = removeString(oldGlobalPreferences, Keys.KEY_RECORD_LOCATION); if (OLD_SETTINGS_VALUE_ON.equals(location)) { settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION, true); } } } /** * Part of the AOSP upgrade path, sets front and back picture sizes. */ private void upgradeCameraSizeSetting(SettingsManager settingsManager, Context context, CameraDeviceInfo infos, SettingsUtil.CameraDeviceSelector facing) { String key; if (facing == SettingsUtil.CAMERA_FACING_FRONT) { key = Keys.KEY_PICTURE_SIZE_FRONT; } else if (facing == SettingsUtil.CAMERA_FACING_BACK) { key = Keys.KEY_PICTURE_SIZE_BACK; } else { Log.w(TAG, "Ignoring attempt to upgrade size of unhandled camera facing direction"); return; } // infos might be null if the underlying camera device is broken. In // that case, just delete the old settings and force the user to // reselect, it's the least evil solution given we want to only upgrade // settings once. if (infos == null) { settingsManager.remove(SettingsManager.SCOPE_GLOBAL, key); return; } String pictureSize = settingsManager.getString(SettingsManager.SCOPE_GLOBAL, key); int camera = SettingsUtil.getCameraId(infos, facing); if (camera != -1) { List supported = CameraPictureSizesCacher.getSizesForCamera(camera, context); if (supported != null) { Size size = SettingsUtil.getPhotoSize(pictureSize, supported, camera); settingsManager.set(SettingsManager.SCOPE_GLOBAL, key, SettingsUtil.sizeToSettingString(size)); } } } /** * Part of the AOSP upgrade path, copies all of the keys and values in a * SharedPreferences file to another SharedPreferences file, as Strings. * Settings that are not a known supported format (int/boolean/String) * are dropped with warning. * * This will normally be run only once but was used both for upgrade version * 4 and 6 -- in 6 we repair issues with previous runs of the upgrader. So * we make sure to remove entries from destination if the source isn't valid * like a null or unsupported type. */ private void copyPreferences(SharedPreferences oldPrefs, SharedPreferences newPrefs) { Map entries = oldPrefs.getAll(); for (Map.Entry entry : entries.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); if (value == null) { Log.w(TAG, "skipped upgrade and removing entry for null key " + key); newPrefs.edit().remove(key).apply(); } else if (value instanceof Boolean) { String boolValue = SettingsManager.convert((Boolean) value); newPrefs.edit().putString(key, boolValue).apply(); } else if (value instanceof Integer) { String intValue = SettingsManager.convert((Integer) value); newPrefs.edit().putString(key, intValue).apply(); } else if (value instanceof Long){ // New SettingsManager only supports int values. Attempt to // recover any longs which happen to be present if they are // within int range. long longValue = (Long) value; if (longValue <= Integer.MAX_VALUE && longValue >= Integer.MIN_VALUE) { String intValue = SettingsManager.convert((int) longValue); newPrefs.edit().putString(key, intValue).apply(); } else { Log.w(TAG, "skipped upgrade for out of bounds long key " + key + " : " + longValue); } } else if (value instanceof String){ newPrefs.edit().putString(key, (String) value).apply(); } else { Log.w(TAG,"skipped upgrade and removing entry for unrecognized " + "key type " + key + " : " + value.getClass()); newPrefs.edit().remove(key).apply(); } } } /** * Part of the AOSP upgrade path, copies all of the key and values in the * old camera SharedPreferences files to new files. */ private void upgradeCameraSettingsFiles(SettingsManager settingsManager, Context context) { String[] cameraIds = context.getResources().getStringArray(R.array.camera_id_entryvalues); for (int i = 0; i < cameraIds.length; i++) { SharedPreferences oldCameraPreferences = settingsManager.openPreferences( OLD_CAMERA_PREFERENCES_PREFIX + cameraIds[i]); SharedPreferences newCameraPreferences = settingsManager.openPreferences( SettingsManager.getCameraSettingScope(cameraIds[i])); copyPreferences(oldCameraPreferences, newCameraPreferences); } } private void upgradeModuleSettingsFiles(SettingsManager settingsManager, Context context, AppController app) { int[] moduleIds = context.getResources().getIntArray(R.array.camera_modes); for (int i = 0; i < moduleIds.length; i++) { String moduleId = Integer.toString(moduleIds[i]); SharedPreferences oldModulePreferences = settingsManager.openPreferences( OLD_MODULE_PREFERENCES_PREFIX + moduleId); if (oldModulePreferences != null && oldModulePreferences.getAll().size() > 0) { ModuleManagerImpl.ModuleAgent agent = app.getModuleManager().getModuleAgent(moduleIds[i]); if (agent != null) { SharedPreferences newModulePreferences = settingsManager.openPreferences( SettingsManager.getModuleSettingScope(agent.getScopeNamespace())); copyPreferences(oldModulePreferences, newModulePreferences); } } } } /** * The R.integer.camera_mode_* indices were cleaned up, resulting in * removals and renaming of certain values. In particular camera_mode_gcam * is now 5, not 6. We modify any persisted user settings that may refer to * the old value. */ private void upgradeSelectedModeIndex(SettingsManager settingsManager, Context context) { int oldGcamIndex = 6; // from hardcoded previous mode index resource int gcamIndex = context.getResources().getInteger(R.integer.camera_mode_gcam); int lastUsedCameraIndex = settingsManager.getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_MODULE_LAST_USED); if (lastUsedCameraIndex == oldGcamIndex) { settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_MODULE_LAST_USED, gcamIndex); } int startupModuleIndex = settingsManager.getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_STARTUP_MODULE_INDEX); if (startupModuleIndex == oldGcamIndex) { settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_STARTUP_MODULE_INDEX, gcamIndex); } } /** * A targeted fix for b/19693226. *

* Since the N5 doesn't natively support a high resolution 16:9 size we need * to artificially add it and then crop the result from the high-resolution * 4:3 size. In version 2.4 we unfortunately swapped the dimensions of * ResolutionUtil#NEXUS_5_LARGE_16_BY_9_SIZE, which now causes a few issues * in 2.5. If we detect this case, we will swap the dimensions here to make * sure they are the right way around going forward. */ private void updateN516by9ResolutionIfNeeded(SettingsManager settingsManager) { if (!ApiHelper.IS_NEXUS_5) { return; } String pictureSize = settingsManager.getString(SettingsManager.SCOPE_GLOBAL, Keys.KEY_PICTURE_SIZE_BACK); if ("1836x3264".equals(pictureSize)) { Log.i(TAG, "Swapped dimensions on N5 16:9 resolution."); settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_PICTURE_SIZE_BACK, "3264x1836"); } } }