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