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