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