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
17package com.android.camera;
18
19import android.content.Context;
20import android.graphics.Matrix;
21import android.graphics.Point;
22import android.graphics.RectF;
23import android.graphics.SurfaceTexture;
24import android.location.Location;
25import android.media.MediaActionSound;
26import android.net.Uri;
27import android.os.AsyncTask;
28import android.os.Handler;
29import android.os.HandlerThread;
30import android.os.SystemClock;
31import android.view.GestureDetector;
32import android.view.KeyEvent;
33import android.view.MotionEvent;
34import android.view.Surface;
35import android.view.View;
36
37import com.android.camera.app.AppController;
38import com.android.camera.app.CameraAppUI;
39import com.android.camera.app.CameraAppUI.BottomBarUISpec;
40import com.android.camera.app.LocationManager;
41import com.android.camera.app.OrientationManager.DeviceOrientation;
42import com.android.camera.async.MainThread;
43import com.android.camera.burst.BurstFacade;
44import com.android.camera.burst.BurstFacadeFactory;
45import com.android.camera.burst.BurstReadyStateChangeListener;
46import com.android.camera.burst.OrientationLockController;
47import com.android.camera.captureintent.PreviewTransformCalculator;
48import com.android.camera.debug.DebugPropertyHelper;
49import com.android.camera.debug.Log;
50import com.android.camera.debug.Log.Tag;
51import com.android.camera.device.CameraId;
52import com.android.camera.hardware.HardwareSpec;
53import com.android.camera.hardware.HeadingSensor;
54import com.android.camera.module.ModuleController;
55import com.android.camera.one.OneCamera;
56import com.android.camera.one.OneCamera.AutoFocusState;
57import com.android.camera.one.OneCamera.CaptureReadyCallback;
58import com.android.camera.one.OneCamera.Facing;
59import com.android.camera.one.OneCamera.OpenCallback;
60import com.android.camera.one.OneCamera.PhotoCaptureParameters;
61import com.android.camera.one.OneCameraAccessException;
62import com.android.camera.one.OneCameraCaptureSetting;
63import com.android.camera.one.OneCameraCharacteristics;
64import com.android.camera.one.OneCameraException;
65import com.android.camera.one.OneCameraManager;
66import com.android.camera.one.OneCameraModule;
67import com.android.camera.one.OneCameraOpener;
68import com.android.camera.one.config.OneCameraFeatureConfig;
69import com.android.camera.one.v2.photo.ImageRotationCalculator;
70import com.android.camera.one.v2.photo.ImageRotationCalculatorImpl;
71import com.android.camera.remote.RemoteCameraModule;
72import com.android.camera.session.CaptureSession;
73import com.android.camera.settings.Keys;
74import com.android.camera.settings.SettingsManager;
75import com.android.camera.stats.CaptureStats;
76import com.android.camera.stats.UsageStatistics;
77import com.android.camera.stats.profiler.Profile;
78import com.android.camera.stats.profiler.Profiler;
79import com.android.camera.stats.profiler.Profilers;
80import com.android.camera.ui.CountDownView;
81import com.android.camera.ui.PreviewStatusListener;
82import com.android.camera.ui.TouchCoordinate;
83import com.android.camera.ui.focus.FocusController;
84import com.android.camera.ui.focus.FocusSound;
85import com.android.camera.util.AndroidServices;
86import com.android.camera.util.ApiHelper;
87import com.android.camera.util.CameraUtil;
88import com.android.camera.util.GcamHelper;
89import com.android.camera.util.Size;
90import com.android.camera2.R;
91import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
92import com.google.common.logging.eventprotos;
93
94import java.util.concurrent.Semaphore;
95import java.util.concurrent.TimeUnit;
96
97import 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 */
107public 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
346    public CaptureModule(AppController appController) {
347        this(appController, false);
348    }
349
350    /** Constructs a new capture module. */
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
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
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();
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
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
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
482    public void onShutterCoordinate(TouchCoordinate coord) {
483        mLastShutterTouchCoordinate = coord;
484    }
485
486    @Override
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
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
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     */
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
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
578    private long getSessionTime() {
579        // TODO: Replace with a mockable TimeProvider interface.
580        return System.currentTimeMillis();
581    }
582
583    @Override
584    public void onCountDownFinished() {
585        mAppController.getCameraAppUI().transitionToCapture();
586        mAppController.getCameraAppUI().showModeOptions();
587        if (mPaused) {
588            return;
589        }
590        takePictureNow();
591    }
592
593    @Override
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
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
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
627    public void onRemoteShutterPress() {
628        Log.d(TAG, "onRemoteShutterPress");
629        // TODO: Check whether shutter is enabled.
630        takePictureNow();
631    }
632
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
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
659    private SurfaceTexture getPreviewSurfaceTexture() {
660        synchronized (mSurfaceTextureLock) {
661            return mPreviewSurfaceTexture;
662        }
663    }
664
665    private void updatePreviewBufferSize() {
666        synchronized (mSurfaceTextureLock) {
667            if (mPreviewSurfaceTexture != null) {
668                mPreviewSurfaceTexture.setDefaultBufferSize(mPreviewBufferWidth,
669                        mPreviewBufferHeight);
670            }
671        }
672    }
673
674    @Override
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
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
744    public void destroy() {
745        mSoundPlayer.release();
746        mMediaActionSound.release();
747        mCameraHandler.getLooper().quitSafely();
748    }
749
750    @Override
751    public void onLayoutOrientationChanged(boolean isLandscape) {
752        Log.d(TAG, "onLayoutOrientationChanged");
753    }
754
755    @Override
756    public void onCameraAvailable(CameraProxy cameraProxy) {
757        // Ignore since we manage the camera ourselves until we remove this.
758    }
759
760    @Override
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
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();
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, mContext);
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     * <p>
1541     * TODO: This needs to change so that we store the direction directly in the
1542     * settings, rather than a Camera ID.
1543     */
1544    private static Facing getFacingFromCameraId(int cameraId) {
1545        return cameraId == 1 ? Facing.FRONT : Facing.BACK;
1546    }
1547
1548    private void resetTextureBufferSize() {
1549        // According to the documentation for
1550        // SurfaceTexture.setDefaultBufferSize,
1551        // photo and video based image producers (presumably only Camera 1 api),
1552        // override this buffer size. Any module that uses egl to render to a
1553        // SurfaceTexture must have these buffer sizes reset manually. Otherwise
1554        // the SurfaceTexture cannot be transformed by matrix set on the
1555        // TextureView.
1556        updatePreviewBufferSize();
1557    }
1558}
1559