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