1 /* 2 * Copyright (C) 2009 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; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.SharedPreferences; 22 import android.content.SharedPreferences.Editor; 23 import android.content.res.Resources; 24 import android.content.res.TypedArray; 25 import android.hardware.Camera.CameraInfo; 26 import android.hardware.Camera.Parameters; 27 import android.hardware.Camera.Size; 28 import android.media.CamcorderProfile; 29 import android.util.Log; 30 31 import com.android.camera.util.ApiHelper; 32 import com.android.camera.util.CameraUtil; 33 import com.android.camera.util.GcamHelper; 34 import com.android.camera2.R; 35 36 import java.util.ArrayList; 37 import java.util.List; 38 import java.util.Locale; 39 40 /** 41 * Provides utilities and keys for Camera settings. 42 */ 43 public class CameraSettings { 44 private static final int NOT_FOUND = -1; 45 46 public static final String KEY_VERSION = "pref_version_key"; 47 public static final String KEY_LOCAL_VERSION = "pref_local_version_key"; 48 public static final String KEY_RECORD_LOCATION = "pref_camera_recordlocation_key"; 49 public static final String KEY_VIDEO_QUALITY = "pref_video_quality_key"; 50 public static final String KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL = "pref_video_time_lapse_frame_interval_key"; 51 public static final String KEY_PICTURE_SIZE = "pref_camera_picturesize_key"; 52 public static final String KEY_JPEG_QUALITY = "pref_camera_jpegquality_key"; 53 public static final String KEY_FOCUS_MODE = "pref_camera_focusmode_key"; 54 public static final String KEY_FLASH_MODE = "pref_camera_flashmode_key"; 55 public static final String KEY_VIDEOCAMERA_FLASH_MODE = "pref_camera_video_flashmode_key"; 56 public static final String KEY_WHITE_BALANCE = "pref_camera_whitebalance_key"; 57 public static final String KEY_SCENE_MODE = "pref_camera_scenemode_key"; 58 public static final String KEY_EXPOSURE = "pref_camera_exposure_key"; 59 public static final String KEY_TIMER = "pref_camera_timer_key"; 60 public static final String KEY_TIMER_SOUND_EFFECTS = "pref_camera_timer_sound_key"; 61 public static final String KEY_VIDEO_EFFECT = "pref_video_effect_key"; 62 public static final String KEY_CAMERA_ID = "pref_camera_id_key"; 63 public static final String KEY_CAMERA_HDR = "pref_camera_hdr_key"; 64 public static final String KEY_CAMERA_HDR_PLUS = "pref_camera_hdr_plus_key"; 65 public static final String KEY_CAMERA_FIRST_USE_HINT_SHOWN = "pref_camera_first_use_hint_shown_key"; 66 public static final String KEY_VIDEO_FIRST_USE_HINT_SHOWN = "pref_video_first_use_hint_shown_key"; 67 public static final String KEY_PHOTOSPHERE_PICTURESIZE = "pref_photosphere_picturesize_key"; 68 public static final String KEY_STARTUP_MODULE_INDEX = "camera.startup_module"; 69 70 public static final String EXPOSURE_DEFAULT_VALUE = "0"; 71 72 public static final int CURRENT_VERSION = 5; 73 public static final int CURRENT_LOCAL_VERSION = 2; 74 75 private static final String TAG = "CameraSettings"; 76 77 private final Context mContext; 78 private final Parameters mParameters; 79 private final CameraInfo[] mCameraInfo; 80 private final int mCameraId; 81 82 public CameraSettings(Activity activity, Parameters parameters, 83 int cameraId, CameraInfo[] cameraInfo) { 84 mContext = activity; 85 mParameters = parameters; 86 mCameraId = cameraId; 87 mCameraInfo = cameraInfo; 88 } 89 90 public PreferenceGroup getPreferenceGroup(int preferenceRes) { 91 PreferenceInflater inflater = new PreferenceInflater(mContext); 92 PreferenceGroup group = 93 (PreferenceGroup) inflater.inflate(preferenceRes); 94 if (mParameters != null) initPreference(group); 95 return group; 96 } 97 98 public static String getSupportedHighestVideoQuality(int cameraId, 99 String defaultQuality) { 100 // When launching the camera app first time, we will set the video quality 101 // to the first one (i.e. highest quality) in the supported list 102 List<String> supported = getSupportedVideoQuality(cameraId); 103 if (supported == null) { 104 Log.e(TAG, "No supported video quality is found"); 105 return defaultQuality; 106 } 107 return supported.get(0); 108 } 109 110 public static void initialCameraPictureSize( 111 Context context, Parameters parameters) { 112 // When launching the camera app first time, we will set the picture 113 // size to the first one in the list defined in "arrays.xml" and is also 114 // supported by the driver. 115 List<Size> supported = parameters.getSupportedPictureSizes(); 116 if (supported == null) return; 117 for (String candidate : context.getResources().getStringArray( 118 R.array.pref_camera_picturesize_entryvalues)) { 119 if (setCameraPictureSize(candidate, supported, parameters)) { 120 SharedPreferences.Editor editor = ComboPreferences 121 .get(context).edit(); 122 editor.putString(KEY_PICTURE_SIZE, candidate); 123 editor.apply(); 124 return; 125 } 126 } 127 Log.e(TAG, "No supported picture size found"); 128 } 129 130 public static void removePreferenceFromScreen( 131 PreferenceGroup group, String key) { 132 removePreference(group, key); 133 } 134 135 public static boolean setCameraPictureSize( 136 String candidate, List<Size> supported, Parameters parameters) { 137 int index = candidate.indexOf('x'); 138 if (index == NOT_FOUND) return false; 139 int width = Integer.parseInt(candidate.substring(0, index)); 140 int height = Integer.parseInt(candidate.substring(index + 1)); 141 for (Size size : supported) { 142 if (size.width == width && size.height == height) { 143 parameters.setPictureSize(width, height); 144 return true; 145 } 146 } 147 return false; 148 } 149 150 public static int getMaxVideoDuration(Context context) { 151 int duration = 0; // in milliseconds, 0 means unlimited. 152 try { 153 duration = context.getResources().getInteger(R.integer.max_video_recording_length); 154 } catch (Resources.NotFoundException ex) { 155 } 156 return duration; 157 } 158 159 private void initPreference(PreferenceGroup group) { 160 ListPreference videoQuality = group.findPreference(KEY_VIDEO_QUALITY); 161 ListPreference timeLapseInterval = group.findPreference(KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL); 162 ListPreference pictureSize = group.findPreference(KEY_PICTURE_SIZE); 163 ListPreference whiteBalance = group.findPreference(KEY_WHITE_BALANCE); 164 ListPreference sceneMode = group.findPreference(KEY_SCENE_MODE); 165 ListPreference flashMode = group.findPreference(KEY_FLASH_MODE); 166 ListPreference focusMode = group.findPreference(KEY_FOCUS_MODE); 167 IconListPreference exposure = 168 (IconListPreference) group.findPreference(KEY_EXPOSURE); 169 IconListPreference cameraIdPref = 170 (IconListPreference) group.findPreference(KEY_CAMERA_ID); 171 ListPreference videoFlashMode = 172 group.findPreference(KEY_VIDEOCAMERA_FLASH_MODE); 173 ListPreference videoEffect = group.findPreference(KEY_VIDEO_EFFECT); 174 ListPreference cameraHdr = group.findPreference(KEY_CAMERA_HDR); 175 ListPreference cameraHdrPlus = group.findPreference(KEY_CAMERA_HDR_PLUS); 176 177 // Since the screen could be loaded from different resources, we need 178 // to check if the preference is available here 179 if (videoQuality != null) { 180 filterUnsupportedOptions(group, videoQuality, getSupportedVideoQuality(mCameraId)); 181 } 182 183 if (pictureSize != null) { 184 filterUnsupportedOptions(group, pictureSize, sizeListToStringList( 185 mParameters.getSupportedPictureSizes())); 186 filterSimilarPictureSize(group, pictureSize); 187 } 188 if (whiteBalance != null) { 189 filterUnsupportedOptions(group, 190 whiteBalance, mParameters.getSupportedWhiteBalance()); 191 } 192 if (sceneMode != null) { 193 filterUnsupportedOptions(group, 194 sceneMode, mParameters.getSupportedSceneModes()); 195 } 196 if (flashMode != null) { 197 filterUnsupportedOptions(group, 198 flashMode, mParameters.getSupportedFlashModes()); 199 } 200 if (focusMode != null) { 201 if (!CameraUtil.isFocusAreaSupported(mParameters)) { 202 filterUnsupportedOptions(group, 203 focusMode, mParameters.getSupportedFocusModes()); 204 } else { 205 // Remove the focus mode if we can use tap-to-focus. 206 removePreference(group, focusMode.getKey()); 207 } 208 } 209 if (videoFlashMode != null) { 210 filterUnsupportedOptions(group, 211 videoFlashMode, mParameters.getSupportedFlashModes()); 212 } 213 if (exposure != null) buildExposureCompensation(group, exposure); 214 if (cameraIdPref != null) buildCameraId(group, cameraIdPref); 215 216 if (timeLapseInterval != null) { 217 resetIfInvalid(timeLapseInterval); 218 } 219 if (videoEffect != null) { 220 filterUnsupportedOptions(group, videoEffect, null); 221 } 222 if (cameraHdr != null && (!ApiHelper.HAS_CAMERA_HDR 223 || !CameraUtil.isCameraHdrSupported(mParameters))) { 224 removePreference(group, cameraHdr.getKey()); 225 } 226 227 int frontCameraId = CameraHolder.instance().getFrontCameraId(); 228 boolean isFrontCamera = (frontCameraId == mCameraId); 229 if (cameraHdrPlus != null && (!ApiHelper.HAS_CAMERA_HDR_PLUS || 230 !GcamHelper.hasGcamCapture() || isFrontCamera)) { 231 removePreference(group, cameraHdrPlus.getKey()); 232 } 233 } 234 235 private void buildExposureCompensation( 236 PreferenceGroup group, IconListPreference exposure) { 237 int max = mParameters.getMaxExposureCompensation(); 238 int min = mParameters.getMinExposureCompensation(); 239 if (max == 0 && min == 0) { 240 removePreference(group, exposure.getKey()); 241 return; 242 } 243 float step = mParameters.getExposureCompensationStep(); 244 245 // show only integer values for exposure compensation 246 int maxValue = Math.min(3, (int) Math.floor(max * step)); 247 int minValue = Math.max(-3, (int) Math.ceil(min * step)); 248 String explabel = mContext.getResources().getString(R.string.pref_exposure_label); 249 CharSequence entries[] = new CharSequence[maxValue - minValue + 1]; 250 CharSequence entryValues[] = new CharSequence[maxValue - minValue + 1]; 251 CharSequence labels[] = new CharSequence[maxValue - minValue + 1]; 252 int[] icons = new int[maxValue - minValue + 1]; 253 TypedArray iconIds = mContext.getResources().obtainTypedArray( 254 R.array.pref_camera_exposure_icons); 255 for (int i = minValue; i <= maxValue; ++i) { 256 entryValues[i - minValue] = Integer.toString(Math.round(i / step)); 257 StringBuilder builder = new StringBuilder(); 258 if (i > 0) builder.append('+'); 259 entries[i - minValue] = builder.append(i).toString(); 260 labels[i - minValue] = explabel + " " + builder.toString(); 261 icons[i - minValue] = iconIds.getResourceId(3 + i, 0); 262 } 263 exposure.setUseSingleIcon(true); 264 exposure.setEntries(entries); 265 exposure.setLabels(labels); 266 exposure.setEntryValues(entryValues); 267 exposure.setLargeIconIds(icons); 268 } 269 270 private void buildCameraId( 271 PreferenceGroup group, IconListPreference preference) { 272 int numOfCameras = mCameraInfo.length; 273 if (numOfCameras < 2) { 274 removePreference(group, preference.getKey()); 275 return; 276 } 277 278 CharSequence[] entryValues = new CharSequence[numOfCameras]; 279 for (int i = 0; i < numOfCameras; ++i) { 280 entryValues[i] = "" + i; 281 } 282 preference.setEntryValues(entryValues); 283 } 284 285 private static boolean removePreference(PreferenceGroup group, String key) { 286 for (int i = 0, n = group.size(); i < n; i++) { 287 CameraPreference child = group.get(i); 288 if (child instanceof PreferenceGroup) { 289 if (removePreference((PreferenceGroup) child, key)) { 290 return true; 291 } 292 } 293 if (child instanceof ListPreference && 294 ((ListPreference) child).getKey().equals(key)) { 295 group.removePreference(i); 296 return true; 297 } 298 } 299 return false; 300 } 301 302 private void filterUnsupportedOptions(PreferenceGroup group, 303 ListPreference pref, List<String> supported) { 304 305 // Remove the preference if the parameter is not supported or there is 306 // only one options for the settings. 307 if (supported == null || supported.size() <= 1) { 308 removePreference(group, pref.getKey()); 309 return; 310 } 311 312 pref.filterUnsupported(supported); 313 if (pref.getEntries().length <= 1) { 314 removePreference(group, pref.getKey()); 315 return; 316 } 317 318 resetIfInvalid(pref); 319 } 320 321 private void filterSimilarPictureSize(PreferenceGroup group, 322 ListPreference pref) { 323 pref.filterDuplicated(); 324 if (pref.getEntries().length <= 1) { 325 removePreference(group, pref.getKey()); 326 return; 327 } 328 resetIfInvalid(pref); 329 } 330 331 private void resetIfInvalid(ListPreference pref) { 332 // Set the value to the first entry if it is invalid. 333 String value = pref.getValue(); 334 if (pref.findIndexOfValue(value) == NOT_FOUND) { 335 pref.setValueIndex(0); 336 } 337 } 338 339 private static List<String> sizeListToStringList(List<Size> sizes) { 340 ArrayList<String> list = new ArrayList<String>(); 341 for (Size size : sizes) { 342 list.add(String.format(Locale.ENGLISH, "%dx%d", size.width, size.height)); 343 } 344 return list; 345 } 346 347 public static void upgradeLocalPreferences(SharedPreferences pref) { 348 int version; 349 try { 350 version = pref.getInt(KEY_LOCAL_VERSION, 0); 351 } catch (Exception ex) { 352 version = 0; 353 } 354 if (version == CURRENT_LOCAL_VERSION) return; 355 356 SharedPreferences.Editor editor = pref.edit(); 357 if (version == 1) { 358 // We use numbers to represent the quality now. The quality definition is identical to 359 // that of CamcorderProfile.java. 360 editor.remove("pref_video_quality_key"); 361 } 362 editor.putInt(KEY_LOCAL_VERSION, CURRENT_LOCAL_VERSION); 363 editor.apply(); 364 } 365 366 public static void upgradeGlobalPreferences(SharedPreferences pref) { 367 upgradeOldVersion(pref); 368 upgradeCameraId(pref); 369 } 370 371 private static void upgradeOldVersion(SharedPreferences pref) { 372 int version; 373 try { 374 version = pref.getInt(KEY_VERSION, 0); 375 } catch (Exception ex) { 376 version = 0; 377 } 378 if (version == CURRENT_VERSION) return; 379 380 SharedPreferences.Editor editor = pref.edit(); 381 if (version == 0) { 382 // We won't use the preference which change in version 1. 383 // So, just upgrade to version 1 directly 384 version = 1; 385 } 386 if (version == 1) { 387 // Change jpeg quality {65,75,85} to {normal,fine,superfine} 388 String quality = pref.getString(KEY_JPEG_QUALITY, "85"); 389 if (quality.equals("65")) { 390 quality = "normal"; 391 } else if (quality.equals("75")) { 392 quality = "fine"; 393 } else { 394 quality = "superfine"; 395 } 396 editor.putString(KEY_JPEG_QUALITY, quality); 397 version = 2; 398 } 399 if (version == 2) { 400 editor.putString(KEY_RECORD_LOCATION, 401 pref.getBoolean(KEY_RECORD_LOCATION, false) 402 ? RecordLocationPreference.VALUE_ON 403 : RecordLocationPreference.VALUE_NONE); 404 version = 3; 405 } 406 if (version == 3) { 407 // Just use video quality to replace it and 408 // ignore the current settings. 409 editor.remove("pref_camera_videoquality_key"); 410 editor.remove("pref_camera_video_duration_key"); 411 } 412 413 editor.putInt(KEY_VERSION, CURRENT_VERSION); 414 editor.apply(); 415 } 416 417 private static void upgradeCameraId(SharedPreferences pref) { 418 // The id stored in the preference may be out of range if we are running 419 // inside the emulator and a webcam is removed. 420 // Note: This method accesses the global preferences directly, not the 421 // combo preferences. 422 int cameraId = readPreferredCameraId(pref); 423 if (cameraId == 0) return; // fast path 424 425 int n = CameraHolder.instance().getNumberOfCameras(); 426 if (cameraId < 0 || cameraId >= n) { 427 writePreferredCameraId(pref, 0); 428 } 429 } 430 431 public static int readPreferredCameraId(SharedPreferences pref) { 432 return Integer.parseInt(pref.getString(KEY_CAMERA_ID, "0")); 433 } 434 435 public static void writePreferredCameraId(SharedPreferences pref, 436 int cameraId) { 437 Editor editor = pref.edit(); 438 editor.putString(KEY_CAMERA_ID, Integer.toString(cameraId)); 439 editor.apply(); 440 } 441 442 public static int readExposure(ComboPreferences preferences) { 443 String exposure = preferences.getString( 444 CameraSettings.KEY_EXPOSURE, 445 EXPOSURE_DEFAULT_VALUE); 446 try { 447 return Integer.parseInt(exposure); 448 } catch (Exception ex) { 449 Log.e(TAG, "Invalid exposure: " + exposure); 450 } 451 return 0; 452 } 453 454 public static void restorePreferences(Context context, 455 ComboPreferences preferences, Parameters parameters) { 456 int currentCameraId = readPreferredCameraId(preferences); 457 458 // Clear the preferences of both cameras. 459 int backCameraId = CameraHolder.instance().getBackCameraId(); 460 if (backCameraId != -1) { 461 preferences.setLocalId(context, backCameraId); 462 Editor editor = preferences.edit(); 463 editor.clear(); 464 editor.apply(); 465 } 466 int frontCameraId = CameraHolder.instance().getFrontCameraId(); 467 if (frontCameraId != -1) { 468 preferences.setLocalId(context, frontCameraId); 469 Editor editor = preferences.edit(); 470 editor.clear(); 471 editor.apply(); 472 } 473 474 // Switch back to the preferences of the current camera. Otherwise, 475 // we may write the preference to wrong camera later. 476 preferences.setLocalId(context, currentCameraId); 477 478 upgradeGlobalPreferences(preferences.getGlobal()); 479 upgradeLocalPreferences(preferences.getLocal()); 480 481 // Write back the current camera id because parameters are related to 482 // the camera. Otherwise, we may switch to the front camera but the 483 // initial picture size is that of the back camera. 484 initialCameraPictureSize(context, parameters); 485 writePreferredCameraId(preferences, currentCameraId); 486 } 487 488 private static ArrayList<String> getSupportedVideoQuality(int cameraId) { 489 ArrayList<String> supported = new ArrayList<String>(); 490 // Check for supported quality 491 if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) { 492 supported.add(Integer.toString(CamcorderProfile.QUALITY_1080P)); 493 } 494 if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) { 495 supported.add(Integer.toString(CamcorderProfile.QUALITY_720P)); 496 } 497 if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) { 498 supported.add(Integer.toString(CamcorderProfile.QUALITY_480P)); 499 } 500 return supported; 501 } 502 } 503