CaptureModule.java revision d927492ade2e253ccc620effd5602582eaa6dd2d
1/* 2 * Copyright (C) 2014 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.content.Context; 20import android.graphics.Matrix; 21import android.graphics.Point; 22import android.graphics.RectF; 23import android.graphics.SurfaceTexture; 24import android.location.Location; 25import android.media.MediaActionSound; 26import android.net.Uri; 27import android.os.Handler; 28import android.os.HandlerThread; 29import android.os.SystemClock; 30import android.view.GestureDetector; 31import android.view.KeyEvent; 32import android.view.MotionEvent; 33import android.view.Surface; 34import android.view.View; 35 36import com.android.camera.app.AppController; 37import com.android.camera.app.CameraAppUI; 38import com.android.camera.app.CameraAppUI.BottomBarUISpec; 39import com.android.camera.app.LocationManager; 40import com.android.camera.app.OrientationManager.DeviceOrientation; 41import com.android.camera.async.MainThread; 42import com.android.camera.burst.BurstFacade; 43import com.android.camera.burst.BurstFacadeFactory; 44import com.android.camera.burst.BurstReadyStateChangeListener; 45import com.android.camera.burst.OrientationLockController; 46import com.android.camera.captureintent.PreviewTransformCalculator; 47import com.android.camera.debug.DebugPropertyHelper; 48import com.android.camera.debug.Log; 49import com.android.camera.debug.Log.Tag; 50import com.android.camera.device.CameraId; 51import com.android.camera.hardware.HardwareSpec; 52import com.android.camera.hardware.HeadingSensor; 53import com.android.camera.module.ModuleController; 54import com.android.camera.one.OneCamera; 55import com.android.camera.one.OneCamera.AutoFocusState; 56import com.android.camera.one.OneCamera.CaptureReadyCallback; 57import com.android.camera.one.OneCamera.Facing; 58import com.android.camera.one.OneCamera.OpenCallback; 59import com.android.camera.one.OneCamera.PhotoCaptureParameters; 60import com.android.camera.one.OneCameraAccessException; 61import com.android.camera.one.OneCameraCaptureSetting; 62import com.android.camera.one.OneCameraCharacteristics; 63import com.android.camera.one.OneCameraException; 64import com.android.camera.one.OneCameraManager; 65import com.android.camera.one.OneCameraModule; 66import com.android.camera.one.OneCameraOpener; 67import com.android.camera.one.config.OneCameraFeatureConfig; 68import com.android.camera.one.v2.photo.ImageRotationCalculator; 69import com.android.camera.one.v2.photo.ImageRotationCalculatorImpl; 70import com.android.camera.remote.RemoteCameraModule; 71import com.android.camera.session.CaptureSession; 72import com.android.camera.settings.Keys; 73import com.android.camera.settings.SettingsManager; 74import com.android.camera.stats.CaptureStats; 75import com.android.camera.stats.UsageStatistics; 76import com.android.camera.stats.profiler.Profile; 77import com.android.camera.stats.profiler.Profiler; 78import com.android.camera.stats.profiler.Profilers; 79import com.android.camera.ui.CountDownView; 80import com.android.camera.ui.PreviewStatusListener; 81import com.android.camera.ui.TouchCoordinate; 82import com.android.camera.ui.focus.FocusController; 83import com.android.camera.ui.focus.FocusSound; 84import com.android.camera.util.AndroidServices; 85import com.android.camera.util.ApiHelper; 86import com.android.camera.util.CameraUtil; 87import com.android.camera.util.GcamHelper; 88import com.android.camera.util.Size; 89import com.android.camera2.R; 90import com.android.ex.camera2.portability.CameraAgent.CameraProxy; 91import com.google.common.logging.eventprotos; 92 93import java.util.concurrent.Semaphore; 94import java.util.concurrent.TimeUnit; 95 96import javax.annotation.Nonnull; 97 98/** 99 * New Capture module that is made to support photo and video capture on top of 100 * the OneCamera API, to transparently support GCam. 101 * <p> 102 * This has been a re-write with pieces taken and improved from GCamModule and 103 * PhotoModule, which are to be retired eventually. 104 * <p> 105 */ 106public class CaptureModule extends CameraModule implements 107 ModuleController, 108 CountDownView.OnCountDownStatusListener, 109 OneCamera.PictureCallback, 110 OneCamera.FocusStateListener, 111 OneCamera.ReadyStateChangedListener, 112 RemoteCameraModule { 113 114 private static final Tag TAG = new Tag("CaptureModule"); 115 /** Enable additional debug output. */ 116 private static final boolean DEBUG = true; 117 /** Workaround Flag for b/19271661 to use autotransformation in Capture Layout in Nexus4 **/ 118 private static final boolean USE_AUTOTRANSFORM_UI_LAYOUT = ApiHelper.IS_NEXUS_4; 119 120 /** Timeout for camera open/close operations. */ 121 private static final int CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS = 2500; 122 123 /** System Properties switch to enable debugging focus UI. */ 124 private static final boolean CAPTURE_DEBUG_UI = DebugPropertyHelper.showCaptureDebugUI(); 125 126 private final Object mDimensionLock = new Object(); 127 128 /** 129 * Sticky Gcam mode is when this module's sole purpose it to be the Gcam 130 * mode. If true, the device uses {@link PhotoModule} for normal picture 131 * taking. 132 */ 133 private final boolean mStickyGcamCamera; 134 135 /** Controller giving us access to other services. */ 136 private final AppController mAppController; 137 /** The applications settings manager. */ 138 private final SettingsManager mSettingsManager; 139 /** Application context. */ 140 private final Context mContext; 141 /** Module UI. */ 142 private CaptureModuleUI mUI; 143 /** The camera manager used to open cameras. */ 144 private OneCameraOpener mOneCameraOpener; 145 /** The manager to query for camera device information */ 146 private OneCameraManager mOneCameraManager; 147 /** The currently opened camera device, or null if the camera is closed. */ 148 private OneCamera mCamera; 149 /** The selected picture size. */ 150 private Size mPictureSize; 151 /** Held when opening or closing the camera. */ 152 private final Semaphore mCameraOpenCloseLock = new Semaphore(1); 153 /** The direction the currently opened camera is facing to. */ 154 private Facing mCameraFacing; 155 /** Whether HDR Scene mode is currently enabled. */ 156 private boolean mHdrSceneEnabled = false; 157 private boolean mHdrPlusEnabled = false; 158 private final Object mSurfaceTextureLock = new Object(); 159 /** 160 * Flag that is used when Fatal Error Handler is running and the app should 161 * not continue execution 162 */ 163 private boolean mShowErrorAndFinish; 164 private TouchCoordinate mLastShutterTouchCoordinate = null; 165 166 private FocusController mFocusController; 167 private OneCameraCharacteristics mCameraCharacteristics; 168 final private PreviewTransformCalculator mPreviewTransformCalculator; 169 170 /** The listener to listen events from the CaptureModuleUI. */ 171 private final CaptureModuleUI.CaptureModuleUIListener mUIListener = 172 new CaptureModuleUI.CaptureModuleUIListener() { 173 @Override 174 public void onZoomRatioChanged(float zoomRatio) { 175 mZoomValue = zoomRatio; 176 if (mCamera != null) { 177 mCamera.setZoom(zoomRatio); 178 } 179 } 180 }; 181 182 /** The listener to respond preview area changes. */ 183 private final PreviewStatusListener.PreviewAreaChangedListener mPreviewAreaChangedListener = 184 new PreviewStatusListener.PreviewAreaChangedListener() { 185 @Override 186 public void onPreviewAreaChanged(RectF previewArea) { 187 mPreviewArea = previewArea; 188 mFocusController.configurePreviewDimensions(previewArea); 189 } 190 }; 191 192 /** The listener to listen events from the preview. */ 193 private final PreviewStatusListener mPreviewStatusListener = new PreviewStatusListener() { 194 @Override 195 public void onPreviewLayoutChanged(View v, int left, int top, int right, 196 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 197 int width = right - left; 198 int height = bottom - top; 199 updatePreviewTransform(width, height, false); 200 } 201 202 @Override 203 public boolean shouldAutoAdjustTransformMatrixOnLayout() { 204 return USE_AUTOTRANSFORM_UI_LAYOUT; 205 } 206 207 @Override 208 public void onPreviewFlipped() { 209 // Do nothing because when preview is flipped, TextureView will lay 210 // itself out again, which will then trigger a transform matrix 211 // update. 212 } 213 214 @Override 215 public GestureDetector.OnGestureListener getGestureListener() { 216 return new GestureDetector.SimpleOnGestureListener() { 217 @Override 218 public boolean onSingleTapUp(MotionEvent ev) { 219 Point tapPoint = new Point((int) ev.getX(), (int) ev.getY()); 220 Log.v(TAG, "onSingleTapUpPreview location=" + tapPoint); 221 if (!mCameraCharacteristics.isAutoExposureSupported() && 222 !mCameraCharacteristics.isAutoFocusSupported()) { 223 return false; 224 } 225 startActiveFocusAt(tapPoint.x, tapPoint.y); 226 return true; 227 } 228 }; 229 } 230 231 @Override 232 public View.OnTouchListener getTouchListener() { 233 return null; 234 } 235 236 @Override 237 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 238 Log.d(TAG, "onSurfaceTextureAvailable"); 239 // Force to re-apply transform matrix here as a workaround for 240 // b/11168275 241 updatePreviewTransform(width, height, true); 242 synchronized (mSurfaceTextureLock) { 243 mPreviewSurfaceTexture = surface; 244 } 245 reopenCamera(); 246 } 247 248 @Override 249 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 250 Log.d(TAG, "onSurfaceTextureDestroyed"); 251 synchronized (mSurfaceTextureLock) { 252 mPreviewSurfaceTexture = null; 253 } 254 closeCamera(); 255 return true; 256 } 257 258 @Override 259 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 260 Log.d(TAG, "onSurfaceTextureSizeChanged"); 261 updatePreviewBufferSize(); 262 } 263 264 @Override 265 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 266 if (mState == ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE) { 267 Log.d(TAG, "onSurfaceTextureUpdated --> updatePreviewTransform"); 268 mState = ModuleState.IDLE; 269 CameraAppUI appUI = mAppController.getCameraAppUI(); 270 updatePreviewTransform(appUI.getSurfaceWidth(), appUI.getSurfaceHeight(), true); 271 } 272 } 273 }; 274 275 private final OneCamera.PictureSaverCallback mPictureSaverCallback = 276 new OneCamera.PictureSaverCallback() { 277 @Override 278 public void onRemoteThumbnailAvailable(final byte[] jpegImage) { 279 mMainThread.execute(new Runnable() { 280 @Override 281 public void run() { 282 mAppController.getServices().getRemoteShutterListener() 283 .onPictureTaken(jpegImage); 284 } 285 }); 286 } 287 }; 288 289 /** State by the module state machine. */ 290 private static enum ModuleState { 291 IDLE, 292 WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED, 293 UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE, 294 } 295 296 /** The current state of the module. */ 297 private ModuleState mState = ModuleState.IDLE; 298 /** Current zoom value. */ 299 private float mZoomValue = 1f; 300 301 /** Records beginning frame of each AF scan. */ 302 private long mAutoFocusScanStartFrame = -1; 303 /** Records beginning time of each AF scan in uptimeMillis. */ 304 private long mAutoFocusScanStartTime; 305 306 /** Heading sensor. */ 307 private HeadingSensor mHeadingSensor; 308 309 /** Used to fetch and embed the location into captured images. */ 310 private final LocationManager mLocationManager; 311 /** Plays sounds for countdown timer. */ 312 private SoundPlayer mSoundPlayer; 313 private final MediaActionSound mMediaActionSound; 314 315 /** Whether the module is paused right now. */ 316 private boolean mPaused; 317 318 /** Main thread. */ 319 private final MainThread mMainThread; 320 /** Handler thread for camera-related operations. */ 321 private Handler mCameraHandler; 322 323 /** Current display rotation in degrees. */ 324 private int mDisplayRotation; 325 /** Current screen width in pixels. */ 326 private int mScreenWidth; 327 /** Current screen height in pixels. */ 328 private int mScreenHeight; 329 /** Current width of preview frames from camera. */ 330 private int mPreviewBufferWidth; 331 /** Current height of preview frames from camera.. */ 332 private int mPreviewBufferHeight; 333 /** Area used by preview. */ 334 RectF mPreviewArea; 335 336 /** The surface texture for the preview. */ 337 private SurfaceTexture mPreviewSurfaceTexture; 338 339 /** The burst manager for controlling the burst. */ 340 private final BurstFacade mBurstController; 341 private static final String BURST_SESSIONS_DIR = "burst_sessions"; 342 343 private final Profiler mProfiler = Profilers.instance().guard(); 344 345 public CaptureModule(AppController appController) { 346 this(appController, false); 347 } 348 349 /** Constructs a new capture module. */ 350 public CaptureModule(AppController appController, boolean stickyHdr) { 351 super(appController); 352 Profile guard = mProfiler.create("new CaptureModule").start(); 353 mPaused = true; 354 mMainThread = MainThread.create(); 355 mAppController = appController; 356 mContext = mAppController.getAndroidContext(); 357 mSettingsManager = mAppController.getSettingsManager(); 358 mStickyGcamCamera = stickyHdr; 359 mLocationManager = mAppController.getLocationManager(); 360 mPreviewTransformCalculator = new PreviewTransformCalculator( 361 mAppController.getOrientationManager()); 362 363 mBurstController = BurstFacadeFactory.create(mContext, 364 new OrientationLockController() { 365 @Override 366 public void unlockOrientation() { 367 mAppController.getOrientationManager().unlockOrientation(); 368 } 369 370 @Override 371 public void lockOrientation() { 372 mAppController.getOrientationManager().lockOrientation(); 373 } 374 }, 375 new BurstReadyStateChangeListener() { 376 @Override 377 public void onBurstReadyStateChanged(boolean ready) { 378 // TODO: This needs to take into account the state of 379 // the whole system, not just burst. 380 onReadyStateChanged(false); 381 } 382 }); 383 mMediaActionSound = new MediaActionSound(); 384 guard.stop(); 385 } 386 387 private boolean updateCameraCharacteristics() { 388 try { 389 CameraId cameraId = mOneCameraManager.findFirstCameraFacing(mCameraFacing); 390 if (cameraId != null && cameraId.getValue() != null) { 391 mCameraCharacteristics = mOneCameraManager.getOneCameraCharacteristics(cameraId); 392 return mCameraCharacteristics != null; 393 } 394 } catch (OneCameraAccessException ignored) { } 395 mAppController.getFatalErrorHandler().onGenericCameraAccessFailure(); 396 return false; 397 } 398 399 @Override 400 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) { 401 Profile guard = mProfiler.create("CaptureModule.init").start(); 402 Log.d(TAG, "init UseAutotransformUiLayout = " + USE_AUTOTRANSFORM_UI_LAYOUT); 403 HandlerThread thread = new HandlerThread("CaptureModule.mCameraHandler"); 404 thread.start(); 405 mCameraHandler = new Handler(thread.getLooper()); 406 mOneCameraOpener = mAppController.getCameraOpener(); 407 408 try { 409 mOneCameraManager = OneCameraModule.provideOneCameraManager(); 410 } catch (OneCameraException e) { 411 Log.e(TAG, "Unable to provide a OneCameraManager. ", e); 412 } 413 mDisplayRotation = CameraUtil.getDisplayRotation(); 414 mCameraFacing = getFacingFromCameraId( 415 mSettingsManager.getInteger(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID)); 416 mShowErrorAndFinish = !updateCameraCharacteristics(); 417 if (mShowErrorAndFinish) { 418 return; 419 } 420 mUI = new CaptureModuleUI(activity, mAppController.getModuleLayoutRoot(), mUIListener); 421 mAppController.setPreviewStatusListener(mPreviewStatusListener); 422 synchronized (mSurfaceTextureLock) { 423 mPreviewSurfaceTexture = mAppController.getCameraAppUI().getSurfaceTexture(); 424 } 425 mSoundPlayer = new SoundPlayer(mContext); 426 427 FocusSound focusSound = new FocusSound(mSoundPlayer, R.raw.material_camera_focus); 428 mFocusController = new FocusController(mUI.getFocusRing(), focusSound, mMainThread); 429 430 mHeadingSensor = new HeadingSensor(AndroidServices.instance().provideSensorManager()); 431 432 View cancelButton = activity.findViewById(R.id.shutter_cancel_button); 433 cancelButton.setOnClickListener(new View.OnClickListener() { 434 @Override 435 public void onClick(View view) { 436 cancelCountDown(); 437 } 438 }); 439 440 mMediaActionSound.load(MediaActionSound.SHUTTER_CLICK); 441 guard.stop(); 442 } 443 444 @Override 445 public void onShutterButtonLongPressed() { 446 try { 447 OneCameraCharacteristics cameraCharacteristics; 448 CameraId cameraId = mOneCameraManager.findFirstCameraFacing(mCameraFacing); 449 cameraCharacteristics = mOneCameraManager.getOneCameraCharacteristics(cameraId); 450 DeviceOrientation deviceOrientation = mAppController.getOrientationManager() 451 .getDeviceOrientation(); 452 ImageRotationCalculator imageRotationCalculator = ImageRotationCalculatorImpl 453 .from(mAppController.getOrientationManager(), cameraCharacteristics); 454 455 mBurstController.startBurst( 456 new CaptureSession.CaptureSessionCreator() { 457 @Override 458 public CaptureSession createAndStartEmpty() { 459 return createAndStartCaptureSession(); 460 } 461 }, 462 deviceOrientation, 463 mCamera.getDirection(), 464 imageRotationCalculator.toImageRotation().getDegrees()); 465 466 } catch (OneCameraAccessException e) { 467 Log.e(TAG, "Cannot start burst", e); 468 return; 469 } 470 } 471 472 @Override 473 public void onShutterButtonFocus(boolean pressed) { 474 if (!pressed) { 475 // the shutter button was released, stop any bursts. 476 mBurstController.stopBurst(); 477 } 478 } 479 480 @Override 481 public void onShutterCoordinate(TouchCoordinate coord) { 482 mLastShutterTouchCoordinate = coord; 483 } 484 485 @Override 486 public void onShutterButtonClick() { 487 if (mCamera == null) { 488 return; 489 } 490 491 int countDownDuration = mSettingsManager 492 .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION); 493 if (countDownDuration > 0) { 494 // Start count down. 495 mAppController.getCameraAppUI().transitionToCancel(); 496 mAppController.getCameraAppUI().hideModeOptions(); 497 mUI.setCountdownFinishedListener(this); 498 mUI.startCountdown(countDownDuration); 499 // Will take picture later via listener callback. 500 } else { 501 takePictureNow(); 502 } 503 } 504 505 506 private void decorateSessionAtCaptureTime(CaptureSession session) { 507 String flashSetting = 508 mSettingsManager.getString(mAppController.getCameraScope(), 509 Keys.KEY_FLASH_MODE); 510 boolean gridLinesOn = Keys.areGridLinesOn(mSettingsManager); 511 float timerDuration = mSettingsManager 512 .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION); 513 514 session.getCollector().decorateAtTimeCaptureRequest( 515 eventprotos.NavigationChange.Mode.PHOTO_CAPTURE, 516 session.getTitle() + ".jpg", 517 (mCameraFacing == Facing.FRONT), 518 mHdrSceneEnabled, 519 mZoomValue, 520 flashSetting, 521 gridLinesOn, 522 timerDuration, 523 mLastShutterTouchCoordinate, 524 null /* TODO: Implement Volume Button Shutter Click Instrumentation */, 525 mCameraCharacteristics.getSensorInfoActiveArraySize() 526 ); 527 } 528 529 private void takePictureNow() { 530 if (mCamera == null) { 531 Log.i(TAG, "Not taking picture since Camera is closed."); 532 return; 533 } 534 535 CaptureSession session = createAndStartCaptureSession(); 536 int orientation = mAppController.getOrientationManager().getDeviceOrientation() 537 .getDegrees(); 538 539 // TODO: This should really not use getExternalCacheDir and instead use 540 // the SessionStorage API. Need to sync with gcam if that's OK. 541 PhotoCaptureParameters params = new PhotoCaptureParameters( 542 session.getTitle(), orientation, session.getLocation(), 543 mContext.getExternalCacheDir(), this, mPictureSaverCallback, 544 mHeadingSensor.getCurrentHeading(), mZoomValue, 0); 545 decorateSessionAtCaptureTime(session); 546 mCamera.takePicture(params, session); 547 } 548 549 /** 550 * Creates, starts and returns a new capture session. The returned session 551 * will have been started with an empty placeholder image. 552 */ 553 private CaptureSession createAndStartCaptureSession() { 554 long sessionTime = getSessionTime(); 555 Location location = mLocationManager.getCurrentLocation(); 556 String title = CameraUtil.instance().createJpegName(sessionTime); 557 CaptureSession session = getServices().getCaptureSessionManager() 558 .createNewSession(title, sessionTime, location); 559 560 session.startEmpty(new CaptureStats(mHdrPlusEnabled), 561 new Size((int) mPreviewArea.width(), (int) mPreviewArea.height())); 562 return session; 563 } 564 565 private long getSessionTime() { 566 // TODO: Replace with a mockable TimeProvider interface. 567 return System.currentTimeMillis(); 568 } 569 570 @Override 571 public void onCountDownFinished() { 572 mAppController.getCameraAppUI().transitionToCapture(); 573 mAppController.getCameraAppUI().showModeOptions(); 574 if (mPaused) { 575 return; 576 } 577 takePictureNow(); 578 } 579 580 @Override 581 public void onRemainingSecondsChanged(int remainingSeconds) { 582 if (remainingSeconds == 1) { 583 mSoundPlayer.play(R.raw.timer_final_second, 0.6f); 584 } else if (remainingSeconds == 2 || remainingSeconds == 3) { 585 mSoundPlayer.play(R.raw.timer_increment, 0.6f); 586 } 587 } 588 589 private void cancelCountDown() { 590 if (mUI.isCountingDown()) { 591 // Cancel on-going countdown. 592 mUI.cancelCountDown(); 593 } 594 595 if (!mPaused) { 596 mAppController.getCameraAppUI().showModeOptions(); 597 mAppController.getCameraAppUI().transitionToCapture(); 598 } 599 } 600 601 @Override 602 public void onQuickExpose() { 603 mMainThread.execute(new Runnable() { 604 @Override 605 public void run() { 606 // Starts the short version of the capture animation UI. 607 mAppController.startFlashAnimation(true); 608 mMediaActionSound.play(MediaActionSound.SHUTTER_CLICK); 609 } 610 }); 611 } 612 613 @Override 614 public void onRemoteShutterPress() { 615 Log.d(TAG, "onRemoteShutterPress"); 616 // TODO: Check whether shutter is enabled. 617 takePictureNow(); 618 } 619 620 private void initSurfaceTextureConsumer() { 621 synchronized (mSurfaceTextureLock) { 622 if (mPreviewSurfaceTexture != null) { 623 mPreviewSurfaceTexture.setDefaultBufferSize( 624 mAppController.getCameraAppUI().getSurfaceWidth(), 625 mAppController.getCameraAppUI().getSurfaceHeight()); 626 } 627 } 628 reopenCamera(); 629 } 630 631 private void reopenCamera() { 632 if (mPaused) { 633 return; 634 } 635 closeCamera(); 636 openCameraAndStartPreview(); 637 } 638 639 private SurfaceTexture getPreviewSurfaceTexture() { 640 synchronized (mSurfaceTextureLock) { 641 return mPreviewSurfaceTexture; 642 } 643 } 644 645 private void updatePreviewBufferSize() { 646 synchronized (mSurfaceTextureLock) { 647 if (mPreviewSurfaceTexture != null) { 648 mPreviewSurfaceTexture.setDefaultBufferSize(mPreviewBufferWidth, 649 mPreviewBufferHeight); 650 } 651 } 652 } 653 654 @Override 655 public void resume() { 656 if (mShowErrorAndFinish) { 657 return; 658 } 659 Profile guard = mProfiler.create("CaptureModule.resume").start(); 660 661 // We'll transition into 'ready' once the preview is started. 662 onReadyStateChanged(false); 663 mPaused = false; 664 mAppController.addPreviewAreaSizeChangedListener(mPreviewAreaChangedListener); 665 mAppController.addPreviewAreaSizeChangedListener(mUI); 666 667 guard.mark(); 668 getServices().getRemoteShutterListener().onModuleReady(this); 669 guard.mark("getRemoteShutterListener.onModuleReady"); 670 mBurstController.initialize(new SurfaceTexture(0)); 671 672 // TODO: Check if we can really take a photo right now (memory, camera 673 // state, ... ). 674 mAppController.getCameraAppUI().enableModeOptions(); 675 mAppController.setShutterEnabled(true); 676 mAppController.getCameraAppUI().showAccessibilityZoomUI( 677 mCameraCharacteristics.getAvailableMaxDigitalZoom()); 678 679 mHdrPlusEnabled = mStickyGcamCamera || mAppController.getSettingsManager().getInteger( 680 SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS) == 1; 681 682 mHdrSceneEnabled = !mStickyGcamCamera && mAppController.getSettingsManager().getBoolean( 683 SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR); 684 685 // The lock only exists for HDR and causes trouble for non-HDR 686 // OneCameras. 687 // TODO: Fix for removing the locks completely is tracked at b/17985028 688 if (!mHdrPlusEnabled) { 689 mCameraOpenCloseLock.release(); 690 } 691 692 // This means we are resuming with an existing preview texture. This 693 // means we will never get the onSurfaceTextureAvailable call. So we 694 // have to open the camera and start the preview here. 695 SurfaceTexture texture = getPreviewSurfaceTexture(); 696 697 guard.mark(); 698 if (texture != null) { 699 initSurfaceTextureConsumer(); 700 guard.mark("initSurfaceTextureConsumer"); 701 } 702 703 mSoundPlayer.loadSound(R.raw.timer_final_second); 704 mSoundPlayer.loadSound(R.raw.timer_increment); 705 706 guard.mark(); 707 mHeadingSensor.activate(); 708 guard.stop("mHeadingSensor.activate()"); 709 } 710 711 @Override 712 public void pause() { 713 if (mShowErrorAndFinish) { 714 return; 715 } 716 cancelCountDown(); 717 mPaused = true; 718 mHeadingSensor.deactivate(); 719 720 mAppController.removePreviewAreaSizeChangedListener(mUI); 721 mAppController.removePreviewAreaSizeChangedListener(mPreviewAreaChangedListener); 722 getServices().getRemoteShutterListener().onModuleExit(); 723 mBurstController.release(); 724 closeCamera(); 725 resetTextureBufferSize(); 726 mSoundPlayer.unloadSound(R.raw.timer_final_second); 727 mSoundPlayer.unloadSound(R.raw.timer_increment); 728 } 729 730 @Override 731 public void destroy() { 732 mSoundPlayer.release(); 733 mMediaActionSound.release(); 734 mCameraHandler.getLooper().quitSafely(); 735 } 736 737 @Override 738 public void onLayoutOrientationChanged(boolean isLandscape) { 739 Log.d(TAG, "onLayoutOrientationChanged"); 740 } 741 742 @Override 743 public void onCameraAvailable(CameraProxy cameraProxy) { 744 // Ignore since we manage the camera ourselves until we remove this. 745 } 746 747 @Override 748 public void hardResetSettings(SettingsManager settingsManager) { 749 if (mStickyGcamCamera) { 750 // Sticky HDR+ mode should hard reset HDR+ to on, and camera back 751 // facing. 752 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, true); 753 settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID, 754 mOneCameraManager.findFirstCameraFacing(Facing.BACK).getValue()); 755 } 756 } 757 758 @Override 759 public HardwareSpec getHardwareSpec() { 760 return new HardwareSpec() { 761 @Override 762 public boolean isFrontCameraSupported() { 763 return true; 764 } 765 766 @Override 767 public boolean isHdrSupported() { 768 if (ApiHelper.IS_NEXUS_4 && is16by9AspectRatio(mPictureSize)) { 769 Log.v(TAG, "16:9 N4, no HDR support"); 770 return false; 771 } else { 772 return mCameraCharacteristics.isHdrSceneSupported(); 773 } 774 } 775 776 @Override 777 public boolean isHdrPlusSupported() { 778 OneCameraFeatureConfig featureConfig = mAppController.getCameraFeatureConfig(); 779 return featureConfig.getHdrPlusSupportLevel(mCameraFacing) != 780 OneCameraFeatureConfig.HdrPlusSupportLevel.NONE; 781 } 782 783 @Override 784 public boolean isFlashSupported() { 785 return mCameraCharacteristics.isFlashSupported(); 786 } 787 }; 788 } 789 790 @Override 791 public BottomBarUISpec getBottomBarSpec() { 792 HardwareSpec hardwareSpec = getHardwareSpec(); 793 BottomBarUISpec bottomBarSpec = new BottomBarUISpec(); 794 bottomBarSpec.enableGridLines = true; 795 bottomBarSpec.enableCamera = true; 796 bottomBarSpec.cameraCallback = getCameraCallback(); 797 bottomBarSpec.enableHdr = 798 hardwareSpec.isHdrSupported() || hardwareSpec.isHdrPlusSupported(); 799 bottomBarSpec.hdrCallback = getHdrButtonCallback(); 800 bottomBarSpec.enableSelfTimer = true; 801 bottomBarSpec.showSelfTimer = true; 802 bottomBarSpec.isExposureCompensationSupported = mCameraCharacteristics 803 .isExposureCompensationSupported(); 804 bottomBarSpec.enableExposureCompensation = bottomBarSpec.isExposureCompensationSupported; 805 806 // We must read the key from the settings because the button callback 807 // is not executed until after this method is called. 808 if ((hardwareSpec.isHdrPlusSupported() && 809 mAppController.getSettingsManager().getBoolean( 810 SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS)) || 811 ( hardwareSpec.isHdrSupported() && 812 mAppController.getSettingsManager().getBoolean( 813 SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR))) { 814 // Disable flash if this is a sticky gcam camera, or if 815 // HDR is enabled. 816 bottomBarSpec.enableFlash = false; 817 // Disable manual exposure if HDR is enabled. 818 bottomBarSpec.enableExposureCompensation = false; 819 } else { 820 // If we are not in HDR / GCAM mode, fallback on the 821 // flash supported property and manual exposure supported property 822 // for this camera. 823 bottomBarSpec.enableFlash = mCameraCharacteristics.isFlashSupported(); 824 } 825 826 bottomBarSpec.minExposureCompensation = 827 mCameraCharacteristics.getMinExposureCompensation(); 828 bottomBarSpec.maxExposureCompensation = 829 mCameraCharacteristics.getMaxExposureCompensation(); 830 bottomBarSpec.exposureCompensationStep = 831 mCameraCharacteristics.getExposureCompensationStep(); 832 bottomBarSpec.exposureCompensationSetCallback = 833 new BottomBarUISpec.ExposureCompensationSetCallback() { 834 @Override 835 public void setExposure(int value) { 836 mSettingsManager.set( 837 mAppController.getCameraScope(), Keys.KEY_EXPOSURE, value); 838 } 839 }; 840 841 return bottomBarSpec; 842 } 843 844 @Override 845 public boolean isUsingBottomBar() { 846 return true; 847 } 848 849 @Override 850 public boolean onKeyDown(int keyCode, KeyEvent event) { 851 switch (keyCode) { 852 case KeyEvent.KEYCODE_CAMERA: 853 case KeyEvent.KEYCODE_DPAD_CENTER: 854 if (mUI.isCountingDown()) { 855 cancelCountDown(); 856 } else if (event.getRepeatCount() == 0) { 857 onShutterButtonClick(); 858 } 859 return true; 860 case KeyEvent.KEYCODE_VOLUME_UP: 861 case KeyEvent.KEYCODE_VOLUME_DOWN: 862 // Prevent default. 863 return true; 864 } 865 return false; 866 } 867 868 @Override 869 public boolean onKeyUp(int keyCode, KeyEvent event) { 870 switch (keyCode) { 871 case KeyEvent.KEYCODE_VOLUME_UP: 872 case KeyEvent.KEYCODE_VOLUME_DOWN: 873 onShutterButtonClick(); 874 return true; 875 } 876 return false; 877 } 878 879 // TODO: Consider refactoring FocusOverlayManager. 880 // Currently AF state transitions are controlled in OneCameraImpl. 881 // PhotoModule uses FocusOverlayManager which uses API1/portability 882 // logic and coordinates. 883 private void startActiveFocusAt(int viewX, int viewY) { 884 if (mCamera == null) { 885 // If we receive this after the camera is closed, do nothing. 886 return; 887 } 888 889 // TODO: make mFocusController final and remove null check. 890 if (mFocusController == null) { 891 Log.v(TAG, "CaptureModule mFocusController is null!"); 892 return; 893 } 894 mFocusController.showActiveFocusAt(viewX, viewY); 895 896 // Normalize coordinates to [0,1] per CameraOne API. 897 float points[] = new float[2]; 898 points[0] = (viewX - mPreviewArea.left) / mPreviewArea.width(); 899 points[1] = (viewY - mPreviewArea.top) / mPreviewArea.height(); 900 901 // Rotate coordinates to portrait orientation per CameraOne API. 902 Matrix rotationMatrix = new Matrix(); 903 rotationMatrix.setRotate(mDisplayRotation, 0.5f, 0.5f); 904 rotationMatrix.mapPoints(points); 905 906 // Invert X coordinate on front camera since the display is mirrored. 907 if (mCameraCharacteristics.getCameraDirection() == Facing.FRONT) { 908 points[0] = 1 - points[0]; 909 } 910 911 mCamera.triggerFocusAndMeterAtPoint(points[0], points[1]); 912 913 // Log touch (screen coordinates). 914 if (mZoomValue == 1f) { 915 TouchCoordinate touchCoordinate = new TouchCoordinate( 916 viewX - mPreviewArea.left, 917 viewY - mPreviewArea.top, 918 mPreviewArea.width(), 919 mPreviewArea.height()); 920 // TODO: Add to logging: duration, rotation. 921 UsageStatistics.instance().tapToFocus(touchCoordinate, null); 922 } 923 } 924 925 /** 926 * Show AF target in center of preview. 927 */ 928 private void startPassiveFocus() { 929 // TODO: make mFocusController final and remove null check. 930 if (mFocusController == null) { 931 return; 932 } 933 934 // TODO: Some passive focus scans may trigger on a location 935 // instead of the center of the screen. 936 mFocusController.showPassiveFocusAtCenter(); 937 } 938 939 /** 940 * Update UI based on AF state changes. 941 */ 942 @Override 943 public void onFocusStatusUpdate(final AutoFocusState state, long frameNumber) { 944 Log.v(TAG, "AF status is state:" + state); 945 946 switch (state) { 947 case PASSIVE_SCAN: 948 startPassiveFocus(); 949 break; 950 case ACTIVE_SCAN: 951 // Unused, manual scans are triggered via the UI 952 break; 953 case PASSIVE_FOCUSED: 954 case PASSIVE_UNFOCUSED: 955 // Unused 956 break; 957 case ACTIVE_FOCUSED: 958 case ACTIVE_UNFOCUSED: 959 // Unused 960 break; 961 } 962 963 if (CAPTURE_DEBUG_UI) { 964 measureAutoFocusScans(state, frameNumber); 965 } 966 } 967 968 private void measureAutoFocusScans(final AutoFocusState state, long frameNumber) { 969 // Log AF scan lengths. 970 boolean passive = false; 971 switch (state) { 972 case PASSIVE_SCAN: 973 case ACTIVE_SCAN: 974 if (mAutoFocusScanStartFrame == -1) { 975 mAutoFocusScanStartFrame = frameNumber; 976 mAutoFocusScanStartTime = SystemClock.uptimeMillis(); 977 } 978 break; 979 case PASSIVE_FOCUSED: 980 case PASSIVE_UNFOCUSED: 981 passive = true; 982 case ACTIVE_FOCUSED: 983 case ACTIVE_UNFOCUSED: 984 if (mAutoFocusScanStartFrame != -1) { 985 long frames = frameNumber - mAutoFocusScanStartFrame; 986 long dt = SystemClock.uptimeMillis() - mAutoFocusScanStartTime; 987 int fps = Math.round(frames * 1000f / dt); 988 String report = String.format("%s scan: fps=%d frames=%d", 989 passive ? "CAF" : "AF", fps, frames); 990 Log.v(TAG, report); 991 mUI.showDebugMessage(String.format("%d / %d", frames, fps)); 992 mAutoFocusScanStartFrame = -1; 993 } 994 break; 995 } 996 } 997 998 @Override 999 public void onReadyStateChanged(boolean readyForCapture) { 1000 if (readyForCapture) { 1001 mAppController.getCameraAppUI().enableModeOptions(); 1002 } 1003 mAppController.setShutterEnabled(readyForCapture); 1004 } 1005 1006 @Override 1007 public String getPeekAccessibilityString() { 1008 return mAppController.getAndroidContext() 1009 .getResources().getString(R.string.photo_accessibility_peek); 1010 } 1011 1012 @Override 1013 public void onThumbnailResult(byte[] jpegData) { 1014 getServices().getRemoteShutterListener().onPictureTaken(jpegData); 1015 } 1016 1017 @Override 1018 public void onPictureTaken(CaptureSession session) { 1019 mAppController.getCameraAppUI().enableModeOptions(); 1020 } 1021 1022 @Override 1023 public void onPictureSaved(Uri uri) { 1024 mAppController.notifyNewMedia(uri); 1025 } 1026 1027 @Override 1028 public void onTakePictureProgress(float progress) { 1029 mUI.setPictureTakingProgress((int) (progress * 100)); 1030 } 1031 1032 @Override 1033 public void onPictureTakingFailed() { 1034 mAppController.getFatalErrorHandler().onMediaStorageFailure(); 1035 } 1036 1037 /** 1038 * Updates the preview transform matrix to adapt to the current preview 1039 * width, height, and orientation. 1040 */ 1041 public void updatePreviewTransform() { 1042 int width; 1043 int height; 1044 synchronized (mDimensionLock) { 1045 width = mScreenWidth; 1046 height = mScreenHeight; 1047 } 1048 updatePreviewTransform(width, height); 1049 } 1050 1051 /** 1052 * @return Depending on whether we're in sticky-HDR mode or not, return the 1053 * proper callback to be used for when the HDR/HDR+ button is 1054 * pressed. 1055 */ 1056 private ButtonManager.ButtonCallback getHdrButtonCallback() { 1057 if (mStickyGcamCamera) { 1058 return new ButtonManager.ButtonCallback() { 1059 @Override 1060 public void onStateChanged(int state) { 1061 if (mPaused) { 1062 return; 1063 } 1064 if (state == ButtonManager.ON) { 1065 throw new IllegalStateException( 1066 "Can't leave hdr plus mode if switching to hdr plus mode."); 1067 } 1068 SettingsManager settingsManager = mAppController.getSettingsManager(); 1069 settingsManager.set(mAppController.getModuleScope(), 1070 Keys.KEY_REQUEST_RETURN_HDR_PLUS, false); 1071 switchToRegularCapture(); 1072 } 1073 }; 1074 } else { 1075 return new ButtonManager.ButtonCallback() { 1076 @Override 1077 public void onStateChanged(int hdrEnabled) { 1078 if (mPaused) { 1079 return; 1080 } 1081 1082 // Only reload the camera if we are toggling HDR+. 1083 if (GcamHelper.hasGcamCapture(mAppController.getCameraFeatureConfig())) { 1084 mHdrPlusEnabled = hdrEnabled == 1; 1085 switchCamera(); 1086 } else { 1087 mHdrSceneEnabled = hdrEnabled == 1; 1088 } 1089 } 1090 }; 1091 } 1092 } 1093 1094 /** 1095 * @return Depending on whether we're in sticky-HDR mode or not, this 1096 * returns the proper callback to be used for when the camera 1097 * (front/back switch) button is pressed. 1098 */ 1099 private ButtonManager.ButtonCallback getCameraCallback() { 1100 if (mStickyGcamCamera) { 1101 return new ButtonManager.ButtonCallback() { 1102 @Override 1103 public void onStateChanged(int state) { 1104 if (mPaused) { 1105 return; 1106 } 1107 1108 // At the time this callback is fired, the camera id setting 1109 // has changed to the desired camera. 1110 SettingsManager settingsManager = mAppController.getSettingsManager(); 1111 if (Keys.isCameraBackFacing(settingsManager, 1112 mAppController.getModuleScope())) { 1113 throw new IllegalStateException( 1114 "Hdr plus should never be switching from front facing camera."); 1115 } 1116 1117 // Switch to photo mode, but request a return to hdr plus on 1118 // switching to back camera again. 1119 settingsManager.set(mAppController.getModuleScope(), 1120 Keys.KEY_REQUEST_RETURN_HDR_PLUS, true); 1121 switchToRegularCapture(); 1122 } 1123 }; 1124 } else { 1125 return new ButtonManager.ButtonCallback() { 1126 @Override 1127 public void onStateChanged(int cameraId) { 1128 if (mPaused) { 1129 return; 1130 } 1131 1132 ButtonManager buttonManager = mAppController.getButtonManager(); 1133 buttonManager.disableCameraButtonAndBlock(); 1134 1135 // At the time this callback is fired, the camera id 1136 // has be set to the desired camera. 1137 mSettingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID, 1138 cameraId); 1139 1140 Log.d(TAG, "Start to switch camera. cameraId=" + cameraId); 1141 mCameraFacing = getFacingFromCameraId(cameraId); 1142 mShowErrorAndFinish = !updateCameraCharacteristics(); 1143 switchCamera(); 1144 } 1145 }; 1146 } 1147 } 1148 1149 /** 1150 * Switches to PhotoModule to do regular photo captures. 1151 * <p> 1152 * TODO: Remove this once we use CaptureModule for photo taking. 1153 */ 1154 private void switchToRegularCapture() { 1155 // Turn off HDR+ before switching back to normal photo mode. 1156 SettingsManager settingsManager = mAppController.getSettingsManager(); 1157 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false); 1158 1159 // Disable this button to prevent callbacks from this module from firing 1160 // while we are transitioning modules. 1161 ButtonManager buttonManager = mAppController.getButtonManager(); 1162 buttonManager.disableButtonClick(ButtonManager.BUTTON_HDR_PLUS); 1163 mAppController.getCameraAppUI().freezeScreenUntilPreviewReady(); 1164 mAppController.onModeSelected(mContext.getResources().getInteger( 1165 R.integer.camera_mode_photo)); 1166 buttonManager.enableButtonClick(ButtonManager.BUTTON_HDR_PLUS); 1167 } 1168 1169 /** 1170 * Called when the preview started. Informs the app controller and queues a 1171 * transform update when the next preview frame arrives. 1172 */ 1173 private void onPreviewStarted() { 1174 if (mState == ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED) { 1175 mState = ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE; 1176 } 1177 mAppController.onPreviewStarted(); 1178 } 1179 1180 /** 1181 * Update the preview transform based on the new dimensions. Will not force 1182 * an update, if it's not necessary. 1183 */ 1184 private void updatePreviewTransform(int incomingWidth, int incomingHeight) { 1185 updatePreviewTransform(incomingWidth, incomingHeight, false); 1186 } 1187 1188 /** 1189 * Returns whether it is necessary to apply device-specific fix for b/19271661 1190 * on the AutoTransform Path, i.e. USE_AUTOTRANSFORM_UI_LAYOUT == true 1191 * 1192 * @return whether to apply workaround fix for b/19271661 1193 */ 1194 private boolean requiresNexus4SpecificFixFor16By9Previews() { 1195 return USE_AUTOTRANSFORM_UI_LAYOUT && ApiHelper.IS_NEXUS_4 1196 && is16by9AspectRatio(mPictureSize); 1197 } 1198 1199 /*** 1200 * Update the preview transform based on the new dimensions. TODO: Make work 1201 * with all: aspect ratios/resolutions x screens/cameras. 1202 */ 1203 private void updatePreviewTransform(int incomingWidth, int incomingHeight, 1204 boolean forceUpdate) { 1205 Log.d(TAG, "updatePreviewTransform: " + incomingWidth + " x " + incomingHeight); 1206 1207 synchronized (mDimensionLock) { 1208 int incomingRotation = CameraUtil.getDisplayRotation(); 1209 // Check for an actual change: 1210 if (mScreenHeight == incomingHeight && mScreenWidth == incomingWidth && 1211 incomingRotation == mDisplayRotation && !forceUpdate) { 1212 return; 1213 } 1214 // Update display rotation and dimensions 1215 mDisplayRotation = incomingRotation; 1216 mScreenWidth = incomingWidth; 1217 mScreenHeight = incomingHeight; 1218 updatePreviewBufferDimension(); 1219 1220 // Assumptions: 1221 // - Aspect ratio for the sensor buffers is in landscape 1222 // orientation, 1223 // - Dimensions of buffers received are rotated to the natural 1224 // device orientation. 1225 // - The contents of each buffer are rotated by the inverse of 1226 // the display rotation. 1227 // - Surface scales the buffer to fit the current view bounds. 1228 1229 // Get natural orientation and buffer dimensions 1230 1231 if(USE_AUTOTRANSFORM_UI_LAYOUT) { 1232 // Use PhotoUI-based AutoTransformation Interface 1233 if (mPreviewBufferWidth != 0 && mPreviewBufferHeight != 0) { 1234 if (requiresNexus4SpecificFixFor16By9Previews()) { 1235 // Force preview size to be 16:9, even though surface is 4:3 1236 // Surface content is assumed to be 16:9. 1237 mAppController.updatePreviewAspectRatio(16.f / 9.f); 1238 } else { 1239 mAppController.updatePreviewAspectRatio( 1240 mPreviewBufferWidth / (float) mPreviewBufferHeight); 1241 } 1242 } 1243 } else { 1244 Matrix transformMatrix = mPreviewTransformCalculator.toTransformMatrix( 1245 new Size(mScreenWidth, mScreenHeight), 1246 new Size(mPreviewBufferWidth, mPreviewBufferHeight)); 1247 mAppController.updatePreviewTransform(transformMatrix); 1248 } 1249 } 1250 } 1251 1252 1253 /** 1254 * Calculates whether a picture size is 16:9 ratio, regardless of its 1255 * orientation. 1256 * 1257 * @param size the size of the picture to be considered 1258 * @return true, if the picture is 16:9; false if it's invalid or size is null 1259 */ 1260 private boolean is16by9AspectRatio(Size size) { 1261 if (size == null || size.getWidth() == 0 || size.getHeight() == 0) { 1262 return false; 1263 } 1264 1265 // Normalize aspect ratio to be greater than 1. 1266 final float aspectRatio = (size.getHeight() > size.getWidth()) 1267 ? (size.getHeight() / (float) size.getWidth()) 1268 : (size.getWidth() / (float) size.getHeight()); 1269 1270 return Math.abs(aspectRatio - (16.f / 9.f)) < 0.001f; 1271 } 1272 1273 /** 1274 * Based on the current picture size, selects the best preview dimension and 1275 * stores it in {@link #mPreviewBufferWidth} and 1276 * {@link #mPreviewBufferHeight}. 1277 */ 1278 private void updatePreviewBufferDimension() { 1279 if (mCamera == null) { 1280 return; 1281 } 1282 1283 Size previewBufferSize = mCamera.pickPreviewSize(mPictureSize, mContext); 1284 mPreviewBufferWidth = previewBufferSize.getWidth(); 1285 mPreviewBufferHeight = previewBufferSize.getHeight(); 1286 1287 // Workaround for N4 TextureView/HAL issues b/19271661 for 16:9 preview 1288 // streams. 1289 if (requiresNexus4SpecificFixFor16By9Previews()) { 1290 // Override the preview selection logic to the largest N4 4:3 1291 // preview size but pass in 16:9 aspect ratio in 1292 // UpdatePreviewAspectRatio later. 1293 mPreviewBufferWidth = 1280; 1294 mPreviewBufferHeight = 960; 1295 } 1296 updatePreviewBufferSize(); 1297 } 1298 1299 /** 1300 * Open camera and start the preview. 1301 */ 1302 private void openCameraAndStartPreview() { 1303 Profile guard = mProfiler.create("CaptureModule.openCameraAndStartPreview()").start(); 1304 try { 1305 // TODO Given the current design, we cannot guarantee that one of 1306 // CaptureReadyCallback.onSetupFailed or onReadyForCapture will 1307 // be called (see below), so it's possible that 1308 // mCameraOpenCloseLock.release() is never called under extremely 1309 // rare cases. If we leak the lock, this timeout ensures that we at 1310 // least crash so we don't deadlock the app. 1311 if (!mCameraOpenCloseLock.tryAcquire(CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS, 1312 TimeUnit.MILLISECONDS)) { 1313 throw new RuntimeException("Time out waiting to acquire camera-open lock."); 1314 } 1315 } catch (InterruptedException e) { 1316 throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e); 1317 } 1318 1319 guard.mark("Acquired mCameraOpenCloseLock"); 1320 1321 if (mOneCameraOpener == null) { 1322 Log.e(TAG, "no available OneCameraManager, showing error dialog"); 1323 mCameraOpenCloseLock.release(); 1324 mAppController.getFatalErrorHandler().onGenericCameraAccessFailure(); 1325 guard.stop("No OneCameraManager"); 1326 return; 1327 } 1328 if (mCamera != null) { 1329 // If the camera is already open, do nothing. 1330 Log.d(TAG, "Camera already open, not re-opening."); 1331 mCameraOpenCloseLock.release(); 1332 guard.stop("Camera is already open"); 1333 return; 1334 } 1335 1336 // Derive objects necessary for camera creation. 1337 MainThread mainThread = MainThread.create(); 1338 ImageRotationCalculator imageRotationCalculator = ImageRotationCalculatorImpl 1339 .from(mAppController.getOrientationManager(), mCameraCharacteristics); 1340 1341 // Only enable GCam on the back camera 1342 boolean useHdr = mHdrPlusEnabled && mCameraFacing == Facing.BACK; 1343 1344 CameraId cameraId = mOneCameraManager.findFirstCameraFacing(mCameraFacing); 1345 final String settingScope = SettingsManager.getCameraSettingScope(cameraId.getValue()); 1346 1347 OneCameraCaptureSetting captureSetting; 1348 // Read the preferred picture size from the setting. 1349 try { 1350 mPictureSize = mAppController.getResolutionSetting().getPictureSize( 1351 cameraId, mCameraFacing); 1352 captureSetting = OneCameraCaptureSetting.create(mPictureSize, mSettingsManager, 1353 getHardwareSpec(), settingScope, useHdr); 1354 } catch (OneCameraAccessException ex) { 1355 mAppController.getFatalErrorHandler().onGenericCameraAccessFailure(); 1356 return; 1357 } 1358 1359 mOneCameraOpener.open(cameraId, captureSetting, mCameraHandler, mainThread, 1360 imageRotationCalculator, mBurstController, mSoundPlayer, 1361 new OpenCallback() { 1362 @Override 1363 public void onFailure() { 1364 Log.e(TAG, "Could not open camera."); 1365 // Sometimes the failure happens due to the controller 1366 // being in paused state but mCamera is already 1367 // initialized. In these cases we just need to close the 1368 // camera device without showing the error dialog. 1369 // Application will properly reopen the camera on the next 1370 // resume operation (b/21025113). 1371 boolean isControllerPaused = mAppController.isPaused(); 1372 if (mCamera != null) { 1373 mCamera.close(); 1374 } 1375 mCamera = null; 1376 mCameraOpenCloseLock.release(); 1377 if (!isControllerPaused) { 1378 mAppController.getFatalErrorHandler().onCameraOpenFailure(); 1379 } 1380 } 1381 1382 @Override 1383 public void onCameraClosed() { 1384 mCamera = null; 1385 mCameraOpenCloseLock.release(); 1386 } 1387 1388 @Override 1389 public void onCameraOpened(@Nonnull final OneCamera camera) { 1390 Log.d(TAG, "onCameraOpened: " + camera); 1391 mCamera = camera; 1392 1393 // A race condition exists where the camera may be in the process 1394 // of opening (blocked), but the activity gets destroyed. If the 1395 // preview is initialized or callbacks are invoked on a destroyed 1396 // activity, bad things can happen. 1397 if (mAppController.isPaused()) { 1398 onFailure(); 1399 return; 1400 } 1401 1402 // When camera is opened, the zoom is implicitly reset to 1.0f 1403 mZoomValue = 1.0f; 1404 1405 updatePreviewBufferDimension(); 1406 1407 // If the surface texture is not destroyed, it may have 1408 // the last frame lingering. We need to hold off setting 1409 // transform until preview is started. 1410 updatePreviewBufferSize(); 1411 mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED; 1412 Log.d(TAG, "starting preview ..."); 1413 1414 // TODO: make mFocusController final and remove null 1415 // check. 1416 if (mFocusController != null) { 1417 camera.setFocusDistanceListener(mFocusController); 1418 } 1419 1420 mMainThread.execute(new Runnable() { 1421 @Override 1422 public void run() { 1423 mAppController.getCameraAppUI().onChangeCamera(); 1424 mAppController.getButtonManager().enableCameraButton(); 1425 } 1426 }); 1427 1428 // TODO: Consider rolling these two calls into one. 1429 camera.startPreview(new Surface(getPreviewSurfaceTexture()), 1430 new CaptureReadyCallback() { 1431 @Override 1432 public void onSetupFailed() { 1433 // We must release this lock here, 1434 // before posting to the main handler 1435 // since we may be blocked in pause(), 1436 // getting ready to close the camera. 1437 mCameraOpenCloseLock.release(); 1438 Log.e(TAG, "Could not set up preview."); 1439 mMainThread.execute(new Runnable() { 1440 @Override 1441 public void run() { 1442 if (mCamera == null) { 1443 Log.d(TAG, "Camera closed, aborting."); 1444 return; 1445 } 1446 mCamera.close(); 1447 mCamera = null; 1448 // TODO: Show an error message 1449 // and exit. 1450 } 1451 }); 1452 } 1453 1454 @Override 1455 public void onReadyForCapture() { 1456 // We must release this lock here, 1457 // before posting to the main handler 1458 // since we may be blocked in pause(), 1459 // getting ready to close the camera. 1460 mCameraOpenCloseLock.release(); 1461 mMainThread.execute(new Runnable() { 1462 @Override 1463 public void run() { 1464 Log.d(TAG, "Ready for capture."); 1465 if (mCamera == null) { 1466 Log.d(TAG, "Camera closed, aborting."); 1467 return; 1468 } 1469 onPreviewStarted(); 1470 // May be overridden by 1471 // subsequent call to 1472 // onReadyStateChanged(). 1473 onReadyStateChanged(true); 1474 mCamera.setReadyStateChangedListener( 1475 CaptureModule.this); 1476 // Enable zooming after preview 1477 // has started. 1478 mUI.initializeZoom(mCamera.getMaxZoom()); 1479 mCamera.setFocusStateListener(CaptureModule.this); 1480 } 1481 }); 1482 } 1483 }); 1484 } 1485 }, mAppController.getFatalErrorHandler()); 1486 guard.stop("mOneCameraOpener.open()"); 1487 } 1488 1489 private void closeCamera() { 1490 Profile profile = mProfiler.create("CaptureModule.closeCamera()").start(); 1491 try { 1492 mCameraOpenCloseLock.acquire(); 1493 } catch (InterruptedException e) { 1494 throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e); 1495 } 1496 profile.mark("mCameraOpenCloseLock.acquire()"); 1497 try { 1498 if (mCamera != null) { 1499 mCamera.close(); 1500 profile.mark("mCamera.close()"); 1501 mCamera.setFocusStateListener(null); 1502 mCamera = null; 1503 } 1504 } finally { 1505 mCameraOpenCloseLock.release(); 1506 } 1507 profile.stop(); 1508 } 1509 1510 /** 1511 * Re-initialize the camera if e.g. the HDR mode or facing property changed. 1512 */ 1513 private void switchCamera() { 1514 if (mShowErrorAndFinish) { 1515 return; 1516 } 1517 if (mPaused) { 1518 return; 1519 } 1520 cancelCountDown(); 1521 mAppController.freezeScreenUntilPreviewReady(); 1522 initSurfaceTextureConsumer(); 1523 } 1524 1525 /** 1526 * Returns which way around the camera is facing, based on it's ID. 1527 * <p> 1528 * TODO: This needs to change so that we store the direction directly in the 1529 * settings, rather than a Camera ID. 1530 */ 1531 private static Facing getFacingFromCameraId(int cameraId) { 1532 return cameraId == 1 ? Facing.FRONT : Facing.BACK; 1533 } 1534 1535 private void resetTextureBufferSize() { 1536 // According to the documentation for 1537 // SurfaceTexture.setDefaultBufferSize, 1538 // photo and video based image producers (presumably only Camera 1 api), 1539 // override this buffer size. Any module that uses egl to render to a 1540 // SurfaceTexture must have these buffer sizes reset manually. Otherwise 1541 // the SurfaceTexture cannot be transformed by matrix set on the 1542 // TextureView. 1543 updatePreviewBufferSize(); 1544 } 1545} 1546