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