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