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