2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.camera;
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.Handler;
28 import android.os.HandlerThread;
29 import android.os.SystemClock;
30 import android.view.GestureDetector;
31 import android.view.KeyEvent;
32 import android.view.MotionEvent;
33 import android.view.Surface;
34 import android.view.View;
36 import com.android.camera.app.AppController;
37 import com.android.camera.app.CameraAppUI;
38 import com.android.camera.app.CameraAppUI.BottomBarUISpec;
39 import com.android.camera.app.LocationManager;
40 import com.android.camera.app.OrientationManager.DeviceOrientation;
41 import com.android.camera.async.MainThread;
42 import com.android.camera.burst.BurstFacade;
43 import com.android.camera.burst.BurstFacadeFactory;
44 import com.android.camera.burst.BurstReadyStateChangeListener;
45 import com.android.camera.burst.OrientationLockController;
46 import com.android.camera.captureintent.PreviewTransformCalculator;
47 import com.android.camera.debug.DebugPropertyHelper;
48 import com.android.camera.debug.Log;
49 import com.android.camera.debug.Log.Tag;
50 import com.android.camera.hardware.HardwareSpec;
51 import com.android.camera.hardware.HeadingSensor;
52 import com.android.camera.module.ModuleController;
53 import com.android.camera.one.OneCamera;
54 import com.android.camera.one.OneCamera.AutoFocusState;
55 import com.android.camera.one.OneCamera.CaptureReadyCallback;
56 import com.android.camera.one.OneCamera.Facing;
57 import com.android.camera.one.OneCamera.OpenCallback;
58 import com.android.camera.one.OneCamera.PhotoCaptureParameters;
59 import com.android.camera.one.OneCameraAccessException;
60 import com.android.camera.one.OneCameraCaptureSetting;
61 import com.android.camera.one.OneCameraCharacteristics;
62 import com.android.camera.one.OneCameraManager;
63 import com.android.camera.one.v2.OneCameraManagerImpl;
64 import com.android.camera.one.v2.photo.ImageRotationCalculator;
65 import com.android.camera.one.v2.photo.ImageRotationCalculatorImpl;
66 import com.android.camera.remote.RemoteCameraModule;
67 import com.android.camera.session.CaptureSession;
68 import com.android.camera.settings.Keys;
69 import com.android.camera.settings.SettingsManager;
70 import com.android.camera.stats.UsageStatistics;
71 import com.android.camera.stats.profiler.Profile;
72 import com.android.camera.stats.profiler.Profiler;
73 import com.android.camera.stats.profiler.Profilers;
74 import com.android.camera.ui.CountDownView;
75 import com.android.camera.ui.PreviewStatusListener;
76 import com.android.camera.ui.TouchCoordinate;
77 import com.android.camera.ui.focus.FocusController;
78 import com.android.camera.ui.focus.FocusSound;
79 import com.android.camera.util.AndroidServices;
80 import com.android.camera.util.ApiHelper;
81 import com.android.camera.util.CameraUtil;
82 import com.android.camera.util.GcamHelper;
83 import com.android.camera.util.Size;
84 import com.android.camera2.R;
85 import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
87 import java.util.concurrent.Semaphore;
88 import java.util.concurrent.TimeUnit;
91 * New Capture module that is made to support photo and video capture on top of
92 * the OneCamera API, to transparently support GCam.
94 * This has been a re-write with pieces taken and improved from GCamModule and
95 * PhotoModule, which are to be retired eventually.
98 public class CaptureModule extends CameraModule implements
100 CountDownView.OnCountDownStatusListener,
101 OneCamera.PictureCallback,
102 OneCamera.FocusStateListener,
103 OneCamera.ReadyStateChangedListener,
106 private static final Tag TAG = new Tag("CaptureModule");
107 /** Enable additional debug output. */
108 private static final boolean DEBUG = true;
109 /** Workaround Flag for b/19271661 to use autotransformation in Capture Layout in Nexus4 **/
110 private static final boolean USE_AUTOTRANSFORM_UI_LAYOUT = ApiHelper.IS_NEXUS_4;
112 /** Timeout for camera open/close operations. */
113 private static final int CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS = 2500;
115 /** System Properties switch to enable debugging focus UI. */
116 private static final boolean CAPTURE_DEBUG_UI = DebugPropertyHelper.showCaptureDebugUI();
118 private final Object mDimensionLock = new Object();
121 * Sticky Gcam mode is when this module's sole purpose it to be the Gcam
122 * mode. If true, the device uses {@link PhotoModule} for normal picture
125 private final boolean mStickyGcamCamera;
127 /** Controller giving us access to other services. */
128 private final AppController mAppController;
129 /** The applications settings manager. */
130 private final SettingsManager mSettingsManager;
131 /** Application context. */
132 private final Context mContext;
134 private CaptureModuleUI mUI;
135 /** The camera manager used to open cameras. */
136 private OneCameraManager mCameraManager;
137 /** The currently opened camera device, or null if the camera is closed. */
138 private OneCamera mCamera;
139 /** The selected picture size. */
140 private Size mPictureSize;
141 /** Held when opening or closing the camera. */
142 private final Semaphore mCameraOpenCloseLock = new Semaphore(1);
143 /** The direction the currently opened camera is facing to. */
144 private Facing mCameraFacing;
145 /** Whether HDR Scene mode is currently enabled. */
146 private boolean mHdrSceneEnabled = false;
147 private boolean mHdrPlusEnabled = false;
148 private final Object mSurfaceTextureLock = new Object();
150 private FocusController mFocusController;
151 private OneCameraCharacteristics mCameraCharacteristics;
152 final private PreviewTransformCalculator mPreviewTransformCalculator;
154 /** The listener to listen events from the CaptureModuleUI. */
155 private final CaptureModuleUI.CaptureModuleUIListener mUIListener =
156 new CaptureModuleUI.CaptureModuleUIListener() {
158 public void onZoomRatioChanged(float zoomRatio) {
159 mZoomValue = zoomRatio;
160 if (mCamera != null) {
161 mCamera.setZoom(zoomRatio);
166 /** The listener to respond preview area changes. */
167 private final PreviewStatusListener.PreviewAreaChangedListener mPreviewAreaChangedListener =
168 new PreviewStatusListener.PreviewAreaChangedListener() {
170 public void onPreviewAreaChanged(RectF previewArea) {
171 mPreviewArea = previewArea;
172 mFocusController.configurePreviewDimensions(previewArea);
176 /** The listener to listen events from the preview. */
177 private final PreviewStatusListener mPreviewStatusListener = new PreviewStatusListener() {
179 public void onPreviewLayoutChanged(View v, int left, int top, int right,
180 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
181 int width = right - left;
182 int height = bottom - top;
183 updatePreviewTransform(width, height, false);
187 public boolean shouldAutoAdjustTransformMatrixOnLayout() {
188 return USE_AUTOTRANSFORM_UI_LAYOUT;
192 public void onPreviewFlipped() {
193 // Do nothing because when preview is flipped, TextureView will lay
194 // itself out again, which will then trigger a transform matrix
199 public GestureDetector.OnGestureListener getGestureListener() {
200 return new GestureDetector.SimpleOnGestureListener() {
202 public boolean onSingleTapUp(MotionEvent ev) {
203 Point tapPoint = new Point((int) ev.getX(), (int) ev.getY());
204 Log.v(TAG, "onSingleTapUpPreview location=" + tapPoint);
205 // TODO: This should query actual capability.
206 if (mCameraFacing == Facing.FRONT) {
209 startActiveFocusAt(tapPoint.x, tapPoint.y);
216 public View.OnTouchListener getTouchListener() {
221 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
222 Log.d(TAG, "onSurfaceTextureAvailable");
223 // Force to re-apply transform matrix here as a workaround for
225 updatePreviewTransform(width, height, true);
226 synchronized (mSurfaceTextureLock) {
227 mPreviewSurfaceTexture = surface;
233 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
234 Log.d(TAG, "onSurfaceTextureDestroyed");
235 synchronized (mSurfaceTextureLock) {
236 mPreviewSurfaceTexture = null;
243 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
244 Log.d(TAG, "onSurfaceTextureSizeChanged");
245 updatePreviewBufferSize();
249 public void onSurfaceTextureUpdated(SurfaceTexture surface) {
250 if (mState == ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE) {
251 Log.d(TAG, "onSurfaceTextureUpdated --> updatePreviewTransform");
252 mState = ModuleState.IDLE;
253 CameraAppUI appUI = mAppController.getCameraAppUI();
254 updatePreviewTransform(appUI.getSurfaceWidth(), appUI.getSurfaceHeight(), true);
259 private final OneCamera.PictureSaverCallback mPictureSaverCallback =
260 new OneCamera.PictureSaverCallback() {
262 public void onRemoteThumbnailAvailable(final byte[] jpegImage) {
263 mMainThread.execute(new Runnable() {
266 mAppController.getServices().getRemoteShutterListener()
267 .onPictureTaken(jpegImage);
273 /** State by the module state machine. */
274 private static enum ModuleState {
276 WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED,
277 UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE,
280 /** The current state of the module. */
281 private ModuleState mState = ModuleState.IDLE;
282 /** Current zoom value. */
283 private float mZoomValue = 1f;
285 /** Records beginning frame of each AF scan. */
286 private long mAutoFocusScanStartFrame = -1;
287 /** Records beginning time of each AF scan in uptimeMillis. */
288 private long mAutoFocusScanStartTime;
290 /** Heading sensor. */
291 private HeadingSensor mHeadingSensor;
293 /** Used to fetch and embed the location into captured images. */
294 private final LocationManager mLocationManager;
295 /** Plays sounds for countdown timer. */
296 private SoundPlayer mSoundPlayer;
297 private final MediaActionSound mMediaActionSound;
299 /** Whether the module is paused right now. */
300 private boolean mPaused;
303 private final MainThread mMainThread;
304 /** Handler thread for camera-related operations. */
305 private Handler mCameraHandler;
307 /** Current display rotation in degrees. */
308 private int mDisplayRotation;
309 /** Current screen width in pixels. */
310 private int mScreenWidth;
311 /** Current screen height in pixels. */
312 private int mScreenHeight;
313 /** Current width of preview frames from camera. */
314 private int mPreviewBufferWidth;
315 /** Current height of preview frames from camera.. */
316 private int mPreviewBufferHeight;
317 /** Area used by preview. */
320 /** The surface texture for the preview. */
321 private SurfaceTexture mPreviewSurfaceTexture;
323 /** The burst manager for controlling the burst. */
324 private final BurstFacade mBurstController;
325 private static final String BURST_SESSIONS_DIR = "burst_sessions";
327 private final Profiler mProfiler = Profilers.instance().guard();
329 public CaptureModule(AppController appController) {
330 this(appController, false);
333 /** Constructs a new capture module. */
334 public CaptureModule(AppController appController, boolean stickyHdr) {
335 super(appController);
336 Profile guard = mProfiler.create("new CaptureModule").start();
338 mMainThread = MainThread.create();
339 mAppController = appController;
340 mContext = mAppController.getAndroidContext();
341 mSettingsManager = mAppController.getSettingsManager();
342 mStickyGcamCamera = stickyHdr;
343 mLocationManager = mAppController.getLocationManager();
344 mPreviewTransformCalculator = new PreviewTransformCalculator(
345 mAppController.getOrientationManager());
347 mBurstController = BurstFacadeFactory.create(mContext,
348 new OrientationLockController() {
350 public void unlockOrientation() {
351 mAppController.getOrientationManager().unlockOrientation();
355 public void lockOrientation() {
356 mAppController.getOrientationManager().lockOrientation();
359 new BurstReadyStateChangeListener() {
361 public void onBurstReadyStateChanged(boolean ready) {
362 // TODO: This needs to take into account the state of
363 // the whole system, not just burst.
364 mAppController.setShutterEnabled(ready);
367 mMediaActionSound = new MediaActionSound();
371 private void updateCameraCharacteristics() {
373 mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraFacing);
374 } catch (OneCameraAccessException ocae) {
375 mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
381 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
382 Profile guard = mProfiler.create("CaptureModule.init").start();
383 Log.d(TAG, "init UseAutotransformUiLayout = " + USE_AUTOTRANSFORM_UI_LAYOUT);
384 HandlerThread thread = new HandlerThread("CaptureModule.mCameraHandler");
386 mCameraHandler = new Handler(thread.getLooper());
387 mCameraManager = mAppController.getCameraManager();
388 mDisplayRotation = CameraUtil.getDisplayRotation();
389 mCameraFacing = getFacingFromCameraId(
390 mSettingsManager.getInteger(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID));
391 updateCameraCharacteristics();
392 mUI = new CaptureModuleUI(activity, mAppController.getModuleLayoutRoot(), mUIListener);
393 mAppController.setPreviewStatusListener(mPreviewStatusListener);
394 synchronized (mSurfaceTextureLock) {
395 mPreviewSurfaceTexture = mAppController.getCameraAppUI().getSurfaceTexture();
397 mSoundPlayer = new SoundPlayer(mContext);
399 FocusSound focusSound = new FocusSound(mSoundPlayer, R.raw.material_camera_focus);
400 mFocusController = new FocusController(mUI.getFocusRing(), focusSound, mMainThread);
402 mHeadingSensor = new HeadingSensor(AndroidServices.instance().provideSensorManager());
404 View cancelButton = activity.findViewById(R.id.shutter_cancel_button);
405 cancelButton.setOnClickListener(new View.OnClickListener() {
407 public void onClick(View view) {
412 mMediaActionSound.load(MediaActionSound.SHUTTER_CLICK);
417 public void onShutterButtonLongPressed() {
419 OneCameraCharacteristics cameraCharacteristics;
420 cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraFacing);
421 DeviceOrientation deviceOrientation = mAppController.getOrientationManager()
422 .getDeviceOrientation();
423 ImageRotationCalculator imageRotationCalculator = ImageRotationCalculatorImpl
424 .from(mAppController.getOrientationManager(), cameraCharacteristics);
426 mBurstController.startBurst(
427 new CaptureSession.CaptureSessionCreator() {
429 public CaptureSession createAndStartEmpty() {
430 return createAndStartEmpty();
434 mCamera.getDirection(),
435 imageRotationCalculator.toImageRotation().getDegrees());
437 } catch (OneCameraAccessException e) {
438 Log.e(TAG, "Cannot start burst", e);
444 public void onShutterButtonFocus(boolean pressed) {
446 // the shutter button was released, stop any bursts.
447 mBurstController.stopBurst();
452 public void onShutterCoordinate(TouchCoordinate coord) {
453 // TODO Auto-generated method stub
457 public void onShutterButtonClick() {
458 if (mCamera == null) {
462 int countDownDuration = mSettingsManager
463 .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);
464 if (countDownDuration > 0) {
466 mAppController.getCameraAppUI().transitionToCancel();
467 mAppController.getCameraAppUI().hideModeOptions();
468 mUI.setCountdownFinishedListener(this);
469 mUI.startCountdown(countDownDuration);
470 // Will take picture later via listener callback.
476 private void takePictureNow() {
477 CaptureSession session = createAndStartCaptureSession();
478 int orientation = mAppController.getOrientationManager().getDeviceOrientation()
481 // TODO: This should really not use getExternalCacheDir and instead use
482 // the SessionStorage API. Need to sync with gcam if that's OK.
483 PhotoCaptureParameters params = new PhotoCaptureParameters(
484 session.getTitle(), orientation, session.getLocation(),
485 mContext.getExternalCacheDir(), this, mPictureSaverCallback,
486 mHeadingSensor.getCurrentHeading(), mZoomValue, 0);
487 mCamera.takePicture(params, session);
491 * Creates, starts and returns a new capture session. The returned session
492 * will have been started with an empty placeholder image.
494 private CaptureSession createAndStartCaptureSession() {
495 long sessionTime = getSessionTime();
496 Location location = mLocationManager.getCurrentLocation();
497 String title = CameraUtil.instance().createJpegName(sessionTime);
498 CaptureSession session = getServices().getCaptureSessionManager()
499 .createNewSession(title, sessionTime, location);
500 session.startEmpty(new Size((int) mPreviewArea.width(), (int) mPreviewArea.height()));
504 private long getSessionTime() {
505 // TODO: Replace with a mockable TimeProvider interface.
506 return System.currentTimeMillis();
510 public void onCountDownFinished() {
511 mAppController.getCameraAppUI().transitionToCapture();
512 mAppController.getCameraAppUI().showModeOptions();
520 public void onRemainingSecondsChanged(int remainingSeconds) {
521 if (remainingSeconds == 1) {
522 mSoundPlayer.play(R.raw.timer_final_second, 0.6f);
523 } else if (remainingSeconds == 2 || remainingSeconds == 3) {
524 mSoundPlayer.play(R.raw.timer_increment, 0.6f);
528 private void cancelCountDown() {
529 if (mUI.isCountingDown()) {
530 // Cancel on-going countdown.
531 mUI.cancelCountDown();
533 mAppController.getCameraAppUI().showModeOptions();
534 mAppController.getCameraAppUI().transitionToCapture();
538 public void onQuickExpose() {
539 mMainThread.execute(new Runnable() {
542 // Starts the short version of the capture animation UI.
543 mAppController.startFlashAnimation(true);
544 mMediaActionSound.play(MediaActionSound.SHUTTER_CLICK);
550 public void onRemoteShutterPress() {
551 Log.d(TAG, "onRemoteShutterPress");
552 // TODO: Check whether shutter is enabled.
556 private void initSurfaceTextureConsumer() {
557 synchronized (mSurfaceTextureLock) {
558 if (mPreviewSurfaceTexture != null) {
559 mPreviewSurfaceTexture.setDefaultBufferSize(
560 mAppController.getCameraAppUI().getSurfaceWidth(),
561 mAppController.getCameraAppUI().getSurfaceHeight());
567 private void reopenCamera() {
572 openCameraAndStartPreview();
575 private SurfaceTexture getPreviewSurfaceTexture() {
576 synchronized (mSurfaceTextureLock) {
577 return mPreviewSurfaceTexture;
581 private void updatePreviewBufferSize() {
582 synchronized (mSurfaceTextureLock) {
583 if (mPreviewSurfaceTexture != null) {
584 mPreviewSurfaceTexture.setDefaultBufferSize(mPreviewBufferWidth,
585 mPreviewBufferHeight);
591 public void resume() {
592 Profile guard = mProfiler.create("CaptureModule.resume").start();
594 // We'll transition into 'ready' once the preview is started.
595 onReadyStateChanged(false);
597 mAppController.addPreviewAreaSizeChangedListener(mPreviewAreaChangedListener);
598 mAppController.addPreviewAreaSizeChangedListener(mUI);
600 mAppController.getCameraAppUI().onChangeCamera();
603 getServices().getRemoteShutterListener().onModuleReady(this);
604 guard.mark("getRemoteShutterListener.onModuleReady");
605 mBurstController.initialize(new SurfaceTexture(0));
607 // TODO: Check if we can really take a photo right now (memory, camera
609 mAppController.getCameraAppUI().enableModeOptions();
610 mAppController.setShutterEnabled(true);
611 mAppController.getCameraAppUI().showAccessibilityZoomUI(
612 mCameraCharacteristics.getAvailableMaxDigitalZoom());
614 mHdrPlusEnabled = mStickyGcamCamera || mAppController.getSettingsManager().getInteger(
615 SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS) == 1;
617 mHdrSceneEnabled = !mStickyGcamCamera && mAppController.getSettingsManager().getBoolean(
618 SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR);
620 // The lock only exists for HDR and causes trouble for non-HDR
622 // TODO: Fix for removing the locks completely is tracked at b/17985028
623 if (!mHdrPlusEnabled) {
624 mCameraOpenCloseLock.release();
627 // This means we are resuming with an existing preview texture. This
628 // means we will never get the onSurfaceTextureAvailable call. So we
629 // have to open the camera and start the preview here.
630 SurfaceTexture texture = getPreviewSurfaceTexture();
633 if (texture != null) {
634 initSurfaceTextureConsumer();
635 guard.mark("initSurfaceTextureConsumer");
638 mSoundPlayer.loadSound(R.raw.timer_final_second);
639 mSoundPlayer.loadSound(R.raw.timer_increment);
642 mHeadingSensor.activate();
643 guard.stop("mHeadingSensor.activate()");
647 public void pause() {
649 mHeadingSensor.deactivate();
651 mAppController.removePreviewAreaSizeChangedListener(mUI);
652 mAppController.removePreviewAreaSizeChangedListener(mPreviewAreaChangedListener);
653 getServices().getRemoteShutterListener().onModuleExit();
654 mBurstController.release();
657 resetTextureBufferSize();
658 mSoundPlayer.unloadSound(R.raw.timer_final_second);
659 mSoundPlayer.unloadSound(R.raw.timer_increment);
663 public void destroy() {
664 mSoundPlayer.release();
665 mMediaActionSound.release();
666 mCameraHandler.getLooper().quitSafely();
670 public void onLayoutOrientationChanged(boolean isLandscape) {
671 Log.d(TAG, "onLayoutOrientationChanged");
675 public void onCameraAvailable(CameraProxy cameraProxy) {
676 // Ignore since we manage the camera ourselves until we remove this.
680 public void hardResetSettings(SettingsManager settingsManager) {
681 if (mStickyGcamCamera) {
682 // Sticky HDR+ mode should hard reset HDR+ to on, and camera back
684 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, true);
685 settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,
686 getBackFacingCameraId());
691 public HardwareSpec getHardwareSpec() {
692 return new HardwareSpec() {
694 public boolean isFrontCameraSupported() {
699 public boolean isHdrSupported() {
700 return mCameraCharacteristics.isHdrSceneSupported();
704 public boolean isHdrPlusSupported() {
705 return GcamHelper.hasGcamCapture(mAppController.getCameraFeatureConfig());
709 public boolean isFlashSupported() {
710 return mCameraCharacteristics.isFlashSupported();
716 public BottomBarUISpec getBottomBarSpec() {
717 HardwareSpec hardwareSpec = getHardwareSpec();
718 BottomBarUISpec bottomBarSpec = new BottomBarUISpec();
719 bottomBarSpec.enableGridLines = true;
720 bottomBarSpec.enableCamera = true;
721 bottomBarSpec.cameraCallback = getCameraCallback();
722 bottomBarSpec.enableHdr = hardwareSpec.isHdrSupported() || hardwareSpec.isHdrPlusSupported();
723 bottomBarSpec.hdrCallback = getHdrButtonCallback();
724 bottomBarSpec.enableSelfTimer = true;
725 bottomBarSpec.showSelfTimer = true;
727 // We must read the key from the settings because the button callback
728 // is not executed until after this method is called.
729 if ((hardwareSpec.isHdrPlusSupported() &&
730 mAppController.getSettingsManager().getBoolean(
731 SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS)) ||
732 ( hardwareSpec.isHdrSupported() &&
733 mAppController.getSettingsManager().getBoolean(
734 SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR))) {
735 // Disable flash if this is a sticky gcam camera, or if
737 bottomBarSpec.enableFlash = false;
739 // If we are not in HDR / GCAM mode, fallback on the
740 // flash supported property for this camera.
741 bottomBarSpec.enableFlash = mCameraCharacteristics.isFlashSupported();
744 bottomBarSpec.enableExposureCompensation =
745 mCameraCharacteristics.isExposureCompensationSupported();
746 bottomBarSpec.minExposureCompensation =
747 mCameraCharacteristics.getMinExposureCompensation();
748 bottomBarSpec.maxExposureCompensation =
749 mCameraCharacteristics.getMaxExposureCompensation();
750 bottomBarSpec.exposureCompensationStep =
751 mCameraCharacteristics.getExposureCompensationStep();
752 bottomBarSpec.exposureCompensationSetCallback =
753 new BottomBarUISpec.ExposureCompensationSetCallback() {
755 public void setExposure(int value) {
756 mSettingsManager.set(
757 mAppController.getCameraScope(), Keys.KEY_EXPOSURE, value);
761 return bottomBarSpec;
765 public boolean isUsingBottomBar() {
770 public boolean onKeyDown(int keyCode, KeyEvent event) {
772 case KeyEvent.KEYCODE_CAMERA:
773 case KeyEvent.KEYCODE_DPAD_CENTER:
774 if (mUI.isCountingDown()) {
776 } else if (event.getRepeatCount() == 0) {
777 onShutterButtonClick();
780 case KeyEvent.KEYCODE_VOLUME_UP:
781 case KeyEvent.KEYCODE_VOLUME_DOWN:
789 public boolean onKeyUp(int keyCode, KeyEvent event) {
791 case KeyEvent.KEYCODE_VOLUME_UP:
792 case KeyEvent.KEYCODE_VOLUME_DOWN:
793 onShutterButtonClick();
799 // TODO: Consider refactoring FocusOverlayManager.
800 // Currently AF state transitions are controlled in OneCameraImpl.
801 // PhotoModule uses FocusOverlayManager which uses API1/portability
802 // logic and coordinates.
803 private void startActiveFocusAt(int viewX, int viewY) {
804 if (mCamera == null) {
805 // If we receive this after the camera is closed, do nothing.
809 // TODO: make mFocusController final and remove null check.
810 if (mFocusController == null) {
811 Log.v(TAG, "CaptureModule mFocusController is null!");
814 mFocusController.showActiveFocusAt(viewX, viewY);
816 // Normalize coordinates to [0,1] per CameraOne API.
817 float points[] = new float[2];
818 points[0] = (viewX - mPreviewArea.left) / mPreviewArea.width();
819 points[1] = (viewY - mPreviewArea.top) / mPreviewArea.height();
821 // Rotate coordinates to portrait orientation per CameraOne API.
822 Matrix rotationMatrix = new Matrix();
823 rotationMatrix.setRotate(mDisplayRotation, 0.5f, 0.5f);
824 rotationMatrix.mapPoints(points);
825 mCamera.triggerFocusAndMeterAtPoint(points[0], points[1]);
827 // Log touch (screen coordinates).
828 if (mZoomValue == 1f) {
829 TouchCoordinate touchCoordinate = new TouchCoordinate(
830 viewX - mPreviewArea.left,
831 viewY - mPreviewArea.top,
832 mPreviewArea.width(),
833 mPreviewArea.height());
834 // TODO: Add to logging: duration, rotation.
835 UsageStatistics.instance().tapToFocus(touchCoordinate, null);
840 * Show AF target in center of preview.
842 private void startPassiveFocus() {
843 // TODO: make mFocusController final and remove null check.
844 if (mFocusController == null) {
848 // TODO: Some passive focus scans may trigger on a location
849 // instead of the center of the screen.
850 mFocusController.showPassiveFocusAtCenter();
854 * Update UI based on AF state changes.
857 public void onFocusStatusUpdate(final AutoFocusState state, long frameNumber) {
858 Log.v(TAG, "AF status is state:" + state);
865 // Unused, manual scans are triggered via the UI
867 case PASSIVE_FOCUSED:
868 case PASSIVE_UNFOCUSED:
872 case ACTIVE_UNFOCUSED:
877 if (CAPTURE_DEBUG_UI) {
878 measureAutoFocusScans(state, frameNumber);
882 private void measureAutoFocusScans(final AutoFocusState state, long frameNumber) {
883 // Log AF scan lengths.
884 boolean passive = false;
888 if (mAutoFocusScanStartFrame == -1) {
889 mAutoFocusScanStartFrame = frameNumber;
890 mAutoFocusScanStartTime = SystemClock.uptimeMillis();
893 case PASSIVE_FOCUSED:
894 case PASSIVE_UNFOCUSED:
897 case ACTIVE_UNFOCUSED:
898 if (mAutoFocusScanStartFrame != -1) {
899 long frames = frameNumber - mAutoFocusScanStartFrame;
900 long dt = SystemClock.uptimeMillis() - mAutoFocusScanStartTime;
901 int fps = Math.round(frames * 1000f / dt);
902 String report = String.format("%s scan: fps=%d frames=%d",
903 passive ? "CAF" : "AF", fps, frames);
905 mUI.showDebugMessage(String.format("%d / %d", frames, fps));
906 mAutoFocusScanStartFrame = -1;
913 public void onReadyStateChanged(boolean readyForCapture) {
914 if (!mBurstController.isReady()) {
918 if (readyForCapture) {
919 mAppController.getCameraAppUI().enableModeOptions();
921 mAppController.setShutterEnabled(readyForCapture);
925 public String getPeekAccessibilityString() {
926 return mAppController.getAndroidContext()
927 .getResources().getString(R.string.photo_accessibility_peek);
931 public void onThumbnailResult(byte[] jpegData) {
932 getServices().getRemoteShutterListener().onPictureTaken(jpegData);
936 public void onPictureTaken(CaptureSession session) {
937 mAppController.getCameraAppUI().enableModeOptions();
941 public void onPictureSaved(Uri uri) {
942 mAppController.notifyNewMedia(uri);
946 public void onTakePictureProgress(float progress) {
947 mUI.setPictureTakingProgress((int) (progress * 100));
951 public void onPictureTakingFailed() {
955 * Updates the preview transform matrix to adapt to the current preview
956 * width, height, and orientation.
958 public void updatePreviewTransform() {
961 synchronized (mDimensionLock) {
962 width = mScreenWidth;
963 height = mScreenHeight;
965 updatePreviewTransform(width, height);
969 * TODO: Remove this method once we are in pure CaptureModule land.
971 private String getBackFacingCameraId() {
972 if (!(mCameraManager instanceof OneCameraManagerImpl)) {
973 throw new IllegalStateException("This should never be called with Camera API V1");
975 OneCameraManagerImpl manager = (OneCameraManagerImpl) mCameraManager;
976 return manager.getFirstBackCameraId();
980 * @return Depending on whether we're in sticky-HDR mode or not, return the
981 * proper callback to be used for when the HDR/HDR+ button is
984 private ButtonManager.ButtonCallback getHdrButtonCallback() {
985 if (mStickyGcamCamera) {
986 return new ButtonManager.ButtonCallback() {
988 public void onStateChanged(int state) {
992 if (state == ButtonManager.ON) {
993 throw new IllegalStateException(
994 "Can't leave hdr plus mode if switching to hdr plus mode.");
996 SettingsManager settingsManager = mAppController.getSettingsManager();
997 settingsManager.set(mAppController.getModuleScope(),
998 Keys.KEY_REQUEST_RETURN_HDR_PLUS, false);
999 switchToRegularCapture();
1003 return new ButtonManager.ButtonCallback() {
1005 public void onStateChanged(int hdrEnabled) {
1010 // Only reload the camera if we are toggling HDR+.
1011 if (GcamHelper.hasGcamCapture(mAppController.getCameraFeatureConfig())) {
1012 mHdrPlusEnabled = hdrEnabled == 1;
1015 mHdrSceneEnabled = hdrEnabled == 1;
1023 * @return Depending on whether we're in sticky-HDR mode or not, this
1024 * returns the proper callback to be used for when the camera
1025 * (front/back switch) button is pressed.
1027 private ButtonManager.ButtonCallback getCameraCallback() {
1028 if (mStickyGcamCamera) {
1029 return new ButtonManager.ButtonCallback() {
1031 public void onStateChanged(int state) {
1036 // At the time this callback is fired, the camera id setting
1037 // has changed to the desired camera.
1038 SettingsManager settingsManager = mAppController.getSettingsManager();
1039 if (Keys.isCameraBackFacing(settingsManager,
1040 mAppController.getModuleScope())) {
1041 throw new IllegalStateException(
1042 "Hdr plus should never be switching from front facing camera.");
1045 // Switch to photo mode, but request a return to hdr plus on
1046 // switching to back camera again.
1047 settingsManager.set(mAppController.getModuleScope(),
1048 Keys.KEY_REQUEST_RETURN_HDR_PLUS, true);
1049 switchToRegularCapture();
1053 return new ButtonManager.ButtonCallback() {
1055 public void onStateChanged(int cameraId) {
1060 // At the time this callback is fired, the camera id
1061 // has be set to the desired camera.
1062 mSettingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,
1065 Log.d(TAG, "Start to switch camera. cameraId=" + cameraId);
1066 mCameraFacing = getFacingFromCameraId(cameraId);
1067 updateCameraCharacteristics();
1075 * Switches to PhotoModule to do regular photo captures.
1077 * TODO: Remove this once we use CaptureModule for photo taking.
1079 private void switchToRegularCapture() {
1080 // Turn off HDR+ before switching back to normal photo mode.
1081 SettingsManager settingsManager = mAppController.getSettingsManager();
1082 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false);
1084 // Disable this button to prevent callbacks from this module from firing
1085 // while we are transitioning modules.
1086 ButtonManager buttonManager = mAppController.getButtonManager();
1087 buttonManager.disableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
1088 mAppController.getCameraAppUI().freezeScreenUntilPreviewReady();
1089 mAppController.onModeSelected(mContext.getResources().getInteger(
1090 R.integer.camera_mode_photo));
1091 buttonManager.enableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
1095 * Called when the preview started. Informs the app controller and queues a
1096 * transform update when the next preview frame arrives.
1098 private void onPreviewStarted() {
1099 if (mState == ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED) {
1100 mState = ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE;
1102 mAppController.onPreviewStarted();
1106 * Update the preview transform based on the new dimensions. Will not force
1107 * an update, if it's not necessary.
1109 private void updatePreviewTransform(int incomingWidth, int incomingHeight) {
1110 updatePreviewTransform(incomingWidth, incomingHeight, false);
1114 * Returns whether it is necessary to apply device-specific fix for b/19271661
1115 * on the AutoTransform Path, i.e. USE_AUTOTRANSFORM_UI_LAYOUT == true
1117 * @return whether to apply workaround fix for b/19271661
1119 private boolean requiresNexus4SpecificFixFor16By9Previews() {
1120 return USE_AUTOTRANSFORM_UI_LAYOUT && ApiHelper.IS_NEXUS_4
1121 && is16by9AspectRatio(mPictureSize);
1125 * Update the preview transform based on the new dimensions. TODO: Make work
1126 * with all: aspect ratios/resolutions x screens/cameras.
1128 private void updatePreviewTransform(int incomingWidth, int incomingHeight,
1129 boolean forceUpdate) {
1130 Log.d(TAG, "updatePreviewTransform: " + incomingWidth + " x " + incomingHeight);
1132 synchronized (mDimensionLock) {
1133 int incomingRotation = CameraUtil.getDisplayRotation();
1134 // Check for an actual change:
1135 if (mScreenHeight == incomingHeight && mScreenWidth == incomingWidth &&
1136 incomingRotation == mDisplayRotation && !forceUpdate) {
1139 // Update display rotation and dimensions
1140 mDisplayRotation = incomingRotation;
1141 mScreenWidth = incomingWidth;
1142 mScreenHeight = incomingHeight;
1143 updatePreviewBufferDimension();
1146 // - Aspect ratio for the sensor buffers is in landscape
1148 // - Dimensions of buffers received are rotated to the natural
1149 // device orientation.
1150 // - The contents of each buffer are rotated by the inverse of
1151 // the display rotation.
1152 // - Surface scales the buffer to fit the current view bounds.
1154 // Get natural orientation and buffer dimensions
1156 if(USE_AUTOTRANSFORM_UI_LAYOUT) {
1157 // Use PhotoUI-based AutoTransformation Interface
1158 if (mPreviewBufferWidth != 0 && mPreviewBufferHeight != 0) {
1159 if (requiresNexus4SpecificFixFor16By9Previews()) {
1160 // Force preview size to be 16:9, even though surface is 4:3
1161 // Surface content is assumed to be 16:9.
1162 mAppController.updatePreviewAspectRatio(16.f / 9.f);
1164 mAppController.updatePreviewAspectRatio(
1165 mPreviewBufferWidth / (float) mPreviewBufferHeight);
1169 Matrix transformMatrix = mPreviewTransformCalculator.toTransformMatrix(
1170 new Size(mScreenWidth, mScreenHeight),
1171 new Size(mPreviewBufferWidth, mPreviewBufferHeight));
1172 mAppController.updatePreviewTransform(transformMatrix);
1179 * Calculates whether a picture size is 16:9 ratio, regardless of its
1182 * @param size the size of the picture to be considered
1183 * @return true, if the picture is 16:9; false if it's invalid or size is null
1185 private boolean is16by9AspectRatio(Size size) {
1186 if (size == null || size.getWidth() == 0 || size.getHeight() == 0) {
1190 // Normalize aspect ratio to be greater than 1.
1191 final float aspectRatio = (size.getHeight() > size.getWidth())
1192 ? (size.getHeight() / (float) size.getWidth())
1193 : (size.getWidth() / (float) size.getHeight());
1195 return Math.abs(aspectRatio - (16.f / 9.f)) < 0.001f;
1199 * Based on the current picture size, selects the best preview dimension and
1200 * stores it in {@link #mPreviewBufferWidth} and
1201 * {@link #mPreviewBufferHeight}.
1203 private void updatePreviewBufferDimension() {
1204 if (mCamera == null) {
1208 Size previewBufferSize = mCamera.pickPreviewSize(mPictureSize, mContext);
1209 mPreviewBufferWidth = previewBufferSize.getWidth();
1210 mPreviewBufferHeight = previewBufferSize.getHeight();
1212 // Workaround for N4 TextureView/HAL issues b/19271661 for 16:9 preview
1214 if (requiresNexus4SpecificFixFor16By9Previews()) {
1215 // Override the preview selection logic to the largest N4 4:3
1216 // preview size but pass in 16:9 aspect ratio in
1217 // UpdatePreviewAspectRatio later.
1218 mPreviewBufferWidth = 1280;
1219 mPreviewBufferHeight = 960;
1221 updatePreviewBufferSize();
1225 * Open camera and start the preview.
1227 private void openCameraAndStartPreview() {
1228 Profile guard = mProfiler.create("CaptureModule.openCameraAndStartPreview()").start();
1230 // TODO Given the current design, we cannot guarantee that one of
1231 // CaptureReadyCallback.onSetupFailed or onReadyForCapture will
1232 // be called (see below), so it's possible that
1233 // mCameraOpenCloseLock.release() is never called under extremely
1234 // rare cases. If we leak the lock, this timeout ensures that we at
1235 // least crash so we don't deadlock the app.
1236 if (!mCameraOpenCloseLock.tryAcquire(CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS,
1237 TimeUnit.MILLISECONDS)) {
1238 throw new RuntimeException("Time out waiting to acquire camera-open lock.");
1240 } catch (InterruptedException e) {
1241 throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e);
1244 guard.mark("Acquired mCameraOpenCloseLock");
1246 if (mCameraManager == null) {
1247 Log.e(TAG, "no available OneCameraManager, showing error dialog");
1248 mCameraOpenCloseLock.release();
1249 mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
1250 guard.stop("No OneCameraManager");
1253 if (mCamera != null) {
1254 // If the camera is already open, do nothing.
1255 Log.d(TAG, "Camera already open, not re-opening.");
1256 mCameraOpenCloseLock.release();
1257 guard.stop("Camera is already open");
1261 // Derive objects necessary for camera creation.
1262 MainThread mainThread = MainThread.create();
1263 ImageRotationCalculator imageRotationCalculator = ImageRotationCalculatorImpl
1264 .from(mAppController.getOrientationManager(), mCameraCharacteristics);
1266 // Only enable GCam on the back camera
1267 boolean useHdr = mHdrPlusEnabled && mCameraFacing == Facing.BACK;
1269 OneCameraCaptureSetting captureSetting;
1270 // Read the preferred picture size from the setting.
1272 captureSetting = OneCameraCaptureSetting.create(
1273 mCameraFacing, mAppController.getResolutionSetting(), mSettingsManager,
1274 mAppController.getModuleScope(), useHdr);
1275 } catch (OneCameraAccessException ex) {
1276 mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
1279 mPictureSize = captureSetting.getCaptureSize();
1281 mCameraManager.open(captureSetting, mCameraHandler, mainThread,
1282 imageRotationCalculator, mBurstController, mSoundPlayer,
1283 new OpenCallback() {
1285 public void onFailure() {
1286 Log.e(TAG, "Could not open camera.");
1288 mCameraOpenCloseLock.release();
1289 mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
1293 public void onCameraClosed() {
1295 mCameraOpenCloseLock.release();
1299 public void onCameraOpened(final OneCamera camera) {
1300 Log.d(TAG, "onCameraOpened: " + camera);
1302 updatePreviewBufferDimension();
1304 // If the surface texture is not destroyed, it may have
1305 // the last frame lingering. We need to hold off setting
1306 // transform until preview is started.
1307 updatePreviewBufferSize();
1308 mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED;
1309 Log.d(TAG, "starting preview ...");
1311 // TODO: make mFocusController final and remove null
1313 if (mFocusController != null) {
1314 camera.setFocusDistanceListener(mFocusController);
1317 // TODO: Consider rolling these two calls into one.
1318 camera.startPreview(new Surface(getPreviewSurfaceTexture()),
1319 new CaptureReadyCallback() {
1321 public void onSetupFailed() {
1322 // We must release this lock here,
1323 // before posting to the main handler
1324 // since we may be blocked in pause(),
1325 // getting ready to close the camera.
1326 mCameraOpenCloseLock.release();
1327 Log.e(TAG, "Could not set up preview.");
1328 mMainThread.execute(new Runnable() {
1331 if (mCamera == null) {
1332 Log.d(TAG, "Camera closed, aborting.");
1337 // TODO: Show an error message
1344 public void onReadyForCapture() {
1345 // We must release this lock here,
1346 // before posting to the main handler
1347 // since we may be blocked in pause(),
1348 // getting ready to close the camera.
1349 mCameraOpenCloseLock.release();
1350 mMainThread.execute(new Runnable() {
1353 Log.d(TAG, "Ready for capture.");
1354 if (mCamera == null) {
1355 Log.d(TAG, "Camera closed, aborting.");
1359 // May be overridden by
1360 // subsequent call to
1361 // onReadyStateChanged().
1362 onReadyStateChanged(true);
1363 mCamera.setReadyStateChangedListener(
1364 CaptureModule.this);
1365 // Enable zooming after preview
1367 mUI.initializeZoom(mCamera.getMaxZoom());
1368 mCamera.setFocusStateListener(CaptureModule.this);
1374 }, mAppController.getFatalErrorHandler());
1375 guard.stop("mCameraManager.open()");
1378 private void closeCamera() {
1379 Profile profile = mProfiler.create("CaptureModule.closeCamera()").start();
1381 mCameraOpenCloseLock.acquire();
1382 } catch (InterruptedException e) {
1383 throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e);
1385 profile.mark("mCameraOpenCloseLock.acquire()");
1387 if (mCamera != null) {
1389 profile.mark("mCamera.close()");
1390 mCamera.setFocusStateListener(null);
1394 mCameraOpenCloseLock.release();
1400 * Re-initialize the camera if e.g. the HDR mode or facing property changed.
1402 private void switchCamera() {
1407 mAppController.freezeScreenUntilPreviewReady();
1408 initSurfaceTextureConsumer();
1411 private int getPreviewOrientation(int deviceOrientationDegrees) {
1412 // Important: Camera2 buffers are already rotated to the natural
1413 // orientation of the device (at least for the back-camera).
1415 return (360 - deviceOrientationDegrees) % 360;
1419 * Returns which way around the camera is facing, based on it's ID.
1421 * TODO: This needs to change so that we store the direction directly in the
1422 * settings, rather than a Camera ID.
1424 private static Facing getFacingFromCameraId(int cameraId) {
1425 return cameraId == 1 ? Facing.FRONT : Facing.BACK;
1428 private void resetTextureBufferSize() {
1429 // According to the documentation for
1430 // SurfaceTexture.setDefaultBufferSize,
1431 // photo and video based image producers (presumably only Camera 1 api),
1432 // override this buffer size. Any module that uses egl to render to a
1433 // SurfaceTexture must have these buffer sizes reset manually. Otherwise
1434 // the SurfaceTexture cannot be transformed by matrix set on the
1436 updatePreviewBufferSize();