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