PhotoModule.java revision 8b2a3ef10a7eea3e703397a3d94766029b0c5581
1/* 2 * Copyright (C) 2012 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.ContentResolver; 22import android.content.Context; 23import android.content.Intent; 24import android.graphics.Bitmap; 25import android.graphics.BitmapFactory; 26import android.graphics.SurfaceTexture; 27import android.hardware.Sensor; 28import android.hardware.SensorEvent; 29import android.hardware.SensorEventListener; 30import android.hardware.SensorManager; 31import android.location.Location; 32import android.media.CameraProfile; 33import android.net.Uri; 34import android.os.AsyncTask; 35import android.os.Build; 36import android.os.Bundle; 37import android.os.Handler; 38import android.os.Looper; 39import android.os.Message; 40import android.os.MessageQueue; 41import android.os.SystemClock; 42import android.provider.MediaStore; 43import android.view.KeyEvent; 44import android.view.View; 45 46import com.android.camera.PhotoModule.NamedImages.NamedEntity; 47import com.android.camera.app.AppController; 48import com.android.camera.app.CameraAppUI; 49import com.android.camera.app.CameraProvider; 50import com.android.camera.app.MediaSaver; 51import com.android.camera.app.MemoryManager; 52import com.android.camera.app.MemoryManager.MemoryListener; 53import com.android.camera.app.MotionManager; 54import com.android.camera.debug.Log; 55import com.android.camera.exif.ExifInterface; 56import com.android.camera.exif.ExifTag; 57import com.android.camera.exif.Rational; 58import com.android.camera.hardware.HardwareSpec; 59import com.android.camera.hardware.HardwareSpecImpl; 60import com.android.camera.hardware.HeadingSensor; 61import com.android.camera.module.ModuleController; 62import com.android.camera.remote.RemoteCameraModule; 63import com.android.camera.settings.CameraPictureSizesCacher; 64import com.android.camera.settings.Keys; 65import com.android.camera.settings.ResolutionUtil; 66import com.android.camera.settings.SettingsManager; 67import com.android.camera.settings.SettingsUtil; 68import com.android.camera.ui.CountDownView; 69import com.android.camera.ui.TouchCoordinate; 70import com.android.camera.util.ApiHelper; 71import com.android.camera.util.CameraUtil; 72import com.android.camera.util.GcamHelper; 73import com.android.camera.util.GservicesHelper; 74import com.android.camera.util.SessionStatsCollector; 75import com.android.camera.util.Size; 76import com.android.camera.util.UsageStatistics; 77import com.android.camera2.R; 78import com.android.ex.camera2.portability.CameraAgent; 79import com.android.ex.camera2.portability.CameraAgent.CameraAFCallback; 80import com.android.ex.camera2.portability.CameraAgent.CameraAFMoveCallback; 81import com.android.ex.camera2.portability.CameraAgent.CameraPictureCallback; 82import com.android.ex.camera2.portability.CameraAgent.CameraProxy; 83import com.android.ex.camera2.portability.CameraAgent.CameraShutterCallback; 84import com.android.ex.camera2.portability.CameraCapabilities; 85import com.android.ex.camera2.portability.CameraDeviceInfo.Characteristics; 86import com.android.ex.camera2.portability.CameraSettings; 87import com.google.common.logging.eventprotos; 88 89import java.io.ByteArrayOutputStream; 90import java.io.File; 91import java.io.FileNotFoundException; 92import java.io.FileOutputStream; 93import java.io.IOException; 94import java.io.OutputStream; 95import java.lang.ref.WeakReference; 96import java.util.ArrayList; 97import java.util.List; 98import java.util.Vector; 99 100public class PhotoModule 101 extends CameraModule 102 implements PhotoController, 103 ModuleController, 104 MemoryListener, 105 FocusOverlayManager.Listener, 106 SettingsManager.OnSettingChangedListener, 107 RemoteCameraModule, 108 CountDownView.OnCountDownStatusListener { 109 110 public static final String PHOTO_MODULE_STRING_ID = "PhotoModule"; 111 112 private static final Log.Tag TAG = new Log.Tag(PHOTO_MODULE_STRING_ID); 113 114 // We number the request code from 1000 to avoid collision with Gallery. 115 private static final int REQUEST_CROP = 1000; 116 117 // Messages defined for the UI thread handler. 118 private static final int MSG_FIRST_TIME_INIT = 1; 119 private static final int MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE = 2; 120 121 // The subset of parameters we need to update in setCameraParameters(). 122 private static final int UPDATE_PARAM_INITIALIZE = 1; 123 private static final int UPDATE_PARAM_ZOOM = 2; 124 private static final int UPDATE_PARAM_PREFERENCE = 4; 125 private static final int UPDATE_PARAM_ALL = -1; 126 127 private static final String DEBUG_IMAGE_PREFIX = "DEBUG_"; 128 129 private CameraActivity mActivity; 130 private CameraProxy mCameraDevice; 131 private int mCameraId; 132 private CameraCapabilities mCameraCapabilities; 133 private CameraSettings mCameraSettings; 134 private boolean mPaused; 135 136 private PhotoUI mUI; 137 138 // The activity is going to switch to the specified camera id. This is 139 // needed because texture copy is done in GL thread. -1 means camera is not 140 // switching. 141 protected int mPendingSwitchCameraId = -1; 142 143 // When setCameraParametersWhenIdle() is called, we accumulate the subsets 144 // needed to be updated in mUpdateSet. 145 private int mUpdateSet; 146 147 private float mZoomValue; // The current zoom ratio. 148 private int mTimerDuration; 149 /** Set when a volume button is clicked to take photo */ 150 private boolean mVolumeButtonClickedFlag = false; 151 152 private boolean mFocusAreaSupported; 153 private boolean mMeteringAreaSupported; 154 private boolean mAeLockSupported; 155 private boolean mAwbLockSupported; 156 private boolean mContinuousFocusSupported; 157 158 private static final String sTempCropFilename = "crop-temp"; 159 160 private boolean mFaceDetectionStarted = false; 161 162 // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true. 163 private String mCropValue; 164 private Uri mSaveUri; 165 166 private Uri mDebugUri; 167 168 // We use a queue to generated names of the images to be used later 169 // when the image is ready to be saved. 170 private NamedImages mNamedImages; 171 172 private final Runnable mDoSnapRunnable = new Runnable() { 173 @Override 174 public void run() { 175 onShutterButtonClick(); 176 } 177 }; 178 179 /** 180 * An unpublished intent flag requesting to return as soon as capturing is 181 * completed. TODO: consider publishing by moving into MediaStore. 182 */ 183 private static final String EXTRA_QUICK_CAPTURE = 184 "android.intent.extra.quickCapture"; 185 186 // The display rotation in degrees. This is only valid when mCameraState is 187 // not PREVIEW_STOPPED. 188 private int mDisplayRotation; 189 // The value for UI components like indicators. 190 private int mDisplayOrientation; 191 // The value for cameradevice.CameraSettings.setPhotoRotationDegrees. 192 private int mJpegRotation; 193 // Indicates whether we are using front camera 194 private boolean mMirror; 195 private boolean mFirstTimeInitialized; 196 private boolean mIsImageCaptureIntent; 197 198 private int mCameraState = PREVIEW_STOPPED; 199 private boolean mSnapshotOnIdle = false; 200 201 private ContentResolver mContentResolver; 202 203 private AppController mAppController; 204 205 private final PostViewPictureCallback mPostViewPictureCallback = 206 new PostViewPictureCallback(); 207 private final RawPictureCallback mRawPictureCallback = 208 new RawPictureCallback(); 209 private final AutoFocusCallback mAutoFocusCallback = 210 new AutoFocusCallback(); 211 private final Object mAutoFocusMoveCallback = 212 ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK 213 ? new AutoFocusMoveCallback() 214 : null; 215 216 private long mFocusStartTime; 217 private long mShutterCallbackTime; 218 private long mPostViewPictureCallbackTime; 219 private long mRawPictureCallbackTime; 220 private long mJpegPictureCallbackTime; 221 private long mOnResumeTime; 222 private byte[] mJpegImageData; 223 /** Touch coordinate for shutter button press. */ 224 private TouchCoordinate mShutterTouchCoordinate; 225 226 227 // These latency time are for the CameraLatency test. 228 public long mAutoFocusTime; 229 public long mShutterLag; 230 public long mShutterToPictureDisplayedTime; 231 public long mPictureDisplayedToJpegCallbackTime; 232 public long mJpegCallbackFinishTime; 233 public long mCaptureStartTime; 234 235 // This handles everything about focus. 236 private FocusOverlayManager mFocusManager; 237 238 private final int mGcamModeIndex; 239 private SoundPlayer mCountdownSoundPlayer; 240 241 private CameraCapabilities.SceneMode mSceneMode; 242 243 private final Handler mHandler = new MainHandler(this); 244 245 private boolean mQuickCapture; 246 247 /** Used to detect motion. We use this to release focus lock early. */ 248 private MotionManager mMotionManager; 249 250 private HeadingSensor mHeadingSensor; 251 252 /** True if all the parameters needed to start preview is ready. */ 253 private boolean mCameraPreviewParamsReady = false; 254 255 private final MediaSaver.OnMediaSavedListener mOnMediaSavedListener = 256 new MediaSaver.OnMediaSavedListener() { 257 @Override 258 public void onMediaSaved(Uri uri) { 259 if (uri != null) { 260 mActivity.notifyNewMedia(uri); 261 } 262 } 263 }; 264 private boolean mShouldResizeTo16x9 = false; 265 266 /** 267 * We keep the flash setting before entering scene modes (HDR) 268 * and restore it after HDR is off. 269 */ 270 private String mFlashModeBeforeSceneMode; 271 272 /** 273 * This callback gets called when user select whether or not to 274 * turn on geo-tagging. 275 */ 276 public interface LocationDialogCallback { 277 /** 278 * Gets called after user selected/unselected geo-tagging feature. 279 * 280 * @param selected whether or not geo-tagging feature is selected 281 */ 282 public void onLocationTaggingSelected(boolean selected); 283 } 284 285 /** 286 * This callback defines the text that is shown in the aspect ratio selection 287 * dialog, provides the current aspect ratio, and gets notified when user changes 288 * aspect ratio selection in the dialog. 289 */ 290 public interface AspectRatioDialogCallback { 291 /** 292 * Returns current aspect ratio that is being used to set as default. 293 */ 294 public Rational getCurrentAspectRatio(); 295 296 /** 297 * Gets notified when user has made the aspect ratio selection. 298 * 299 * @param chosenAspectRatio The aspect ratio that user has selected 300 * @param dialogHandlingFinishedRunnable runnable to run when the operations 301 * needed to handle changes from dialog 302 * are finished. 303 */ 304 public void onAspectRatioSelected( 305 Rational chosenAspectRatio, 306 Runnable dialogHandlingFinishedRunnable); 307 } 308 309 private void checkDisplayRotation() { 310 // Need to just be a no-op for the quick resume-pause scenario. 311 if (mPaused) { 312 return; 313 } 314 // Set the display orientation if display rotation has changed. 315 // Sometimes this happens when the device is held upside 316 // down and camera app is opened. Rotation animation will 317 // take some time and the rotation value we have got may be 318 // wrong. Framework does not have a callback for this now. 319 if (CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation) { 320 setDisplayOrientation(); 321 } 322 if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) { 323 mHandler.postDelayed(new Runnable() { 324 @Override 325 public void run() { 326 checkDisplayRotation(); 327 } 328 }, 100); 329 } 330 } 331 332 /** 333 * This Handler is used to post message back onto the main thread of the 334 * application 335 */ 336 private static class MainHandler extends Handler { 337 private final WeakReference<PhotoModule> mModule; 338 339 public MainHandler(PhotoModule module) { 340 super(Looper.getMainLooper()); 341 mModule = new WeakReference<PhotoModule>(module); 342 } 343 344 @Override 345 public void handleMessage(Message msg) { 346 PhotoModule module = mModule.get(); 347 if (module == null) { 348 return; 349 } 350 switch (msg.what) { 351 case MSG_FIRST_TIME_INIT: { 352 module.initializeFirstTime(); 353 break; 354 } 355 356 case MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE: { 357 module.setCameraParametersWhenIdle(0); 358 break; 359 } 360 } 361 } 362 } 363 364 private void switchToGcamCapture() { 365 if (mActivity != null && mGcamModeIndex != 0) { 366 SettingsManager settingsManager = mActivity.getSettingsManager(); 367 settingsManager.set(SettingsManager.SCOPE_GLOBAL, 368 Keys.KEY_CAMERA_HDR_PLUS, true); 369 370 // Disable the HDR+ button to prevent callbacks from being 371 // queued before the correct callback is attached to the button 372 // in the new module. The new module will set the enabled/disabled 373 // of this button when the module's preferred camera becomes available. 374 ButtonManager buttonManager = mActivity.getButtonManager(); 375 376 buttonManager.disableButtonClick(ButtonManager.BUTTON_HDR_PLUS); 377 378 mAppController.getCameraAppUI().freezeScreenUntilPreviewReady(); 379 380 // Do not post this to avoid this module switch getting interleaved with 381 // other button callbacks. 382 mActivity.onModeSelected(mGcamModeIndex); 383 384 buttonManager.enableButtonClick(ButtonManager.BUTTON_HDR_PLUS); 385 } 386 } 387 388 /** 389 * Constructs a new photo module. 390 */ 391 public PhotoModule(AppController app) { 392 super(app); 393 mGcamModeIndex = app.getAndroidContext().getResources() 394 .getInteger(R.integer.camera_mode_gcam); 395 } 396 397 @Override 398 public String getPeekAccessibilityString() { 399 return mAppController.getAndroidContext() 400 .getResources().getString(R.string.photo_accessibility_peek); 401 } 402 403 @Override 404 public String getModuleStringIdentifier() { 405 return PHOTO_MODULE_STRING_ID; 406 } 407 408 @Override 409 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) { 410 mActivity = activity; 411 // TODO: Need to look at the controller interface to see if we can get 412 // rid of passing in the activity directly. 413 mAppController = mActivity; 414 415 mUI = new PhotoUI(mActivity, this, mActivity.getModuleLayoutRoot()); 416 mActivity.setPreviewStatusListener(mUI); 417 418 SettingsManager settingsManager = mActivity.getSettingsManager(); 419 mCameraId = settingsManager.getInteger(mAppController.getModuleScope(), 420 Keys.KEY_CAMERA_ID); 421 422 // TODO: Move this to SettingsManager as a part of upgrade procedure. 423 if (!settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 424 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) { 425 // Switch to back camera to set aspect ratio. 426 mCameraId = settingsManager.getIntegerDefault(Keys.KEY_CAMERA_ID); 427 } 428 429 mContentResolver = mActivity.getContentResolver(); 430 431 // Surface texture is from camera screen nail and startPreview needs it. 432 // This must be done before startPreview. 433 mIsImageCaptureIntent = isImageCaptureIntent(); 434 435 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false); 436 mHeadingSensor = new HeadingSensor( 437 (SensorManager) mActivity.getSystemService(Context.SENSOR_SERVICE)); 438 mUI.setCountdownFinishedListener(this); 439 mCountdownSoundPlayer = new SoundPlayer(mAppController.getAndroidContext()); 440 441 // TODO: Make this a part of app controller API. 442 View cancelButton = mActivity.findViewById(R.id.shutter_cancel_button); 443 cancelButton.setOnClickListener(new View.OnClickListener() { 444 @Override 445 public void onClick(View view) { 446 cancelCountDown(); 447 } 448 }); 449 } 450 451 private void cancelCountDown() { 452 if (mUI.isCountingDown()) { 453 // Cancel on-going countdown. 454 mUI.cancelCountDown(); 455 } 456 mAppController.getCameraAppUI().transitionToCapture(); 457 mAppController.getCameraAppUI().showModeOptions(); 458 mAppController.setShutterEnabled(true); 459 } 460 461 @Override 462 public boolean isUsingBottomBar() { 463 return true; 464 } 465 466 private void initializeControlByIntent() { 467 if (mIsImageCaptureIntent) { 468 mActivity.getCameraAppUI().transitionToIntentCaptureLayout(); 469 setupCaptureParams(); 470 } 471 } 472 473 private void onPreviewStarted() { 474 mAppController.onPreviewStarted(); 475 mAppController.setShutterEnabled(true); 476 setCameraState(IDLE); 477 startFaceDetection(); 478 settingsFirstRun(); 479 } 480 481 /** 482 * Prompt the user to pick to record location and choose aspect ratio for the 483 * very first run of camera only. 484 */ 485 private void settingsFirstRun() { 486 final SettingsManager settingsManager = mActivity.getSettingsManager(); 487 488 if (mActivity.isSecureCamera() || isImageCaptureIntent()) { 489 return; 490 } 491 492 boolean locationPrompt = !settingsManager.isSet(SettingsManager.SCOPE_GLOBAL, 493 Keys.KEY_RECORD_LOCATION); 494 boolean aspectRatioPrompt = !settingsManager.getBoolean( 495 SettingsManager.SCOPE_GLOBAL, Keys.KEY_USER_SELECTED_ASPECT_RATIO); 496 if (!locationPrompt && !aspectRatioPrompt) { 497 return; 498 } 499 500 // Check if the back camera exists 501 int backCameraId = mAppController.getCameraProvider().getFirstBackCameraId(); 502 if (backCameraId == -1) { 503 // If there is no back camera, do not show the prompt. 504 return; 505 } 506 507 if (locationPrompt) { 508 // Show both location and aspect ratio selection dialog. 509 mUI.showLocationAndAspectRatioDialog(new LocationDialogCallback(){ 510 @Override 511 public void onLocationTaggingSelected(boolean selected) { 512 Keys.setLocation(mActivity.getSettingsManager(), selected, 513 mActivity.getLocationManager()); 514 } 515 }, createAspectRatioDialogCallback()); 516 } else { 517 // App upgrade. Only show aspect ratio selection. 518 boolean wasShown = mUI.showAspectRatioDialog(createAspectRatioDialogCallback()); 519 if (!wasShown) { 520 // If the dialog was not shown, set this flag to true so that we 521 // never have to check for it again. It means that we don't need 522 // to show the dialog on this device. 523 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL, 524 Keys.KEY_USER_SELECTED_ASPECT_RATIO, true); 525 } 526 } 527 } 528 529 private AspectRatioDialogCallback createAspectRatioDialogCallback() { 530 Size currentSize = new Size(mCameraSettings.getCurrentPhotoSize()); 531 final Rational currentAspectRatio = ResolutionUtil.getAspectRatio(currentSize); 532 AspectRatioDialogCallback callback = new AspectRatioDialogCallback() { 533 @Override 534 public Rational getCurrentAspectRatio() { 535 return currentAspectRatio; 536 } 537 538 @Override 539 public void onAspectRatioSelected( 540 Rational chosenAspectRatio, Runnable dialogHandlingFinishedRunnable) { 541 List<Size> supportedPhotoSizes = Size.convert(mCameraCapabilities.getSupportedPhotoSizes()); 542 Size largestPictureSize = ResolutionUtil.getLargestPictureSize( 543 chosenAspectRatio, supportedPhotoSizes); 544 mActivity.getSettingsManager().set( 545 SettingsManager.SCOPE_GLOBAL, 546 Keys.KEY_PICTURE_SIZE_BACK, 547 SettingsUtil.sizeToSettingString(largestPictureSize)); 548 mActivity.getSettingsManager().set( 549 SettingsManager.SCOPE_GLOBAL, 550 Keys.KEY_USER_SELECTED_ASPECT_RATIO, 551 true); 552 String aspectRatio = mActivity.getSettingsManager().getString( 553 SettingsManager.SCOPE_GLOBAL, 554 Keys.KEY_USER_SELECTED_ASPECT_RATIO); 555 Log.e(TAG, "aspect ratio after setting it to true=" + aspectRatio); 556 if (chosenAspectRatio != currentAspectRatio) { 557 Log.i(TAG, "changing aspect ratio from dialog"); 558 stopPreview(); 559 startPreview(); 560 mUI.setRunnableForNextFrame(dialogHandlingFinishedRunnable); 561 } else { 562 mHandler.post(dialogHandlingFinishedRunnable); 563 } 564 } 565 }; 566 return callback; 567 } 568 569 @Override 570 public void onPreviewUIReady() { 571 Log.i(TAG, "onPreviewUIReady"); 572 startPreview(); 573 } 574 575 @Override 576 public void onPreviewUIDestroyed() { 577 if (mCameraDevice == null) { 578 return; 579 } 580 mCameraDevice.setPreviewTexture(null); 581 stopPreview(); 582 } 583 584 @Override 585 public void startPreCaptureAnimation() { 586 mAppController.startFlashAnimation(false); 587 } 588 589 private void onCameraOpened() { 590 openCameraCommon(); 591 initializeControlByIntent(); 592 } 593 594 private void switchCamera() { 595 if (mPaused) { 596 return; 597 } 598 cancelCountDown(); 599 600 mAppController.freezeScreenUntilPreviewReady(); 601 SettingsManager settingsManager = mActivity.getSettingsManager(); 602 603 Log.i(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId); 604 closeCamera(); 605 mCameraId = mPendingSwitchCameraId; 606 607 settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID, mCameraId); 608 requestCameraOpen(); 609 mUI.clearFaces(); 610 if (mFocusManager != null) { 611 mFocusManager.removeMessages(); 612 } 613 614 mMirror = isCameraFrontFacing(); 615 mFocusManager.setMirror(mMirror); 616 // Start switch camera animation. Post a message because 617 // onFrameAvailable from the old camera may already exist. 618 } 619 620 /** 621 * Uses the {@link CameraProvider} to open the currently-selected camera 622 * device, using {@link GservicesHelper} to choose between API-1 and API-2. 623 */ 624 private void requestCameraOpen() { 625 Log.v(TAG, "requestCameraOpen"); 626 mActivity.getCameraProvider().requestCamera(mCameraId, 627 GservicesHelper.useCamera2ApiThroughPortabilityLayer(mActivity)); 628 } 629 630 private final ButtonManager.ButtonCallback mCameraCallback = 631 new ButtonManager.ButtonCallback() { 632 @Override 633 public void onStateChanged(int state) { 634 // At the time this callback is fired, the camera id 635 // has be set to the desired camera. 636 637 if (mPaused || mAppController.getCameraProvider().waitingForCamera()) { 638 return; 639 } 640 // If switching to back camera, and HDR+ is still on, 641 // switch back to gcam, otherwise handle callback normally. 642 SettingsManager settingsManager = mActivity.getSettingsManager(); 643 if (Keys.isCameraBackFacing(settingsManager, 644 mAppController.getModuleScope())) { 645 if (Keys.requestsReturnToHdrPlus(settingsManager, 646 mAppController.getModuleScope())) { 647 switchToGcamCapture(); 648 return; 649 } 650 } 651 652 mPendingSwitchCameraId = state; 653 654 Log.d(TAG, "Start to switch camera. cameraId=" + state); 655 // We need to keep a preview frame for the animation before 656 // releasing the camera. This will trigger 657 // onPreviewTextureCopied. 658 // TODO: Need to animate the camera switch 659 switchCamera(); 660 } 661 }; 662 663 private final ButtonManager.ButtonCallback mHdrPlusCallback = 664 new ButtonManager.ButtonCallback() { 665 @Override 666 public void onStateChanged(int state) { 667 SettingsManager settingsManager = mActivity.getSettingsManager(); 668 if (GcamHelper.hasGcamAsSeparateModule()) { 669 // Set the camera setting to default backfacing. 670 settingsManager.setToDefault(mAppController.getModuleScope(), 671 Keys.KEY_CAMERA_ID); 672 switchToGcamCapture(); 673 } else { 674 if (Keys.isHdrOn(settingsManager)) { 675 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_SCENE_MODE, 676 mCameraCapabilities.getStringifier().stringify( 677 CameraCapabilities.SceneMode.HDR)); 678 } else { 679 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_SCENE_MODE, 680 mCameraCapabilities.getStringifier().stringify( 681 CameraCapabilities.SceneMode.AUTO)); 682 } 683 updateParametersSceneMode(); 684 if (mCameraDevice != null) { 685 mCameraDevice.applySettings(mCameraSettings); 686 } 687 updateSceneMode(); 688 } 689 } 690 }; 691 692 private final View.OnClickListener mCancelCallback = new View.OnClickListener() { 693 @Override 694 public void onClick(View v) { 695 onCaptureCancelled(); 696 } 697 }; 698 699 private final View.OnClickListener mDoneCallback = new View.OnClickListener() { 700 @Override 701 public void onClick(View v) { 702 onCaptureDone(); 703 } 704 }; 705 706 private final View.OnClickListener mRetakeCallback = new View.OnClickListener() { 707 @Override 708 public void onClick(View v) { 709 mActivity.getCameraAppUI().transitionToIntentCaptureLayout(); 710 onCaptureRetake(); 711 } 712 }; 713 714 @Override 715 public void hardResetSettings(SettingsManager settingsManager) { 716 // PhotoModule should hard reset HDR+ to off, 717 // and HDR to off if HDR+ is supported. 718 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false); 719 if (GcamHelper.hasGcamAsSeparateModule()) { 720 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR, false); 721 } 722 } 723 724 @Override 725 public HardwareSpec getHardwareSpec() { 726 return (mCameraSettings != null ? 727 new HardwareSpecImpl(getCameraProvider(), mCameraCapabilities) : null); 728 } 729 730 @Override 731 public CameraAppUI.BottomBarUISpec getBottomBarSpec() { 732 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec(); 733 734 bottomBarSpec.enableCamera = true; 735 bottomBarSpec.cameraCallback = mCameraCallback; 736 bottomBarSpec.enableFlash = !mAppController.getSettingsManager() 737 .getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR); 738 bottomBarSpec.enableHdr = true; 739 bottomBarSpec.hdrCallback = mHdrPlusCallback; 740 bottomBarSpec.enableGridLines = true; 741 if (mCameraCapabilities != null) { 742 bottomBarSpec.enableExposureCompensation = true; 743 bottomBarSpec.exposureCompensationSetCallback = 744 new CameraAppUI.BottomBarUISpec.ExposureCompensationSetCallback() { 745 @Override 746 public void setExposure(int value) { 747 setExposureCompensation(value); 748 } 749 }; 750 bottomBarSpec.minExposureCompensation = 751 mCameraCapabilities.getMinExposureCompensation(); 752 bottomBarSpec.maxExposureCompensation = 753 mCameraCapabilities.getMaxExposureCompensation(); 754 bottomBarSpec.exposureCompensationStep = 755 mCameraCapabilities.getExposureCompensationStep(); 756 } 757 758 bottomBarSpec.enableSelfTimer = true; 759 bottomBarSpec.showSelfTimer = true; 760 761 if (isImageCaptureIntent()) { 762 bottomBarSpec.showCancel = true; 763 bottomBarSpec.cancelCallback = mCancelCallback; 764 bottomBarSpec.showDone = true; 765 bottomBarSpec.doneCallback = mDoneCallback; 766 bottomBarSpec.showRetake = true; 767 bottomBarSpec.retakeCallback = mRetakeCallback; 768 } 769 770 return bottomBarSpec; 771 } 772 773 // either open a new camera or switch cameras 774 private void openCameraCommon() { 775 mUI.onCameraOpened(mCameraCapabilities, mCameraSettings); 776 if (mIsImageCaptureIntent) { 777 // Set hdr plus to default: off. 778 SettingsManager settingsManager = mActivity.getSettingsManager(); 779 settingsManager.setToDefault(SettingsManager.SCOPE_GLOBAL, 780 Keys.KEY_CAMERA_HDR_PLUS); 781 } 782 updateSceneMode(); 783 } 784 785 @Override 786 public void updatePreviewAspectRatio(float aspectRatio) { 787 mAppController.updatePreviewAspectRatio(aspectRatio); 788 } 789 790 private void resetExposureCompensation() { 791 SettingsManager settingsManager = mActivity.getSettingsManager(); 792 if (settingsManager == null) { 793 Log.e(TAG, "Settings manager is null!"); 794 return; 795 } 796 settingsManager.setToDefault(mAppController.getCameraScope(), 797 Keys.KEY_EXPOSURE); 798 } 799 800 // Snapshots can only be taken after this is called. It should be called 801 // once only. We could have done these things in onCreate() but we want to 802 // make preview screen appear as soon as possible. 803 private void initializeFirstTime() { 804 if (mFirstTimeInitialized || mPaused) { 805 return; 806 } 807 808 mUI.initializeFirstTime(); 809 810 // We set the listener only when both service and shutterbutton 811 // are initialized. 812 getServices().getMemoryManager().addListener(this); 813 814 mNamedImages = new NamedImages(); 815 816 mFirstTimeInitialized = true; 817 addIdleHandler(); 818 819 mActivity.updateStorageSpaceAndHint(null); 820 } 821 822 // If the activity is paused and resumed, this method will be called in 823 // onResume. 824 private void initializeSecondTime() { 825 getServices().getMemoryManager().addListener(this); 826 mNamedImages = new NamedImages(); 827 mUI.initializeSecondTime(mCameraCapabilities, mCameraSettings); 828 } 829 830 private void addIdleHandler() { 831 MessageQueue queue = Looper.myQueue(); 832 queue.addIdleHandler(new MessageQueue.IdleHandler() { 833 @Override 834 public boolean queueIdle() { 835 Storage.ensureOSXCompatible(); 836 return false; 837 } 838 }); 839 } 840 841 @Override 842 public void startFaceDetection() { 843 if (mFaceDetectionStarted || mCameraDevice == null) { 844 return; 845 } 846 if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) { 847 mFaceDetectionStarted = true; 848 mUI.onStartFaceDetection(mDisplayOrientation, isCameraFrontFacing()); 849 mCameraDevice.setFaceDetectionCallback(mHandler, mUI); 850 mCameraDevice.startFaceDetection(); 851 SessionStatsCollector.instance().faceScanActive(true); 852 } 853 } 854 855 @Override 856 public void stopFaceDetection() { 857 if (!mFaceDetectionStarted || mCameraDevice == null) { 858 return; 859 } 860 if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) { 861 mFaceDetectionStarted = false; 862 mCameraDevice.setFaceDetectionCallback(null, null); 863 mCameraDevice.stopFaceDetection(); 864 mUI.clearFaces(); 865 SessionStatsCollector.instance().faceScanActive(false); 866 } 867 } 868 869 private final class ShutterCallback 870 implements CameraShutterCallback { 871 872 private final boolean mNeedsAnimation; 873 874 public ShutterCallback(boolean needsAnimation) { 875 mNeedsAnimation = needsAnimation; 876 } 877 878 @Override 879 public void onShutter(CameraProxy camera) { 880 mShutterCallbackTime = System.currentTimeMillis(); 881 mShutterLag = mShutterCallbackTime - mCaptureStartTime; 882 Log.v(TAG, "mShutterLag = " + mShutterLag + "ms"); 883 if (mNeedsAnimation) { 884 mActivity.runOnUiThread(new Runnable() { 885 @Override 886 public void run() { 887 animateAfterShutter(); 888 } 889 }); 890 } 891 } 892 } 893 894 private final class PostViewPictureCallback 895 implements CameraPictureCallback { 896 @Override 897 public void onPictureTaken(byte[] data, CameraProxy camera) { 898 mPostViewPictureCallbackTime = System.currentTimeMillis(); 899 Log.v(TAG, "mShutterToPostViewCallbackTime = " 900 + (mPostViewPictureCallbackTime - mShutterCallbackTime) 901 + "ms"); 902 } 903 } 904 905 private final class RawPictureCallback 906 implements CameraPictureCallback { 907 @Override 908 public void onPictureTaken(byte[] rawData, CameraProxy camera) { 909 mRawPictureCallbackTime = System.currentTimeMillis(); 910 Log.v(TAG, "mShutterToRawCallbackTime = " 911 + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms"); 912 } 913 } 914 915 private static class ResizeBundle { 916 byte[] jpegData; 917 float targetAspectRatio; 918 ExifInterface exif; 919 } 920 921 /** 922 * @return Cropped image if the target aspect ratio is larger than the jpeg 923 * aspect ratio on the long axis. The original jpeg otherwise. 924 */ 925 private ResizeBundle cropJpegDataToAspectRatio(ResizeBundle dataBundle) { 926 927 final byte[] jpegData = dataBundle.jpegData; 928 final ExifInterface exif = dataBundle.exif; 929 float targetAspectRatio = dataBundle.targetAspectRatio; 930 931 Bitmap original = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); 932 int originalWidth = original.getWidth(); 933 int originalHeight = original.getHeight(); 934 int newWidth; 935 int newHeight; 936 937 if (originalWidth > originalHeight) { 938 newHeight = (int) (originalWidth / targetAspectRatio); 939 newWidth = originalWidth; 940 } else { 941 newWidth = (int) (originalHeight / targetAspectRatio); 942 newHeight = originalHeight; 943 } 944 int xOffset = (originalWidth - newWidth)/2; 945 int yOffset = (originalHeight - newHeight)/2; 946 947 if (xOffset < 0 || yOffset < 0) { 948 return dataBundle; 949 } 950 951 Bitmap resized = Bitmap.createBitmap(original,xOffset,yOffset,newWidth, newHeight); 952 exif.setTagValue(ExifInterface.TAG_PIXEL_X_DIMENSION, new Integer(newWidth)); 953 exif.setTagValue(ExifInterface.TAG_PIXEL_Y_DIMENSION, new Integer(newHeight)); 954 955 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 956 957 resized.compress(Bitmap.CompressFormat.JPEG, 90, stream); 958 dataBundle.jpegData = stream.toByteArray(); 959 return dataBundle; 960 } 961 962 private final class JpegPictureCallback 963 implements CameraPictureCallback { 964 Location mLocation; 965 966 public JpegPictureCallback(Location loc) { 967 mLocation = loc; 968 } 969 970 @Override 971 public void onPictureTaken(final byte[] originalJpegData, final CameraProxy camera) { 972 Log.i(TAG, "onPictureTaken"); 973 mAppController.setShutterEnabled(true); 974 if (mPaused) { 975 return; 976 } 977 if (mIsImageCaptureIntent) { 978 stopPreview(); 979 } 980 if (mSceneMode == CameraCapabilities.SceneMode.HDR) { 981 mUI.setSwipingEnabled(true); 982 } 983 984 mJpegPictureCallbackTime = System.currentTimeMillis(); 985 // If postview callback has arrived, the captured image is displayed 986 // in postview callback. If not, the captured image is displayed in 987 // raw picture callback. 988 if (mPostViewPictureCallbackTime != 0) { 989 mShutterToPictureDisplayedTime = 990 mPostViewPictureCallbackTime - mShutterCallbackTime; 991 mPictureDisplayedToJpegCallbackTime = 992 mJpegPictureCallbackTime - mPostViewPictureCallbackTime; 993 } else { 994 mShutterToPictureDisplayedTime = 995 mRawPictureCallbackTime - mShutterCallbackTime; 996 mPictureDisplayedToJpegCallbackTime = 997 mJpegPictureCallbackTime - mRawPictureCallbackTime; 998 } 999 Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = " 1000 + mPictureDisplayedToJpegCallbackTime + "ms"); 1001 1002 if (!mIsImageCaptureIntent) { 1003 setupPreview(); 1004 } 1005 1006 long now = System.currentTimeMillis(); 1007 mJpegCallbackFinishTime = now - mJpegPictureCallbackTime; 1008 Log.v(TAG, "mJpegCallbackFinishTime = " + mJpegCallbackFinishTime + "ms"); 1009 mJpegPictureCallbackTime = 0; 1010 1011 final ExifInterface exif = Exif.getExif(originalJpegData); 1012 final NamedEntity name = mNamedImages.getNextNameEntity(); 1013 if (mShouldResizeTo16x9) { 1014 final ResizeBundle dataBundle = new ResizeBundle(); 1015 dataBundle.jpegData = originalJpegData; 1016 dataBundle.targetAspectRatio = ResolutionUtil.NEXUS_5_LARGE_16_BY_9_ASPECT_RATIO; 1017 dataBundle.exif = exif; 1018 new AsyncTask<ResizeBundle, Void, ResizeBundle>() { 1019 1020 @Override 1021 protected ResizeBundle doInBackground(ResizeBundle... resizeBundles) { 1022 return cropJpegDataToAspectRatio(resizeBundles[0]); 1023 } 1024 1025 @Override 1026 protected void onPostExecute(ResizeBundle result) { 1027 saveFinalPhoto(result.jpegData, name, result.exif, camera); 1028 } 1029 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dataBundle); 1030 1031 } else { 1032 saveFinalPhoto(originalJpegData, name, exif, camera); 1033 } 1034 } 1035 1036 void saveFinalPhoto(final byte[] jpegData, NamedEntity name, final ExifInterface exif, 1037 CameraProxy camera) { 1038 int orientation = Exif.getOrientation(exif); 1039 1040 float zoomValue = 1.0f; 1041 if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) { 1042 zoomValue = mCameraSettings.getCurrentZoomRatio(); 1043 } 1044 boolean hdrOn = CameraCapabilities.SceneMode.HDR == mSceneMode; 1045 String flashSetting = 1046 mActivity.getSettingsManager().getString(mAppController.getCameraScope(), 1047 Keys.KEY_FLASH_MODE); 1048 boolean gridLinesOn = Keys.areGridLinesOn(mActivity.getSettingsManager()); 1049 UsageStatistics.instance().photoCaptureDoneEvent( 1050 eventprotos.NavigationChange.Mode.PHOTO_CAPTURE, 1051 name.title + ".jpg", exif, 1052 isCameraFrontFacing(), hdrOn, zoomValue, flashSetting, gridLinesOn, 1053 (float) mTimerDuration, mShutterTouchCoordinate, mVolumeButtonClickedFlag); 1054 mShutterTouchCoordinate = null; 1055 mVolumeButtonClickedFlag = false; 1056 1057 if (!mIsImageCaptureIntent) { 1058 // Calculate the width and the height of the jpeg. 1059 Integer exifWidth = exif.getTagIntValue(ExifInterface.TAG_PIXEL_X_DIMENSION); 1060 Integer exifHeight = exif.getTagIntValue(ExifInterface.TAG_PIXEL_Y_DIMENSION); 1061 int width, height; 1062 if (mShouldResizeTo16x9 && exifWidth != null && exifHeight != null) { 1063 width = exifWidth; 1064 height = exifHeight; 1065 } else { 1066 Size s = new Size(mCameraSettings.getCurrentPhotoSize()); 1067 if ((mJpegRotation + orientation) % 180 == 0) { 1068 width = s.width(); 1069 height = s.height(); 1070 } else { 1071 width = s.height(); 1072 height = s.width(); 1073 } 1074 } 1075 String title = (name == null) ? null : name.title; 1076 long date = (name == null) ? -1 : name.date; 1077 1078 // Handle debug mode outputs 1079 if (mDebugUri != null) { 1080 // If using a debug uri, save jpeg there. 1081 saveToDebugUri(jpegData); 1082 1083 // Adjust the title of the debug image shown in mediastore. 1084 if (title != null) { 1085 title = DEBUG_IMAGE_PREFIX + title; 1086 } 1087 } 1088 1089 if (title == null) { 1090 Log.e(TAG, "Unbalanced name/data pair"); 1091 } else { 1092 if (date == -1) { 1093 date = mCaptureStartTime; 1094 } 1095 int heading = mHeadingSensor.getCurrentHeading(); 1096 if (heading != HeadingSensor.INVALID_HEADING) { 1097 // heading direction has been updated by the sensor. 1098 ExifTag directionRefTag = exif.buildTag( 1099 ExifInterface.TAG_GPS_IMG_DIRECTION_REF, 1100 ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION); 1101 ExifTag directionTag = exif.buildTag( 1102 ExifInterface.TAG_GPS_IMG_DIRECTION, 1103 new Rational(heading, 1)); 1104 exif.setTag(directionRefTag); 1105 exif.setTag(directionTag); 1106 } 1107 getServices().getMediaSaver().addImage( 1108 jpegData, title, date, mLocation, width, height, 1109 orientation, exif, mOnMediaSavedListener, mContentResolver); 1110 } 1111 // Animate capture with real jpeg data instead of a preview 1112 // frame. 1113 mUI.animateCapture(jpegData, orientation, mMirror); 1114 } else { 1115 mJpegImageData = jpegData; 1116 if (!mQuickCapture) { 1117 Log.v(TAG, "showing UI"); 1118 mUI.showCapturedImageForReview(jpegData, orientation, mMirror); 1119 } else { 1120 onCaptureDone(); 1121 } 1122 } 1123 1124 // Send the taken photo to remote shutter listeners, if any are 1125 // registered. 1126 getServices().getRemoteShutterListener().onPictureTaken(jpegData); 1127 1128 // Check this in advance of each shot so we don't add to shutter 1129 // latency. It's true that someone else could write to the SD card 1130 // in the mean time and fill it, but that could have happened 1131 // between the shutter press and saving the JPEG too. 1132 mActivity.updateStorageSpaceAndHint(null); 1133 } 1134 } 1135 1136 private final class AutoFocusCallback implements CameraAFCallback { 1137 @Override 1138 public void onAutoFocus(boolean focused, CameraProxy camera) { 1139 SessionStatsCollector.instance().autofocusResult(focused); 1140 if (mPaused) { 1141 return; 1142 } 1143 1144 mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime; 1145 Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms focused = "+focused); 1146 setCameraState(IDLE); 1147 mFocusManager.onAutoFocus(focused, false); 1148 } 1149 } 1150 1151 private final class AutoFocusMoveCallback 1152 implements CameraAFMoveCallback { 1153 @Override 1154 public void onAutoFocusMoving( 1155 boolean moving, CameraProxy camera) { 1156 mFocusManager.onAutoFocusMoving(moving); 1157 SessionStatsCollector.instance().autofocusMoving(moving); 1158 } 1159 } 1160 1161 /** 1162 * This class is just a thread-safe queue for name,date holder objects. 1163 */ 1164 public static class NamedImages { 1165 private final Vector<NamedEntity> mQueue; 1166 1167 public NamedImages() { 1168 mQueue = new Vector<NamedEntity>(); 1169 } 1170 1171 public void nameNewImage(long date) { 1172 NamedEntity r = new NamedEntity(); 1173 r.title = CameraUtil.createJpegName(date); 1174 r.date = date; 1175 mQueue.add(r); 1176 } 1177 1178 public NamedEntity getNextNameEntity() { 1179 synchronized (mQueue) { 1180 if (!mQueue.isEmpty()) { 1181 return mQueue.remove(0); 1182 } 1183 } 1184 return null; 1185 } 1186 1187 public static class NamedEntity { 1188 public String title; 1189 public long date; 1190 } 1191 } 1192 1193 private void setCameraState(int state) { 1194 mCameraState = state; 1195 switch (state) { 1196 case PREVIEW_STOPPED: 1197 case SNAPSHOT_IN_PROGRESS: 1198 case SWITCHING_CAMERA: 1199 // TODO: Tell app UI to disable swipe 1200 break; 1201 case PhotoController.IDLE: 1202 // TODO: Tell app UI to enable swipe 1203 break; 1204 } 1205 } 1206 1207 private void animateAfterShutter() { 1208 // Only animate when in full screen capture mode 1209 // i.e. If monkey/a user swipes to the gallery during picture taking, 1210 // don't show animation 1211 if (!mIsImageCaptureIntent) { 1212 mUI.animateFlash(); 1213 } 1214 } 1215 1216 @Override 1217 public boolean capture() { 1218 Log.i(TAG, "capture"); 1219 // If we are already in the middle of taking a snapshot or the image 1220 // save request is full then ignore. 1221 if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS 1222 || mCameraState == SWITCHING_CAMERA) { 1223 return false; 1224 } 1225 setCameraState(SNAPSHOT_IN_PROGRESS); 1226 1227 mCaptureStartTime = System.currentTimeMillis(); 1228 1229 mPostViewPictureCallbackTime = 0; 1230 mJpegImageData = null; 1231 1232 final boolean animateBefore = (mSceneMode == CameraCapabilities.SceneMode.HDR); 1233 1234 if (animateBefore) { 1235 animateAfterShutter(); 1236 } 1237 1238 Location loc = mActivity.getLocationManager().getCurrentLocation(); 1239 CameraUtil.setGpsParameters(mCameraSettings, loc); 1240 mCameraDevice.applySettings(mCameraSettings); 1241 1242 // Set JPEG orientation. Even if screen UI is locked in portrait, camera orientation should 1243 // still match device orientation (e.g., users should always get landscape photos while 1244 // capturing by putting device in landscape.) 1245 Characteristics info = mActivity.getCameraProvider().getCharacteristics(mCameraId); 1246 int sensorOrientation = info.getSensorOrientation(); 1247 int deviceOrientation = 1248 mAppController.getOrientationManager().getDeviceOrientation().getDegrees(); 1249 boolean isFrontCamera = info.isFacingFront(); 1250 mJpegRotation = 1251 CameraUtil.getImageRotation(sensorOrientation, deviceOrientation, isFrontCamera); 1252 mCameraDevice.setJpegOrientation(mJpegRotation); 1253 1254 mCameraDevice.takePicture(mHandler, 1255 new ShutterCallback(!animateBefore), 1256 mRawPictureCallback, mPostViewPictureCallback, 1257 new JpegPictureCallback(loc)); 1258 1259 mNamedImages.nameNewImage(mCaptureStartTime); 1260 1261 mFaceDetectionStarted = false; 1262 return true; 1263 } 1264 1265 @Override 1266 public void setFocusParameters() { 1267 setCameraParameters(UPDATE_PARAM_PREFERENCE); 1268 } 1269 1270 private void updateSceneMode() { 1271 // If scene mode is set, we cannot set flash mode, white balance, and 1272 // focus mode, instead, we read it from driver. Some devices don't have 1273 // any scene modes, so we must check both NO_SCENE_MODE in addition to 1274 // AUTO to check where there is no actual scene mode set. 1275 if (!(CameraCapabilities.SceneMode.AUTO == mSceneMode || 1276 CameraCapabilities.SceneMode.NO_SCENE_MODE == mSceneMode)) { 1277 overrideCameraSettings(mCameraSettings.getCurrentFlashMode(), 1278 mCameraSettings.getCurrentFocusMode()); 1279 } 1280 } 1281 1282 private void overrideCameraSettings(CameraCapabilities.FlashMode flashMode, 1283 CameraCapabilities.FocusMode focusMode) { 1284 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier(); 1285 SettingsManager settingsManager = mActivity.getSettingsManager(); 1286 if (!CameraCapabilities.FlashMode.NO_FLASH.equals(flashMode)) { 1287 String flashModeString = stringifier.stringify(flashMode); 1288 Log.v(TAG, "override flash setting to: " + flashModeString); 1289 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FLASH_MODE, 1290 flashModeString); 1291 } else { 1292 Log.v(TAG, "skip setting flash mode on override due to NO_FLASH"); 1293 } 1294 String focusModeString = stringifier.stringify(focusMode); 1295 Log.v(TAG, "override focus setting to: " + focusModeString); 1296 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FOCUS_MODE, 1297 focusModeString); 1298 } 1299 1300 @Override 1301 public void onCameraAvailable(CameraProxy cameraProxy) { 1302 Log.i(TAG, "onCameraAvailable"); 1303 if (mPaused) { 1304 return; 1305 } 1306 mCameraDevice = cameraProxy; 1307 1308 initializeCapabilities(); 1309 1310 // Reset zoom value index. 1311 mZoomValue = 1.0f; 1312 if (mFocusManager == null) { 1313 initializeFocusManager(); 1314 } 1315 mFocusManager.updateCapabilities(mCameraCapabilities); 1316 1317 // Do camera parameter dependent initialization. 1318 mCameraSettings = mCameraDevice.getSettings(); 1319 1320 setCameraParameters(UPDATE_PARAM_ALL); 1321 // Set a listener which updates camera parameters based 1322 // on changed settings. 1323 SettingsManager settingsManager = mActivity.getSettingsManager(); 1324 settingsManager.addListener(this); 1325 mCameraPreviewParamsReady = true; 1326 1327 startPreview(); 1328 1329 onCameraOpened(); 1330 } 1331 1332 @Override 1333 public void onCaptureCancelled() { 1334 mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent()); 1335 mActivity.finish(); 1336 } 1337 1338 @Override 1339 public void onCaptureRetake() { 1340 Log.i(TAG, "onCaptureRetake"); 1341 if (mPaused) { 1342 return; 1343 } 1344 mUI.hidePostCaptureAlert(); 1345 mUI.hideIntentReviewImageView(); 1346 setupPreview(); 1347 } 1348 1349 @Override 1350 public void onCaptureDone() { 1351 Log.i(TAG, "onCaptureDone"); 1352 if (mPaused) { 1353 return; 1354 } 1355 1356 byte[] data = mJpegImageData; 1357 1358 if (mCropValue == null) { 1359 // First handle the no crop case -- just return the value. If the 1360 // caller specifies a "save uri" then write the data to its 1361 // stream. Otherwise, pass back a scaled down version of the bitmap 1362 // directly in the extras. 1363 if (mSaveUri != null) { 1364 OutputStream outputStream = null; 1365 try { 1366 outputStream = mContentResolver.openOutputStream(mSaveUri); 1367 outputStream.write(data); 1368 outputStream.close(); 1369 1370 Log.v(TAG, "saved result to URI: " + mSaveUri); 1371 mActivity.setResultEx(Activity.RESULT_OK); 1372 mActivity.finish(); 1373 } catch (IOException ex) { 1374 Log.w(TAG, "exception saving result to URI: " + mSaveUri, ex); 1375 // ignore exception 1376 } finally { 1377 CameraUtil.closeSilently(outputStream); 1378 } 1379 } else { 1380 ExifInterface exif = Exif.getExif(data); 1381 int orientation = Exif.getOrientation(exif); 1382 Bitmap bitmap = CameraUtil.makeBitmap(data, 50 * 1024); 1383 bitmap = CameraUtil.rotate(bitmap, orientation); 1384 Log.v(TAG, "inlined bitmap into capture intent result"); 1385 mActivity.setResultEx(Activity.RESULT_OK, 1386 new Intent("inline-data").putExtra("data", bitmap)); 1387 mActivity.finish(); 1388 } 1389 } else { 1390 // Save the image to a temp file and invoke the cropper 1391 Uri tempUri = null; 1392 FileOutputStream tempStream = null; 1393 try { 1394 File path = mActivity.getFileStreamPath(sTempCropFilename); 1395 path.delete(); 1396 tempStream = mActivity.openFileOutput(sTempCropFilename, 0); 1397 tempStream.write(data); 1398 tempStream.close(); 1399 tempUri = Uri.fromFile(path); 1400 Log.v(TAG, "wrote temp file for cropping to: " + sTempCropFilename); 1401 } catch (FileNotFoundException ex) { 1402 Log.w(TAG, "error writing temp cropping file to: " + sTempCropFilename, ex); 1403 mActivity.setResultEx(Activity.RESULT_CANCELED); 1404 mActivity.finish(); 1405 return; 1406 } catch (IOException ex) { 1407 Log.w(TAG, "error writing temp cropping file to: " + sTempCropFilename, ex); 1408 mActivity.setResultEx(Activity.RESULT_CANCELED); 1409 mActivity.finish(); 1410 return; 1411 } finally { 1412 CameraUtil.closeSilently(tempStream); 1413 } 1414 1415 Bundle newExtras = new Bundle(); 1416 if (mCropValue.equals("circle")) { 1417 newExtras.putString("circleCrop", "true"); 1418 } 1419 if (mSaveUri != null) { 1420 Log.v(TAG, "setting output of cropped file to: " + mSaveUri); 1421 newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri); 1422 } else { 1423 newExtras.putBoolean(CameraUtil.KEY_RETURN_DATA, true); 1424 } 1425 if (mActivity.isSecureCamera()) { 1426 newExtras.putBoolean(CameraUtil.KEY_SHOW_WHEN_LOCKED, true); 1427 } 1428 1429 // TODO: Share this constant. 1430 final String CROP_ACTION = "com.android.camera.action.CROP"; 1431 Intent cropIntent = new Intent(CROP_ACTION); 1432 1433 cropIntent.setData(tempUri); 1434 cropIntent.putExtras(newExtras); 1435 Log.v(TAG, "starting CROP intent for capture"); 1436 mActivity.startActivityForResult(cropIntent, REQUEST_CROP); 1437 } 1438 } 1439 1440 @Override 1441 public void onShutterCoordinate(TouchCoordinate coord) { 1442 mShutterTouchCoordinate = coord; 1443 } 1444 1445 @Override 1446 public void onShutterButtonFocus(boolean pressed) { 1447 // Do nothing. We don't support half-press to focus anymore. 1448 } 1449 1450 @Override 1451 public void onShutterButtonClick() { 1452 if (mPaused || (mCameraState == SWITCHING_CAMERA) 1453 || (mCameraState == PREVIEW_STOPPED) 1454 || !mAppController.isShutterEnabled()) { 1455 mVolumeButtonClickedFlag = false; 1456 return; 1457 } 1458 1459 // Do not take the picture if there is not enough storage. 1460 if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) { 1461 Log.i(TAG, "Not enough space or storage not ready. remaining=" 1462 + mActivity.getStorageSpaceBytes()); 1463 mVolumeButtonClickedFlag = false; 1464 return; 1465 } 1466 Log.d(TAG, "onShutterButtonClick: mCameraState=" + mCameraState + 1467 " mVolumeButtonClickedFlag=" + mVolumeButtonClickedFlag); 1468 1469 mAppController.setShutterEnabled(false); 1470 1471 int countDownDuration = mActivity.getSettingsManager() 1472 .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION); 1473 mTimerDuration = countDownDuration; 1474 if (countDownDuration > 0) { 1475 // Start count down. 1476 mAppController.getCameraAppUI().transitionToCancel(); 1477 mAppController.getCameraAppUI().hideModeOptions(); 1478 mUI.startCountdown(countDownDuration); 1479 return; 1480 } else { 1481 focusAndCapture(); 1482 } 1483 } 1484 1485 private void focusAndCapture() { 1486 if (mSceneMode == CameraCapabilities.SceneMode.HDR) { 1487 mUI.setSwipingEnabled(false); 1488 } 1489 // If the user wants to do a snapshot while the previous one is still 1490 // in progress, remember the fact and do it after we finish the previous 1491 // one and re-start the preview. Snapshot in progress also includes the 1492 // state that autofocus is focusing and a picture will be taken when 1493 // focus callback arrives. 1494 if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)) { 1495 if (!mIsImageCaptureIntent) { 1496 mSnapshotOnIdle = true; 1497 } 1498 return; 1499 } 1500 1501 mSnapshotOnIdle = false; 1502 mFocusManager.focusAndCapture(mCameraSettings.getCurrentFocusMode()); 1503 } 1504 1505 @Override 1506 public void onRemainingSecondsChanged(int remainingSeconds) { 1507 if (remainingSeconds == 1) { 1508 mCountdownSoundPlayer.play(R.raw.timer_final_second, 0.6f); 1509 } else if (remainingSeconds == 2 || remainingSeconds == 3) { 1510 mCountdownSoundPlayer.play(R.raw.timer_increment, 0.6f); 1511 } 1512 } 1513 1514 @Override 1515 public void onCountDownFinished() { 1516 mAppController.getCameraAppUI().transitionToCapture(); 1517 mAppController.getCameraAppUI().showModeOptions(); 1518 if (mPaused) { 1519 return; 1520 } 1521 focusAndCapture(); 1522 } 1523 1524 @Override 1525 public void resume() { 1526 mPaused = false; 1527 1528 mCountdownSoundPlayer.loadSound(R.raw.timer_final_second); 1529 mCountdownSoundPlayer.loadSound(R.raw.timer_increment); 1530 if (mFocusManager != null) { 1531 // If camera is not open when resume is called, focus manager will 1532 // not be initialized yet, in which case it will start listening to 1533 // preview area size change later in the initialization. 1534 mAppController.addPreviewAreaSizeChangedListener(mFocusManager); 1535 } 1536 mAppController.addPreviewAreaSizeChangedListener(mUI); 1537 1538 CameraProvider camProvider = mActivity.getCameraProvider(); 1539 if (camProvider == null) { 1540 // No camera provider, the Activity is destroyed already. 1541 return; 1542 } 1543 requestCameraOpen(); 1544 1545 mJpegPictureCallbackTime = 0; 1546 mZoomValue = 1.0f; 1547 1548 mOnResumeTime = SystemClock.uptimeMillis(); 1549 checkDisplayRotation(); 1550 1551 // If first time initialization is not finished, put it in the 1552 // message queue. 1553 if (!mFirstTimeInitialized) { 1554 mHandler.sendEmptyMessage(MSG_FIRST_TIME_INIT); 1555 } else { 1556 initializeSecondTime(); 1557 } 1558 1559 mHeadingSensor.activate(); 1560 1561 getServices().getRemoteShutterListener().onModuleReady(this); 1562 SessionStatsCollector.instance().sessionActive(true); 1563 } 1564 1565 /** 1566 * @return Whether the currently active camera is front-facing. 1567 */ 1568 private boolean isCameraFrontFacing() { 1569 return mAppController.getCameraProvider().getCharacteristics(mCameraId) 1570 .isFacingFront(); 1571 } 1572 1573 /** 1574 * The focus manager is the first UI related element to get initialized, and 1575 * it requires the RenderOverlay, so initialize it here 1576 */ 1577 private void initializeFocusManager() { 1578 // Create FocusManager object. startPreview needs it. 1579 // if mFocusManager not null, reuse it 1580 // otherwise create a new instance 1581 if (mFocusManager != null) { 1582 mFocusManager.removeMessages(); 1583 } else { 1584 mMirror = isCameraFrontFacing(); 1585 String[] defaultFocusModesStrings = mActivity.getResources().getStringArray( 1586 R.array.pref_camera_focusmode_default_array); 1587 ArrayList<CameraCapabilities.FocusMode> defaultFocusModes = 1588 new ArrayList<CameraCapabilities.FocusMode>(); 1589 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier(); 1590 for (String modeString : defaultFocusModesStrings) { 1591 CameraCapabilities.FocusMode mode = stringifier.focusModeFromString(modeString); 1592 if (mode != null) { 1593 defaultFocusModes.add(mode); 1594 } 1595 } 1596 mFocusManager = 1597 new FocusOverlayManager(mAppController, defaultFocusModes, 1598 mCameraCapabilities, this, mMirror, mActivity.getMainLooper(), 1599 mUI.getFocusRing()); 1600 mMotionManager = getServices().getMotionManager(); 1601 if (mMotionManager != null) { 1602 mMotionManager.addListener(mFocusManager); 1603 } 1604 } 1605 mAppController.addPreviewAreaSizeChangedListener(mFocusManager); 1606 } 1607 1608 /** 1609 * @return Whether we are resuming from within the lockscreen. 1610 */ 1611 private boolean isResumeFromLockscreen() { 1612 String action = mActivity.getIntent().getAction(); 1613 return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action) 1614 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)); 1615 } 1616 1617 @Override 1618 public void pause() { 1619 Log.v(TAG, "pause"); 1620 mPaused = true; 1621 getServices().getRemoteShutterListener().onModuleExit(); 1622 SessionStatsCollector.instance().sessionActive(false); 1623 1624 mHeadingSensor.deactivate(); 1625 1626 // Reset the focus first. Camera CTS does not guarantee that 1627 // cancelAutoFocus is allowed after preview stops. 1628 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) { 1629 mCameraDevice.cancelAutoFocus(); 1630 } 1631 1632 // If the camera has not been opened asynchronously yet, 1633 // and startPreview hasn't been called, then this is a no-op. 1634 // (e.g. onResume -> onPause -> onResume). 1635 stopPreview(); 1636 cancelCountDown(); 1637 mCountdownSoundPlayer.unloadSound(R.raw.timer_final_second); 1638 mCountdownSoundPlayer.unloadSound(R.raw.timer_increment); 1639 1640 mNamedImages = null; 1641 // If we are in an image capture intent and has taken 1642 // a picture, we just clear it in onPause. 1643 mJpegImageData = null; 1644 1645 // Remove the messages and runnables in the queue. 1646 mHandler.removeCallbacksAndMessages(null); 1647 1648 if (mMotionManager != null) { 1649 mMotionManager.removeListener(mFocusManager); 1650 mMotionManager = null; 1651 } 1652 1653 closeCamera(); 1654 mActivity.enableKeepScreenOn(false); 1655 mUI.onPause(); 1656 1657 mPendingSwitchCameraId = -1; 1658 if (mFocusManager != null) { 1659 mFocusManager.removeMessages(); 1660 } 1661 getServices().getMemoryManager().removeListener(this); 1662 mAppController.removePreviewAreaSizeChangedListener(mFocusManager); 1663 mAppController.removePreviewAreaSizeChangedListener(mUI); 1664 1665 SettingsManager settingsManager = mActivity.getSettingsManager(); 1666 settingsManager.removeListener(this); 1667 } 1668 1669 @Override 1670 public void destroy() { 1671 mCountdownSoundPlayer.release(); 1672 } 1673 1674 @Override 1675 public void onLayoutOrientationChanged(boolean isLandscape) { 1676 setDisplayOrientation(); 1677 } 1678 1679 @Override 1680 public void updateCameraOrientation() { 1681 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) { 1682 setDisplayOrientation(); 1683 } 1684 } 1685 1686 private boolean canTakePicture() { 1687 return isCameraIdle() 1688 && (mActivity.getStorageSpaceBytes() > Storage.LOW_STORAGE_THRESHOLD_BYTES); 1689 } 1690 1691 @Override 1692 public void autoFocus() { 1693 if (mCameraDevice == null) { 1694 return; 1695 } 1696 Log.v(TAG,"Starting auto focus"); 1697 mFocusStartTime = System.currentTimeMillis(); 1698 mCameraDevice.autoFocus(mHandler, mAutoFocusCallback); 1699 SessionStatsCollector.instance().autofocusManualTrigger(); 1700 setCameraState(FOCUSING); 1701 } 1702 1703 @Override 1704 public void cancelAutoFocus() { 1705 if (mCameraDevice == null) { 1706 return; 1707 } 1708 mCameraDevice.cancelAutoFocus(); 1709 setCameraState(IDLE); 1710 setCameraParameters(UPDATE_PARAM_PREFERENCE); 1711 } 1712 1713 @Override 1714 public void onSingleTapUp(View view, int x, int y) { 1715 if (mPaused || mCameraDevice == null || !mFirstTimeInitialized 1716 || mCameraState == SNAPSHOT_IN_PROGRESS 1717 || mCameraState == SWITCHING_CAMERA 1718 || mCameraState == PREVIEW_STOPPED) { 1719 return; 1720 } 1721 1722 // Check if metering area or focus area is supported. 1723 if (!mFocusAreaSupported && !mMeteringAreaSupported) { 1724 return; 1725 } 1726 mFocusManager.onSingleTapUp(x, y); 1727 } 1728 1729 @Override 1730 public boolean onBackPressed() { 1731 return mUI.onBackPressed(); 1732 } 1733 1734 @Override 1735 public boolean onKeyDown(int keyCode, KeyEvent event) { 1736 switch (keyCode) { 1737 case KeyEvent.KEYCODE_VOLUME_UP: 1738 case KeyEvent.KEYCODE_VOLUME_DOWN: 1739 case KeyEvent.KEYCODE_FOCUS: 1740 if (/* TODO: mActivity.isInCameraApp() && */mFirstTimeInitialized && 1741 !mActivity.getCameraAppUI().isInIntentReview()) { 1742 if (event.getRepeatCount() == 0) { 1743 onShutterButtonFocus(true); 1744 } 1745 return true; 1746 } 1747 return false; 1748 case KeyEvent.KEYCODE_CAMERA: 1749 if (mFirstTimeInitialized && event.getRepeatCount() == 0) { 1750 onShutterButtonClick(); 1751 } 1752 return true; 1753 case KeyEvent.KEYCODE_DPAD_CENTER: 1754 // If we get a dpad center event without any focused view, move 1755 // the focus to the shutter button and press it. 1756 if (mFirstTimeInitialized && event.getRepeatCount() == 0) { 1757 // Start auto-focus immediately to reduce shutter lag. After 1758 // the shutter button gets the focus, onShutterButtonFocus() 1759 // will be called again but it is fine. 1760 onShutterButtonFocus(true); 1761 } 1762 return true; 1763 } 1764 return false; 1765 } 1766 1767 @Override 1768 public boolean onKeyUp(int keyCode, KeyEvent event) { 1769 switch (keyCode) { 1770 case KeyEvent.KEYCODE_VOLUME_UP: 1771 case KeyEvent.KEYCODE_VOLUME_DOWN: 1772 if (/* mActivity.isInCameraApp() && */mFirstTimeInitialized && 1773 !mActivity.getCameraAppUI().isInIntentReview()) { 1774 if (mUI.isCountingDown()) { 1775 cancelCountDown(); 1776 } else { 1777 mVolumeButtonClickedFlag = true; 1778 onShutterButtonClick(); 1779 } 1780 return true; 1781 } 1782 return false; 1783 case KeyEvent.KEYCODE_FOCUS: 1784 if (mFirstTimeInitialized) { 1785 onShutterButtonFocus(false); 1786 } 1787 return true; 1788 } 1789 return false; 1790 } 1791 1792 private void closeCamera() { 1793 if (mCameraDevice != null) { 1794 stopFaceDetection(); 1795 mCameraDevice.setZoomChangeListener(null); 1796 mCameraDevice.setFaceDetectionCallback(null, null); 1797 1798 mFaceDetectionStarted = false; 1799 mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId()); 1800 mCameraDevice = null; 1801 setCameraState(PREVIEW_STOPPED); 1802 mFocusManager.onCameraReleased(); 1803 } 1804 } 1805 1806 private void setDisplayOrientation() { 1807 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity); 1808 Characteristics info = 1809 mActivity.getCameraProvider().getCharacteristics(mCameraId); 1810 mDisplayOrientation = info.getPreviewOrientation(mDisplayRotation); 1811 mUI.setDisplayOrientation(mDisplayOrientation); 1812 if (mFocusManager != null) { 1813 mFocusManager.setDisplayOrientation(mDisplayOrientation); 1814 } 1815 // Change the camera display orientation 1816 if (mCameraDevice != null) { 1817 mCameraDevice.setDisplayOrientation(mDisplayRotation); 1818 } 1819 Log.v(TAG, "setDisplayOrientation (screen:preview) " + 1820 mDisplayRotation + ":" + mDisplayOrientation); 1821 } 1822 1823 /** Only called by UI thread. */ 1824 private void setupPreview() { 1825 Log.i(TAG, "setupPreview"); 1826 mFocusManager.resetTouchFocus(); 1827 startPreview(); 1828 } 1829 1830 /** 1831 * Returns whether we can/should start the preview or not. 1832 */ 1833 private boolean checkPreviewPreconditions() { 1834 if (mPaused) { 1835 return false; 1836 } 1837 1838 if (mCameraDevice == null) { 1839 Log.w(TAG, "startPreview: camera device not ready yet."); 1840 return false; 1841 } 1842 1843 SurfaceTexture st = mActivity.getCameraAppUI().getSurfaceTexture(); 1844 if (st == null) { 1845 Log.w(TAG, "startPreview: surfaceTexture is not ready."); 1846 return false; 1847 } 1848 1849 if (!mCameraPreviewParamsReady) { 1850 Log.w(TAG, "startPreview: parameters for preview is not ready."); 1851 return false; 1852 } 1853 return true; 1854 } 1855 1856 /** 1857 * The start/stop preview should only run on the UI thread. 1858 */ 1859 private void startPreview() { 1860 if (mCameraDevice == null) { 1861 Log.i(TAG, "attempted to start preview before camera device"); 1862 // do nothing 1863 return; 1864 } 1865 1866 if (!checkPreviewPreconditions()) { 1867 return; 1868 } 1869 1870 setDisplayOrientation(); 1871 1872 if (!mSnapshotOnIdle) { 1873 // If the focus mode is continuous autofocus, call cancelAutoFocus 1874 // to resume it because it may have been paused by autoFocus call. 1875 if (mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) == 1876 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) { 1877 mCameraDevice.cancelAutoFocus(); 1878 } 1879 mFocusManager.setAeAwbLock(false); // Unlock AE and AWB. 1880 } 1881 1882 // Nexus 4 must have picture size set to > 640x480 before other 1883 // parameters are set in setCameraParameters, b/18227551. This call to 1884 // updateParametersPictureSize should occur before setCameraParameters 1885 // to address the issue. 1886 updateParametersPictureSize(); 1887 1888 setCameraParameters(UPDATE_PARAM_ALL); 1889 1890 mCameraDevice.setPreviewTexture(mActivity.getCameraAppUI().getSurfaceTexture()); 1891 1892 Log.i(TAG, "startPreview"); 1893 // If we're using API2 in portability layers, don't use startPreviewWithCallback() 1894 // b/17576554 1895 CameraAgent.CameraStartPreviewCallback startPreviewCallback = 1896 new CameraAgent.CameraStartPreviewCallback() { 1897 @Override 1898 public void onPreviewStarted() { 1899 mFocusManager.onPreviewStarted(); 1900 PhotoModule.this.onPreviewStarted(); 1901 SessionStatsCollector.instance().previewActive(true); 1902 if (mSnapshotOnIdle) { 1903 mHandler.post(mDoSnapRunnable); 1904 } 1905 } 1906 }; 1907 if (GservicesHelper.useCamera2ApiThroughPortabilityLayer(mActivity)) { 1908 mCameraDevice.startPreview(); 1909 startPreviewCallback.onPreviewStarted(); 1910 } else { 1911 mCameraDevice.startPreviewWithCallback(new Handler(Looper.getMainLooper()), 1912 startPreviewCallback); 1913 } 1914 } 1915 1916 @Override 1917 public void stopPreview() { 1918 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) { 1919 Log.i(TAG, "stopPreview"); 1920 mCameraDevice.stopPreview(); 1921 mFaceDetectionStarted = false; 1922 } 1923 setCameraState(PREVIEW_STOPPED); 1924 if (mFocusManager != null) { 1925 mFocusManager.onPreviewStopped(); 1926 } 1927 SessionStatsCollector.instance().previewActive(false); 1928 } 1929 1930 @Override 1931 public void onSettingChanged(SettingsManager settingsManager, String key) { 1932 if (key.equals(Keys.KEY_FLASH_MODE)) { 1933 updateParametersFlashMode(); 1934 } 1935 if (key.equals(Keys.KEY_CAMERA_HDR)) { 1936 if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 1937 Keys.KEY_CAMERA_HDR)) { 1938 // HDR is on. 1939 mAppController.getButtonManager().disableButton(ButtonManager.BUTTON_FLASH); 1940 mFlashModeBeforeSceneMode = settingsManager.getString( 1941 mAppController.getCameraScope(), Keys.KEY_FLASH_MODE); 1942 } else { 1943 if (mFlashModeBeforeSceneMode != null) { 1944 settingsManager.set(mAppController.getCameraScope(), 1945 Keys.KEY_FLASH_MODE, 1946 mFlashModeBeforeSceneMode); 1947 updateParametersFlashMode(); 1948 mFlashModeBeforeSceneMode = null; 1949 } 1950 mAppController.getButtonManager().enableButton(ButtonManager.BUTTON_FLASH); 1951 } 1952 } 1953 1954 if (mCameraDevice != null) { 1955 mCameraDevice.applySettings(mCameraSettings); 1956 } 1957 } 1958 1959 private void updateCameraParametersInitialize() { 1960 // Reset preview frame rate to the maximum because it may be lowered by 1961 // video camera application. 1962 int[] fpsRange = CameraUtil.getPhotoPreviewFpsRange(mCameraCapabilities); 1963 if (fpsRange != null && fpsRange.length > 0) { 1964 mCameraSettings.setPreviewFpsRange(fpsRange[0], fpsRange[1]); 1965 } 1966 1967 mCameraSettings.setRecordingHintEnabled(false); 1968 1969 if (mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_STABILIZATION)) { 1970 mCameraSettings.setVideoStabilization(false); 1971 } 1972 } 1973 1974 private void updateCameraParametersZoom() { 1975 // Set zoom. 1976 if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) { 1977 mCameraSettings.setZoomRatio(mZoomValue); 1978 } 1979 } 1980 1981 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 1982 private void setAutoExposureLockIfSupported() { 1983 if (mAeLockSupported) { 1984 mCameraSettings.setAutoExposureLock(mFocusManager.getAeAwbLock()); 1985 } 1986 } 1987 1988 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 1989 private void setAutoWhiteBalanceLockIfSupported() { 1990 if (mAwbLockSupported) { 1991 mCameraSettings.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock()); 1992 } 1993 } 1994 1995 private void setFocusAreasIfSupported() { 1996 if (mFocusAreaSupported) { 1997 mCameraSettings.setFocusAreas(mFocusManager.getFocusAreas()); 1998 } 1999 } 2000 2001 private void setMeteringAreasIfSupported() { 2002 if (mMeteringAreaSupported) { 2003 mCameraSettings.setMeteringAreas(mFocusManager.getMeteringAreas()); 2004 } 2005 } 2006 2007 private void updateCameraParametersPreference() { 2008 // some monkey tests can get here when shutting the app down 2009 // make sure mCameraDevice is still valid, b/17580046 2010 if (mCameraDevice == null) { 2011 return; 2012 } 2013 2014 setAutoExposureLockIfSupported(); 2015 setAutoWhiteBalanceLockIfSupported(); 2016 setFocusAreasIfSupported(); 2017 setMeteringAreasIfSupported(); 2018 2019 // Initialize focus mode. 2020 mFocusManager.overrideFocusMode(null); 2021 mCameraSettings 2022 .setFocusMode(mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode())); 2023 SessionStatsCollector.instance().autofocusActive( 2024 mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) == 2025 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE 2026 ); 2027 2028 // Set JPEG quality. 2029 updateParametersPictureQuality(); 2030 2031 // For the following settings, we need to check if the settings are 2032 // still supported by latest driver, if not, ignore the settings. 2033 2034 // Set exposure compensation 2035 updateParametersExposureCompensation(); 2036 2037 // Set the scene mode: also sets flash and white balance. 2038 updateParametersSceneMode(); 2039 2040 if (mContinuousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) { 2041 updateAutoFocusMoveCallback(); 2042 } 2043 } 2044 2045 /** 2046 * This method sets picture size parameters. Size parameters should only be 2047 * set when the preview is stopped, and so this method is only invoked in 2048 * {@link #startPreview()} just before starting the preview. 2049 */ 2050 private void updateParametersPictureSize() { 2051 if (mCameraDevice == null) { 2052 Log.w(TAG, "attempting to set picture size without caemra device"); 2053 return; 2054 } 2055 2056 SettingsManager settingsManager = mActivity.getSettingsManager(); 2057 String pictureSizeKey = isCameraFrontFacing() ? Keys.KEY_PICTURE_SIZE_FRONT 2058 : Keys.KEY_PICTURE_SIZE_BACK; 2059 String pictureSize = settingsManager.getString(SettingsManager.SCOPE_GLOBAL, 2060 pictureSizeKey); 2061 2062 List<com.android.camera.util.Size> supported = 2063 com.android.camera.util.Size.convert(mCameraCapabilities.getSupportedPhotoSizes()); 2064 CameraPictureSizesCacher.updateSizesForCamera(mAppController.getAndroidContext(), 2065 mCameraDevice.getCameraId(), supported); 2066 SettingsUtil.setCameraPictureSize(pictureSize, supported, mCameraSettings, 2067 mCameraDevice.getCameraId()); 2068 2069 Size size = SettingsUtil.getPhotoSize(pictureSize, supported, 2070 mCameraDevice.getCameraId()); 2071 if (ApiHelper.IS_NEXUS_5) { 2072 if (ResolutionUtil.NEXUS_5_LARGE_16_BY_9.equals(pictureSize)) { 2073 mShouldResizeTo16x9 = true; 2074 } else { 2075 mShouldResizeTo16x9 = false; 2076 } 2077 } 2078 2079 // Set a preview size that is closest to the viewfinder height and has 2080 // the right aspect ratio. 2081 List<Size> sizes = Size.convert(mCameraCapabilities.getSupportedPreviewSizes()); 2082 Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes, 2083 (double) size.width() / size.height()); 2084 Size original = new Size(mCameraSettings.getCurrentPreviewSize()); 2085 if (!optimalSize.equals(original)) { 2086 Log.v(TAG, "setting preview size. optimal: " + optimalSize + "original: " + original); 2087 mCameraSettings.setPreviewSize(optimalSize.toPortabilitySize()); 2088 2089 mCameraDevice.applySettings(mCameraSettings); 2090 mCameraSettings = mCameraDevice.getSettings(); 2091 } 2092 2093 if (optimalSize.width() != 0 && optimalSize.height() != 0) { 2094 Log.v(TAG, "updating aspect ratio"); 2095 mUI.updatePreviewAspectRatio((float) optimalSize.width() 2096 / (float) optimalSize.height()); 2097 } 2098 Log.d(TAG, "Preview size is " + optimalSize); 2099 } 2100 2101 private void updateParametersPictureQuality() { 2102 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId, 2103 CameraProfile.QUALITY_HIGH); 2104 mCameraSettings.setPhotoJpegCompressionQuality(jpegQuality); 2105 } 2106 2107 private void updateParametersExposureCompensation() { 2108 SettingsManager settingsManager = mActivity.getSettingsManager(); 2109 if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 2110 Keys.KEY_EXPOSURE_COMPENSATION_ENABLED)) { 2111 int value = settingsManager.getInteger(mAppController.getCameraScope(), 2112 Keys.KEY_EXPOSURE); 2113 int max = mCameraCapabilities.getMaxExposureCompensation(); 2114 int min = mCameraCapabilities.getMinExposureCompensation(); 2115 if (value >= min && value <= max) { 2116 mCameraSettings.setExposureCompensationIndex(value); 2117 } else { 2118 Log.w(TAG, "invalid exposure range: " + value); 2119 } 2120 } else { 2121 // If exposure compensation is not enabled, reset the exposure compensation value. 2122 setExposureCompensation(0); 2123 } 2124 } 2125 2126 private void updateParametersSceneMode() { 2127 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier(); 2128 SettingsManager settingsManager = mActivity.getSettingsManager(); 2129 2130 mSceneMode = stringifier. 2131 sceneModeFromString(settingsManager.getString(mAppController.getCameraScope(), 2132 Keys.KEY_SCENE_MODE)); 2133 if (mCameraCapabilities.supports(mSceneMode)) { 2134 if (mCameraSettings.getCurrentSceneMode() != mSceneMode) { 2135 mCameraSettings.setSceneMode(mSceneMode); 2136 2137 // Setting scene mode will change the settings of flash mode, 2138 // white balance, and focus mode. Here we read back the 2139 // parameters, so we can know those settings. 2140 mCameraDevice.applySettings(mCameraSettings); 2141 mCameraSettings = mCameraDevice.getSettings(); 2142 } 2143 } else { 2144 mSceneMode = mCameraSettings.getCurrentSceneMode(); 2145 if (mSceneMode == null) { 2146 mSceneMode = CameraCapabilities.SceneMode.AUTO; 2147 } 2148 } 2149 2150 if (CameraCapabilities.SceneMode.AUTO == mSceneMode) { 2151 // Set flash mode. 2152 updateParametersFlashMode(); 2153 2154 // Set focus mode. 2155 mFocusManager.overrideFocusMode(null); 2156 mCameraSettings.setFocusMode( 2157 mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode())); 2158 } else { 2159 mFocusManager.overrideFocusMode(mCameraSettings.getCurrentFocusMode()); 2160 } 2161 } 2162 2163 private void updateParametersFlashMode() { 2164 SettingsManager settingsManager = mActivity.getSettingsManager(); 2165 2166 CameraCapabilities.FlashMode flashMode = mCameraCapabilities.getStringifier() 2167 .flashModeFromString(settingsManager.getString(mAppController.getCameraScope(), 2168 Keys.KEY_FLASH_MODE)); 2169 if (mCameraCapabilities.supports(flashMode)) { 2170 mCameraSettings.setFlashMode(flashMode); 2171 } 2172 } 2173 2174 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 2175 private void updateAutoFocusMoveCallback() { 2176 if (mCameraDevice == null) { 2177 return; 2178 } 2179 if (mCameraSettings.getCurrentFocusMode() == 2180 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) { 2181 mCameraDevice.setAutoFocusMoveCallback(mHandler, 2182 (CameraAFMoveCallback) mAutoFocusMoveCallback); 2183 } else { 2184 mCameraDevice.setAutoFocusMoveCallback(null, null); 2185 } 2186 } 2187 2188 /** 2189 * Sets the exposure compensation to the given value and also updates settings. 2190 * 2191 * @param value exposure compensation value to be set 2192 */ 2193 public void setExposureCompensation(int value) { 2194 int max = mCameraCapabilities.getMaxExposureCompensation(); 2195 int min = mCameraCapabilities.getMinExposureCompensation(); 2196 if (value >= min && value <= max) { 2197 mCameraSettings.setExposureCompensationIndex(value); 2198 SettingsManager settingsManager = mActivity.getSettingsManager(); 2199 settingsManager.set(mAppController.getCameraScope(), 2200 Keys.KEY_EXPOSURE, value); 2201 } else { 2202 Log.w(TAG, "invalid exposure range: " + value); 2203 } 2204 } 2205 2206 // We separate the parameters into several subsets, so we can update only 2207 // the subsets actually need updating. The PREFERENCE set needs extra 2208 // locking because the preference can be changed from GLThread as well. 2209 private void setCameraParameters(int updateSet) { 2210 if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) { 2211 updateCameraParametersInitialize(); 2212 } 2213 2214 if ((updateSet & UPDATE_PARAM_ZOOM) != 0) { 2215 updateCameraParametersZoom(); 2216 } 2217 2218 if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) { 2219 updateCameraParametersPreference(); 2220 } 2221 2222 if (mCameraDevice != null) { 2223 mCameraDevice.applySettings(mCameraSettings); 2224 } 2225 } 2226 2227 // If the Camera is idle, update the parameters immediately, otherwise 2228 // accumulate them in mUpdateSet and update later. 2229 private void setCameraParametersWhenIdle(int additionalUpdateSet) { 2230 mUpdateSet |= additionalUpdateSet; 2231 if (mCameraDevice == null) { 2232 // We will update all the parameters when we open the device, so 2233 // we don't need to do anything now. 2234 mUpdateSet = 0; 2235 return; 2236 } else if (isCameraIdle()) { 2237 setCameraParameters(mUpdateSet); 2238 updateSceneMode(); 2239 mUpdateSet = 0; 2240 } else { 2241 if (!mHandler.hasMessages(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE)) { 2242 mHandler.sendEmptyMessageDelayed(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000); 2243 } 2244 } 2245 } 2246 2247 @Override 2248 public boolean isCameraIdle() { 2249 return (mCameraState == IDLE) || 2250 (mCameraState == PREVIEW_STOPPED) || 2251 ((mFocusManager != null) && mFocusManager.isFocusCompleted() 2252 && (mCameraState != SWITCHING_CAMERA)); 2253 } 2254 2255 @Override 2256 public boolean isImageCaptureIntent() { 2257 String action = mActivity.getIntent().getAction(); 2258 return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action) 2259 || CameraActivity.ACTION_IMAGE_CAPTURE_SECURE.equals(action)); 2260 } 2261 2262 private void setupCaptureParams() { 2263 Bundle myExtras = mActivity.getIntent().getExtras(); 2264 if (myExtras != null) { 2265 mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT); 2266 mCropValue = myExtras.getString("crop"); 2267 } 2268 } 2269 2270 private void initializeCapabilities() { 2271 mCameraCapabilities = mCameraDevice.getCapabilities(); 2272 mFocusAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA); 2273 mMeteringAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.METERING_AREA); 2274 mAeLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_EXPOSURE_LOCK); 2275 mAwbLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_WHITE_BALANCE_LOCK); 2276 mContinuousFocusSupported = 2277 mCameraCapabilities.supports(CameraCapabilities.FocusMode.CONTINUOUS_PICTURE); 2278 } 2279 2280 @Override 2281 public void onZoomChanged(float ratio) { 2282 // Not useful to change zoom value when the activity is paused. 2283 if (mPaused) { 2284 return; 2285 } 2286 mZoomValue = ratio; 2287 if (mCameraSettings == null || mCameraDevice == null) { 2288 return; 2289 } 2290 // Set zoom parameters asynchronously 2291 mCameraSettings.setZoomRatio(mZoomValue); 2292 mCameraDevice.applySettings(mCameraSettings); 2293 } 2294 2295 @Override 2296 public int getCameraState() { 2297 return mCameraState; 2298 } 2299 2300 @Override 2301 public void onMemoryStateChanged(int state) { 2302 mAppController.setShutterEnabled(state == MemoryManager.STATE_OK); 2303 } 2304 2305 @Override 2306 public void onLowMemory() { 2307 // Not much we can do in the photo module. 2308 } 2309 2310 // For debugging only. 2311 public void setDebugUri(Uri uri) { 2312 mDebugUri = uri; 2313 } 2314 2315 // For debugging only. 2316 private void saveToDebugUri(byte[] data) { 2317 if (mDebugUri != null) { 2318 OutputStream outputStream = null; 2319 try { 2320 outputStream = mContentResolver.openOutputStream(mDebugUri); 2321 outputStream.write(data); 2322 outputStream.close(); 2323 } catch (IOException e) { 2324 Log.e(TAG, "Exception while writing debug jpeg file", e); 2325 } finally { 2326 CameraUtil.closeSilently(outputStream); 2327 } 2328 } 2329 } 2330 2331 @Override 2332 public void onRemoteShutterPress() { 2333 mHandler.post(new Runnable() { 2334 @Override 2335 public void run() { 2336 focusAndCapture(); 2337 } 2338 }); 2339 } 2340} 2341