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