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