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