OSDN Git Service

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