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