CaptureModule.java revision 7a02c1146cd465aa89e4f7249d60bf52ac596dc5
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.app.Activity;
20import android.content.Context;
21import android.content.res.Configuration;
22import android.graphics.Bitmap;
23import android.graphics.Matrix;
24import android.graphics.RectF;
25import android.graphics.SurfaceTexture;
26import android.hardware.Sensor;
27import android.hardware.SensorEvent;
28import android.hardware.SensorEventListener;
29import android.hardware.SensorManager;
30import android.location.Location;
31import android.net.Uri;
32import android.os.Handler;
33import android.os.HandlerThread;
34import android.os.SystemClock;
35import android.provider.MediaStore;
36import android.view.KeyEvent;
37import android.view.OrientationEventListener;
38import android.view.Surface;
39import android.view.TextureView;
40import android.view.View;
41import android.view.View.OnLayoutChangeListener;
42
43import com.android.camera.app.AppController;
44import com.android.camera.app.CameraAppUI;
45import com.android.camera.app.CameraAppUI.BottomBarUISpec;
46import com.android.camera.app.LocationManager;
47import com.android.camera.app.MediaSaver;
48import com.android.camera.debug.DebugPropertyHelper;
49import com.android.camera.debug.Log;
50import com.android.camera.debug.Log.Tag;
51import com.android.camera.hardware.HardwareSpec;
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.OneCamera.PhotoCaptureParameters.Flash;
60import com.android.camera.one.OneCameraManager;
61import com.android.camera.one.Settings3A;
62import com.android.camera.one.v2.OneCameraManagerImpl;
63import com.android.camera.remote.RemoteCameraModule;
64import com.android.camera.session.CaptureSession;
65import com.android.camera.settings.Keys;
66import com.android.camera.settings.SettingsManager;
67import com.android.camera.ui.CountDownView;
68import com.android.camera.ui.PreviewStatusListener;
69import com.android.camera.ui.TouchCoordinate;
70import com.android.camera.util.CameraUtil;
71import com.android.camera.util.GcamHelper;
72import com.android.camera.util.Size;
73import com.android.camera.util.UsageStatistics;
74import com.android.camera2.R;
75import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
76
77import java.io.File;
78import java.util.concurrent.Semaphore;
79import java.util.concurrent.TimeUnit;
80
81/**
82 * New Capture module that is made to support photo and video capture on top of
83 * the OneCamera API, to transparently support GCam.
84 * <p>
85 * This has been a re-write with pieces taken and improved from GCamModule and
86 * PhotoModule, which are to be retired eventually.
87 * <p>
88 * TODO:
89 * <ul>
90 * <li>Server-side logging
91 * <li>Focusing (managed by OneCamera implementations)
92 * <li>Show location dialog on first start
93 * <li>Show resolution dialog on certain devices
94 * <li>Store location
95 * <li>Capture intent
96 * </ul>
97 */
98public class CaptureModule extends CameraModule
99        implements MediaSaver.QueueListener,
100        ModuleController,
101        CountDownView.OnCountDownStatusListener,
102        OneCamera.PictureCallback,
103        OneCamera.FocusStateListener,
104        OneCamera.ReadyStateChangedListener,
105        PreviewStatusListener.PreviewAreaChangedListener,
106        RemoteCameraModule,
107        SensorEventListener,
108        SettingsManager.OnSettingChangedListener,
109        TextureView.SurfaceTextureListener {
110
111    /**
112     * Called on layout changes.
113     */
114    private final OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
115        @Override
116        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
117                int oldTop, int oldRight, int oldBottom) {
118            int width = right - left;
119            int height = bottom - top;
120            updatePreviewTransform(width, height, false);
121        }
122    };
123
124    /**
125     * Hide AF target UI element.
126     */
127    Runnable mHideAutoFocusTargetRunnable = new Runnable() {
128        @Override
129        public void run() {
130            // For debug UI off, showAutoFocusSuccess() just hides the AF UI.
131            if (mFocusedAtEnd) {
132                mUI.showAutoFocusSuccess();
133            } else {
134                mUI.showAutoFocusFailure();
135            }
136        }
137    };
138
139    private static final Tag TAG = new Tag("CaptureModule");
140    private static final String PHOTO_MODULE_STRING_ID = "PhotoModule";
141    /** Enable additional debug output. */
142    private static final boolean DEBUG = true;
143    /**
144     * This is the delay before we execute onResume tasks when coming from the
145     * lock screen, to allow time for onPause to execute.
146     * <p>
147     * TODO: Make sure this value is in sync with what we see on L.
148     */
149    private static final int ON_RESUME_TASKS_DELAY_MSEC = 20;
150
151    /** Timeout for camera open/close operations. */
152    private static final int CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS = 2500;
153
154    /** System Properties switch to enable debugging focus UI. */
155    private static final boolean CAPTURE_DEBUG_UI = DebugPropertyHelper.showCaptureDebugUI();
156
157    private final Object mDimensionLock = new Object();
158
159    /**
160     * Sticky Gcam mode is when this module's sole purpose it to be the Gcam
161     * mode. If true, the device uses {@link PhotoModule} for normal picture
162     * taking.
163     */
164    private final boolean mStickyGcamCamera;
165
166    /**
167     * Lock for race conditions in the SurfaceTextureListener callbacks.
168     */
169    private final Object mSurfaceLock = new Object();
170    /** Controller giving us access to other services. */
171    private final AppController mAppController;
172    /** The applications settings manager. */
173    private final SettingsManager mSettingsManager;
174    /** Application context. */
175    private final Context mContext;
176    private CaptureModuleUI mUI;
177    /** The camera manager used to open cameras. */
178    private OneCameraManager mCameraManager;
179    /** The currently opened camera device, or null if the camera is closed. */
180    private OneCamera mCamera;
181    /** Held when opening or closing the camera. */
182    private final Semaphore mCameraOpenCloseLock = new Semaphore(1);
183    /** The direction the currently opened camera is facing to. */
184    private Facing mCameraFacing = Facing.BACK;
185    /** Whether HDR is currently enabled. */
186    private boolean mHdrEnabled = false;
187
188    /** The texture used to render the preview in. */
189    private SurfaceTexture mPreviewTexture;
190
191    /** State by the module state machine. */
192    private static enum ModuleState {
193        IDLE,
194        WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED,
195        UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE,
196    }
197
198    /** The current state of the module. */
199    private ModuleState mState = ModuleState.IDLE;
200    /** Current orientation of the device. */
201    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
202    /** Current zoom value. */
203    private float mZoomValue = 1f;
204    /** Current duration of capture timer in seconds. */
205    private int mTimerDuration;
206    // TODO: Get image capture intent UI working.
207    private boolean mIsImageCaptureIntent;
208
209    /** True if in AF tap-to-focus sequence. */
210    private boolean mTapToFocusWaitForActiveScan = false;
211    /** Records beginning frame of each AF scan. */
212    private long mAutoFocusScanStartFrame = -1;
213    /** Records beginning time of each AF scan in uptimeMillis. */
214    private long mAutoFocusScanStartTime;
215
216    /** Persistence of Tap to Focus target UI after scan complete. */
217    private static final int FOCUS_HOLD_UI_MILLIS = 0;
218    /** Worst case persistence of TTF target UI. */
219    private static final int FOCUS_UI_TIMEOUT_MILLIS = 2000;
220    /** Results from last tap to focus scan */
221    private boolean mFocusedAtEnd;
222    /** Sensor manager we use to get the heading of the device. */
223    private SensorManager mSensorManager;
224    /** Accelerometer. */
225    private Sensor mAccelerometerSensor;
226    /** Compass. */
227    private Sensor mMagneticSensor;
228
229    /** Accelerometer data. */
230    private final float[] mGData = new float[3];
231    /** Magnetic sensor data. */
232    private final float[] mMData = new float[3];
233    /** Temporary rotation matrix. */
234    private final float[] mR = new float[16];
235    /** Current compass heading. */
236    private int mHeading = -1;
237
238    /** Used to fetch and embed the location into captured images. */
239    private LocationManager mLocationManager;
240    /** Plays sounds for countdown timer. */
241    private SoundPlayer mCountdownSoundPlayer;
242
243    /** Whether the module is paused right now. */
244    private boolean mPaused;
245
246    /** Whether this module was resumed from lockscreen capture intent. */
247    private boolean mIsResumeFromLockScreen = false;
248
249    private final Runnable mResumeTaskRunnable = new Runnable() {
250        @Override
251        public void run() {
252            onResumeTasks();
253        }
254    };
255
256    /** Main thread handler. */
257    private Handler mMainHandler;
258    /** Handler thread for camera-related operations. */
259    private Handler mCameraHandler;
260
261    /** Current display rotation in degrees. */
262    private int mDisplayRotation;
263    /** Current screen width in pixels. */
264    private int mScreenWidth;
265    /** Current screen height in pixels. */
266    private int mScreenHeight;
267    /** Current width of preview frames from camera. */
268    private int mPreviewBufferWidth;
269    /** Current height of preview frames from camera.. */
270    private int mPreviewBufferHeight;
271    /** Area used by preview. */
272    RectF mPreviewArea;
273
274    /** The current preview transformation matrix. */
275    private Matrix mPreviewTranformationMatrix = new Matrix();
276    /** TODO: This is N5 specific. */
277    public static final float FULLSCREEN_ASPECT_RATIO = 16 / 9f;
278
279    /** A directory to store debug information in during development. */
280    private final File mDebugDataDir;
281
282    /** CLEAN UP START */
283    // private boolean mFirstLayout;
284    // private int[] mTargetFPSRanges;
285    // private float mZoomValue;
286    // private int mSensorOrientation;
287    // private int mLensFacing;
288    // private String mFlashMode;
289    /** CLEAN UP END */
290
291    public CaptureModule(AppController appController) {
292        this(appController, false);
293    }
294
295    /** Constructs a new capture module. */
296    public CaptureModule(AppController appController, boolean stickyHdr) {
297        super(appController);
298        mAppController = appController;
299        mContext = mAppController.getAndroidContext();
300        mSettingsManager = mAppController.getSettingsManager();
301        mSettingsManager.addListener(this);
302        mDebugDataDir = mContext.getExternalCacheDir();
303        mStickyGcamCamera = stickyHdr;
304    }
305
306    @Override
307    public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
308        Log.d(TAG, "init");
309        mIsResumeFromLockScreen = isResumeFromLockscreen(activity);
310        mMainHandler = new Handler(activity.getMainLooper());
311        HandlerThread thread = new HandlerThread("CaptureModule.mCameraHandler");
312        thread.start();
313        mCameraHandler = new Handler(thread.getLooper());
314        mCameraManager = mAppController.getCameraManager();
315        mLocationManager = mAppController.getLocationManager();
316        mDisplayRotation = CameraUtil.getDisplayRotation(mContext);
317        mCameraFacing = getFacingFromCameraId(mSettingsManager.getInteger(
318                mAppController.getModuleScope(),
319                Keys.KEY_CAMERA_ID));
320        mUI = new CaptureModuleUI(activity, this, mAppController.getModuleLayoutRoot(),
321                mLayoutListener);
322        mAppController.setPreviewStatusListener(mUI);
323        mPreviewTexture = mAppController.getCameraAppUI().getSurfaceTexture();
324        mSensorManager = (SensorManager) (mContext.getSystemService(Context.SENSOR_SERVICE));
325        mAccelerometerSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
326        mMagneticSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
327        mCountdownSoundPlayer = new SoundPlayer(mContext);
328
329        String action = activity.getIntent().getAction();
330        mIsImageCaptureIntent = (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
331                || CameraActivity.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
332        View cancelButton = activity.findViewById(R.id.shutter_cancel_button);
333        cancelButton.setOnClickListener(new View.OnClickListener() {
334            @Override
335            public void onClick(View view) {
336                cancelCountDown();
337            }
338        });
339    }
340
341    @Override
342    public void onShutterButtonFocus(boolean pressed) {
343        // TODO Auto-generated method stub
344    }
345
346    @Override
347    public void onShutterCoordinate(TouchCoordinate coord) {
348        // TODO Auto-generated method stub
349    }
350
351    @Override
352    public void onShutterButtonClick() {
353        if (mCamera == null) {
354            return;
355        }
356
357        int countDownDuration = mSettingsManager
358                .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);
359        mTimerDuration = countDownDuration;
360        if (countDownDuration > 0) {
361            // Start count down.
362            mAppController.getCameraAppUI().transitionToCancel();
363            mAppController.getCameraAppUI().hideModeOptions();
364            mUI.setCountdownFinishedListener(this);
365            mUI.startCountdown(countDownDuration);
366            // Will take picture later via listener callback.
367        } else {
368            takePictureNow();
369        }
370    }
371
372    private void takePictureNow() {
373        Location location = mLocationManager.getCurrentLocation();
374
375        // Set up the capture session.
376        long sessionTime = System.currentTimeMillis();
377        String title = CameraUtil.createJpegName(sessionTime);
378        CaptureSession session = getServices().getCaptureSessionManager()
379                .createNewSession(title, sessionTime, location);
380
381        // Set up the parameters for this capture.
382        PhotoCaptureParameters params = new PhotoCaptureParameters();
383        params.title = title;
384        params.callback = this;
385        params.orientation = getOrientation();
386        params.flashMode = getFlashModeFromSettings();
387        params.heading = mHeading;
388        params.debugDataFolder = mDebugDataDir;
389        params.location = location;
390        params.zoom = mZoomValue;
391        params.timerSeconds = mTimerDuration > 0 ? (float) mTimerDuration : null;
392
393        mCamera.takePicture(params, session);
394    }
395
396    @Override
397    public void onCountDownFinished() {
398        if (mIsImageCaptureIntent) {
399            mAppController.getCameraAppUI().transitionToIntentReviewLayout();
400        } else {
401            mAppController.getCameraAppUI().transitionToCapture();
402        }
403        mAppController.getCameraAppUI().showModeOptions();
404        if (mPaused) {
405            return;
406        }
407        takePictureNow();
408    }
409
410    @Override
411    public void onRemainingSecondsChanged(int remainingSeconds) {
412        if (remainingSeconds == 1) {
413            mCountdownSoundPlayer.play(R.raw.timer_final_second, 0.6f);
414        } else if (remainingSeconds == 2 || remainingSeconds == 3) {
415            mCountdownSoundPlayer.play(R.raw.timer_increment, 0.6f);
416        }
417    }
418
419    private void cancelCountDown() {
420        if (mUI.isCountingDown()) {
421            // Cancel on-going countdown.
422            mUI.cancelCountDown();
423        }
424        mAppController.getCameraAppUI().showModeOptions();
425        mAppController.getCameraAppUI().transitionToCapture();
426    }
427
428    @Override
429    public void onQuickExpose() {
430        mMainHandler.post(new Runnable() {
431            @Override
432            public void run() {
433                // Starts the short version of the capture animation UI.
434                mAppController.startPreCaptureAnimation(true);
435            }
436        });
437    }
438
439    @Override
440    public void onPreviewAreaChanged(RectF previewArea) {
441        mPreviewArea = previewArea;
442        mUI.onPreviewAreaChanged(previewArea);
443        // mUI.updatePreviewAreaRect(previewArea);
444        mUI.positionProgressOverlay(previewArea);
445    }
446
447    @Override
448    public void onSensorChanged(SensorEvent event) {
449        // This is literally the same as the GCamModule implementation.
450        int type = event.sensor.getType();
451        float[] data;
452        if (type == Sensor.TYPE_ACCELEROMETER) {
453            data = mGData;
454        } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
455            data = mMData;
456        } else {
457            Log.w(TAG, String.format("Unexpected sensor type %s", event.sensor.getName()));
458            return;
459        }
460        for (int i = 0; i < 3; i++) {
461            data[i] = event.values[i];
462        }
463        float[] orientation = new float[3];
464        SensorManager.getRotationMatrix(mR, null, mGData, mMData);
465        SensorManager.getOrientation(mR, orientation);
466        mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
467
468        if (mHeading < 0) {
469            mHeading += 360;
470        }
471    }
472
473    @Override
474    public void onAccuracyChanged(Sensor sensor, int accuracy) {
475        // TODO Auto-generated method stub
476    }
477
478    @Override
479    public void onQueueStatus(boolean full) {
480        // TODO Auto-generated method stub
481    }
482
483    @Override
484    public void onRemoteShutterPress() {
485        // TODO: Check whether shutter is enabled.
486        onShutterButtonClick();
487    }
488
489    @Override
490    public void onSurfaceTextureAvailable(final SurfaceTexture surface, int width, int height) {
491        Log.d(TAG, "onSurfaceTextureAvailable");
492        // Force to re-apply transform matrix here as a workaround for
493        // b/11168275
494        updatePreviewTransform(width, height, true);
495        initSurface(surface);
496    }
497
498    public void initSurface(final SurfaceTexture surface) {
499        mPreviewTexture = surface;
500        closeCamera();
501        openCameraAndStartPreview();
502    }
503
504    @Override
505    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
506        Log.d(TAG, "onSurfaceTextureSizeChanged");
507        resetDefaultBufferSize();
508    }
509
510    @Override
511    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
512        Log.d(TAG, "onSurfaceTextureDestroyed");
513        mPreviewTexture = null;
514        closeCamera();
515        return true;
516    }
517
518    @Override
519    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
520        if (mState == ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE) {
521            Log.d(TAG, "onSurfaceTextureUpdated --> updatePreviewTransform");
522            mState = ModuleState.IDLE;
523            CameraAppUI appUI = mAppController.getCameraAppUI();
524            updatePreviewTransform(appUI.getSurfaceWidth(), appUI.getSurfaceHeight(), true);
525        }
526    }
527
528    @Override
529    public String getModuleStringIdentifier() {
530        return PHOTO_MODULE_STRING_ID;
531    }
532
533    @Override
534    public void resume() {
535        // Add delay on resume from lock screen only, in order to to speed up
536        // the onResume --> onPause --> onResume cycle from lock screen.
537        // Don't do always because letting go of thread can cause delay.
538        if (mIsResumeFromLockScreen) {
539            Log.v(TAG, "Delayng onResumeTasks from lock screen. " + System.currentTimeMillis());
540            // Note: onPauseAfterSuper() will delete this runnable, so we will
541            // at most have 1 copy queued up.
542            mMainHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC);
543        } else {
544            onResumeTasks();
545        }
546    }
547
548    private void onResumeTasks() {
549        Log.d(TAG, "onResumeTasks + " + System.currentTimeMillis());
550        mPaused = false;
551        mAppController.getCameraAppUI().onChangeCamera();
552        mAppController.addPreviewAreaSizeChangedListener(this);
553        resetDefaultBufferSize();
554        getServices().getRemoteShutterListener().onModuleReady(this);
555        // TODO: Check if we can really take a photo right now (memory, camera
556        // state, ... ).
557        mAppController.getCameraAppUI().enableModeOptions();
558        mAppController.setShutterEnabled(true);
559
560        // Get events from the accelerometer and magnetic sensor.
561        if (mAccelerometerSensor != null) {
562            mSensorManager.registerListener(this, mAccelerometerSensor,
563                    SensorManager.SENSOR_DELAY_NORMAL);
564        }
565        if (mMagneticSensor != null) {
566            mSensorManager.registerListener(this, mMagneticSensor,
567                    SensorManager.SENSOR_DELAY_NORMAL);
568        }
569        mHdrEnabled = mStickyGcamCamera || mAppController.getSettingsManager().getInteger(
570                SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS) == 1;
571
572        // This means we are resuming with an existing preview texture. This
573        // means we will never get the onSurfaceTextureAvailable call. So we
574        // have to open the camera and start the preview here.
575        if (mPreviewTexture != null) {
576            initSurface(mPreviewTexture);
577        }
578
579        mCountdownSoundPlayer.loadSound(R.raw.timer_final_second);
580        mCountdownSoundPlayer.loadSound(R.raw.timer_increment);
581    }
582
583    @Override
584    public void pause() {
585        mPaused = true;
586        cancelCountDown();
587        resetTextureBufferSize();
588        closeCamera();
589        mCountdownSoundPlayer.unloadSound(R.raw.timer_final_second);
590        mCountdownSoundPlayer.unloadSound(R.raw.timer_increment);
591        // Remove delayed resume trigger, if it hasn't been executed yet.
592        mMainHandler.removeCallbacksAndMessages(null);
593
594        // Unregister the sensors.
595        if (mAccelerometerSensor != null) {
596            mSensorManager.unregisterListener(this, mAccelerometerSensor);
597        }
598        if (mMagneticSensor != null) {
599            mSensorManager.unregisterListener(this, mMagneticSensor);
600        }
601    }
602
603    @Override
604    public void destroy() {
605        mCountdownSoundPlayer.release();
606        mCameraHandler.getLooper().quitSafely();
607    }
608
609    @Override
610    public void onLayoutOrientationChanged(boolean isLandscape) {
611        Log.d(TAG, "onLayoutOrientationChanged");
612    }
613
614    @Override
615    public void onOrientationChanged(int orientation) {
616        // We keep the last known orientation. So if the user first orient
617        // the camera then point the camera to floor or sky, we still have
618        // the correct orientation.
619        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
620            return;
621        }
622
623        // TODO: Document orientation compute logic and unify them in OrientationManagerImpl.
624        // b/17443789
625        // Flip to counter-clockwise orientation.
626        mOrientation = (360 - orientation) % 360;
627    }
628
629    @Override
630    public void onCameraAvailable(CameraProxy cameraProxy) {
631        // Ignore since we manage the camera ourselves until we remove this.
632    }
633
634    @Override
635    public void hardResetSettings(SettingsManager settingsManager) {
636        if (mStickyGcamCamera) {
637            // Sitcky HDR+ mode should hard reset HDR+ to on, and camera back
638            // facing.
639            settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, true);
640            settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,
641                    getBackFacingCameraId());
642        }
643    }
644
645    @Override
646    public HardwareSpec getHardwareSpec() {
647        return new HardwareSpec() {
648            @Override
649            public boolean isFrontCameraSupported() {
650                return true;
651            }
652
653            @Override
654            public boolean isHdrSupported() {
655                // TODO: Check if the device has HDR and not HDR+.
656                return false;
657            }
658
659            @Override
660            public boolean isHdrPlusSupported() {
661                return GcamHelper.hasGcamCapture();
662            }
663
664            @Override
665            public boolean isFlashSupported() {
666                return true;
667            }
668        };
669    }
670
671    @Override
672    public BottomBarUISpec getBottomBarSpec() {
673        CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
674        bottomBarSpec.enableGridLines = true;
675        bottomBarSpec.enableCamera = true;
676        bottomBarSpec.cameraCallback = getCameraCallback();
677        bottomBarSpec.enableHdr = GcamHelper.hasGcamCapture();
678        bottomBarSpec.hdrCallback = getHdrButtonCallback();
679        bottomBarSpec.enableSelfTimer = true;
680        bottomBarSpec.showSelfTimer = true;
681        if (!mHdrEnabled) {
682            bottomBarSpec.enableFlash = true;
683        }
684        // Added to handle case of CaptureModule being used only for Gcam.
685        if (mStickyGcamCamera) {
686            bottomBarSpec.enableFlash = false;
687        }
688        return bottomBarSpec;
689    }
690
691    @Override
692    public boolean isUsingBottomBar() {
693        return true;
694    }
695
696    @Override
697    public boolean onKeyDown(int keyCode, KeyEvent event) {
698        switch (keyCode) {
699            case KeyEvent.KEYCODE_CAMERA:
700            case KeyEvent.KEYCODE_DPAD_CENTER:
701                if (mUI.isCountingDown()) {
702                    cancelCountDown();
703                } else if (event.getRepeatCount() == 0) {
704                    onShutterButtonClick();
705                }
706                return true;
707            case KeyEvent.KEYCODE_VOLUME_UP:
708            case KeyEvent.KEYCODE_VOLUME_DOWN:
709                // Prevent default.
710                return true;
711        }
712        return false;
713    }
714
715    @Override
716    public boolean onKeyUp(int keyCode, KeyEvent event) {
717        switch (keyCode) {
718            case KeyEvent.KEYCODE_VOLUME_UP:
719            case KeyEvent.KEYCODE_VOLUME_DOWN:
720                onShutterButtonClick();
721                return true;
722        }
723        return false;
724    }
725
726    /**
727     * Focus sequence starts for zone around tap location for single tap.
728     */
729    @Override
730    public void onSingleTapUp(View view, int x, int y) {
731        Log.v(TAG, "onSingleTapUp x=" + x + " y=" + y);
732        // TODO: This should query actual capability.
733        if (mCameraFacing == Facing.FRONT) {
734            return;
735        }
736        triggerFocusAtScreenCoord(x, y);
737    }
738
739    // TODO: Consider refactoring FocusOverlayManager.
740    // Currently AF state transitions are controlled in OneCameraImpl.
741    // PhotoModule uses FocusOverlayManager which uses API1/portability
742    // logic and coordinates.
743    private void triggerFocusAtScreenCoord(int x, int y) {
744        if (mCamera == null) {
745            // If we receive this after the camera is closed, do nothing.
746            return;
747        }
748
749        mTapToFocusWaitForActiveScan = true;
750        // Show UI immediately even though scan has not started yet.
751        float minEdge = Math.min(mPreviewArea.width(), mPreviewArea.height());
752        mUI.setAutoFocusTarget(x, y, false,
753                (int) (Settings3A.getAutoFocusRegionWidth() * mZoomValue * minEdge),
754                (int) (Settings3A.getMeteringRegionWidth() * mZoomValue * minEdge));
755        mUI.showAutoFocusInProgress();
756
757        // Cancel any scheduled auto focus target UI actions.
758        mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
759        // Timeout in case camera fails to stop (unlikely).
760        mMainHandler.postDelayed(new Runnable() {
761            @Override
762            public void run() {
763                mMainHandler.post(mHideAutoFocusTargetRunnable);
764            }
765        }, FOCUS_UI_TIMEOUT_MILLIS);
766
767        // Normalize coordinates to [0,1] per CameraOne API.
768        float points[] = new float[2];
769        points[0] = (x - mPreviewArea.left) / mPreviewArea.width();
770        points[1] = (y - mPreviewArea.top) / mPreviewArea.height();
771
772        // Rotate coordinates to portrait orientation per CameraOne API.
773        Matrix rotationMatrix = new Matrix();
774        rotationMatrix.setRotate(mDisplayRotation, 0.5f, 0.5f);
775        rotationMatrix.mapPoints(points);
776        mCamera.triggerFocusAndMeterAtPoint(points[0], points[1]);
777
778        // Log touch (screen coordinates).
779        if (mZoomValue == 1f) {
780            TouchCoordinate touchCoordinate = new TouchCoordinate(x - mPreviewArea.left,
781                    y - mPreviewArea.top, mPreviewArea.width(), mPreviewArea.height());
782            // TODO: Add to logging: duration, rotation.
783            UsageStatistics.instance().tapToFocus(touchCoordinate, null);
784        }
785    }
786
787    /**
788     * Show AF target in center of preview.
789     */
790    private void setAutoFocusTargetPassive() {
791        float minEdge = Math.min(mPreviewArea.width(), mPreviewArea.height());
792        mUI.setAutoFocusTarget((int) mPreviewArea.centerX(), (int) mPreviewArea.centerY(),
793                true,
794                (int) (Settings3A.getAutoFocusRegionWidth() * mZoomValue * minEdge),
795                (int) (Settings3A.getMeteringRegionWidth() * mZoomValue * minEdge));
796        mUI.showAutoFocusInProgress();
797    }
798
799    /**
800     * Update UI based on AF state changes.
801     */
802    @Override
803    public void onFocusStatusUpdate(final AutoFocusState state, long frameNumber) {
804        Log.v(TAG, "AF status is state:" + state);
805
806        switch (state) {
807            case PASSIVE_SCAN:
808                mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
809                mMainHandler.post(new Runnable() {
810                    @Override
811                    public void run() {
812                        setAutoFocusTargetPassive();
813                    }
814                });
815                break;
816            case ACTIVE_SCAN:
817                mTapToFocusWaitForActiveScan = false;
818                break;
819            case PASSIVE_FOCUSED:
820            case PASSIVE_UNFOCUSED:
821                mMainHandler.post(new Runnable() {
822                    @Override
823                    public void run() {
824                        mUI.setPassiveFocusSuccess(state == AutoFocusState.PASSIVE_FOCUSED);
825                    }
826                });
827                break;
828            case ACTIVE_FOCUSED:
829            case ACTIVE_UNFOCUSED:
830                if (!mTapToFocusWaitForActiveScan) {
831                    mFocusedAtEnd = state != AutoFocusState.ACTIVE_UNFOCUSED;
832                    mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
833                    mMainHandler.post(mHideAutoFocusTargetRunnable);
834                }
835                break;
836        }
837
838        if (CAPTURE_DEBUG_UI) {
839            measureAutoFocusScans(state, frameNumber);
840        }
841    }
842
843    private void measureAutoFocusScans(final AutoFocusState state, long frameNumber) {
844        // Log AF scan lengths.
845        boolean passive = false;
846        switch (state) {
847            case PASSIVE_SCAN:
848            case ACTIVE_SCAN:
849                if (mAutoFocusScanStartFrame == -1) {
850                    mAutoFocusScanStartFrame = frameNumber;
851                    mAutoFocusScanStartTime = SystemClock.uptimeMillis();
852                }
853                break;
854            case PASSIVE_FOCUSED:
855            case PASSIVE_UNFOCUSED:
856                passive = true;
857            case ACTIVE_FOCUSED:
858            case ACTIVE_UNFOCUSED:
859                if (mAutoFocusScanStartFrame != -1) {
860                    long frames = frameNumber - mAutoFocusScanStartFrame;
861                    long dt = SystemClock.uptimeMillis() - mAutoFocusScanStartTime;
862                    int fps = Math.round(frames * 1000f / dt);
863                    String report = String.format("%s scan: fps=%d frames=%d",
864                            passive ? "CAF" : "AF", fps, frames);
865                    Log.v(TAG, report);
866                    mUI.showDebugMessage(String.format("%d / %d", frames, fps));
867                    mAutoFocusScanStartFrame = -1;
868                }
869                break;
870        }
871    }
872
873    @Override
874    public void onReadyStateChanged(boolean readyForCapture) {
875        if (readyForCapture) {
876            mAppController.getCameraAppUI().enableModeOptions();
877        }
878        mAppController.setShutterEnabled(readyForCapture);
879    }
880
881    @Override
882    public String getPeekAccessibilityString() {
883        return mAppController.getAndroidContext()
884                .getResources().getString(R.string.photo_accessibility_peek);
885    }
886
887    @Override
888    public void onThumbnailResult(Bitmap bitmap) {
889        // TODO
890    }
891
892    @Override
893    public void onPictureTaken(CaptureSession session) {
894        mAppController.getCameraAppUI().enableModeOptions();
895    }
896
897    @Override
898    public void onPictureSaved(Uri uri) {
899        mAppController.notifyNewMedia(uri);
900    }
901
902    @Override
903    public void onTakePictureProgress(float progress) {
904        mUI.setPictureTakingProgress((int) (progress * 100));
905    }
906
907    @Override
908    public void onPictureTakenFailed() {
909    }
910
911    @Override
912    public void onSettingChanged(SettingsManager settingsManager, String key) {
913        // TODO Auto-generated method stub
914    }
915
916    /**
917     * Updates the preview transform matrix to adapt to the current preview
918     * width, height, and orientation.
919     */
920    public void updatePreviewTransform() {
921        int width;
922        int height;
923        synchronized (mDimensionLock) {
924            width = mScreenWidth;
925            height = mScreenHeight;
926        }
927        updatePreviewTransform(width, height);
928    }
929
930    /**
931     * Set zoom value.
932     *
933     * @param zoom Zoom value, must be between 1.0 and mCamera.getMaxZoom().
934     */
935    public void setZoom(float zoom) {
936        mZoomValue = zoom;
937        if (mCamera != null) {
938            mCamera.setZoom(zoom);
939        }
940    }
941
942    /**
943     * TODO: Remove this method once we are in pure CaptureModule land.
944     */
945    private String getBackFacingCameraId() {
946        if (!(mCameraManager instanceof OneCameraManagerImpl)) {
947            throw new IllegalStateException("This should never be called with Camera API V1");
948        }
949        OneCameraManagerImpl manager = (OneCameraManagerImpl) mCameraManager;
950        return manager.getFirstBackCameraId();
951    }
952
953    /**
954     * @return Depending on whether we're in sticky-HDR mode or not, return the
955     *         proper callback to be used for when the HDR/HDR+ button is
956     *         pressed.
957     */
958    private ButtonManager.ButtonCallback getHdrButtonCallback() {
959        if (mStickyGcamCamera) {
960            return new ButtonManager.ButtonCallback() {
961                @Override
962                public void onStateChanged(int state) {
963                    if (mPaused) {
964                        return;
965                    }
966                    if (state == ButtonManager.ON) {
967                        throw new IllegalStateException(
968                                "Can't leave hdr plus mode if switching to hdr plus mode.");
969                    }
970                    SettingsManager settingsManager = mAppController.getSettingsManager();
971                    settingsManager.set(mAppController.getModuleScope(),
972                            Keys.KEY_REQUEST_RETURN_HDR_PLUS, false);
973                    switchToRegularCapture();
974                }
975            };
976        } else {
977            return new ButtonManager.ButtonCallback() {
978                @Override
979                public void onStateChanged(int hdrEnabled) {
980                    if (mPaused) {
981                        return;
982                    }
983                    Log.d(TAG, "HDR enabled =" + hdrEnabled);
984                    mHdrEnabled = hdrEnabled == 1;
985                    switchCamera();
986                }
987            };
988        }
989    }
990
991    /**
992     * @return Depending on whether we're in sticky-HDR mode or not, this
993     *         returns the proper callback to be used for when the camera
994     *         (front/back switch) button is pressed.
995     */
996    private ButtonManager.ButtonCallback getCameraCallback() {
997        if (mStickyGcamCamera) {
998            return new ButtonManager.ButtonCallback() {
999                @Override
1000                public void onStateChanged(int state) {
1001                    if (mPaused) {
1002                        return;
1003                    }
1004
1005                    // At the time this callback is fired, the camera id setting
1006                    // has changed to the desired camera.
1007                    SettingsManager settingsManager = mAppController.getSettingsManager();
1008                    if (Keys.isCameraBackFacing(settingsManager,
1009                            mAppController.getModuleScope())) {
1010                        throw new IllegalStateException(
1011                                "Hdr plus should never be switching from front facing camera.");
1012                    }
1013
1014                    // Switch to photo mode, but request a return to hdr plus on
1015                    // switching to back camera again.
1016                    settingsManager.set(mAppController.getModuleScope(),
1017                            Keys.KEY_REQUEST_RETURN_HDR_PLUS, true);
1018                    switchToRegularCapture();
1019                }
1020            };
1021        } else {
1022            return new ButtonManager.ButtonCallback() {
1023                @Override
1024                public void onStateChanged(int cameraId) {
1025                    if (mPaused) {
1026                        return;
1027                    }
1028
1029                    // At the time this callback is fired, the camera id
1030                    // has be set to the desired camera.
1031                    mSettingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,
1032                            cameraId);
1033
1034                    Log.d(TAG, "Start to switch camera. cameraId=" + cameraId);
1035                    mCameraFacing = getFacingFromCameraId(cameraId);
1036                    switchCamera();
1037                }
1038            };
1039        }
1040    }
1041
1042    /**
1043     * Switches to PhotoModule to do regular photo captures.
1044     * <p>
1045     * TODO: Remove this once we use CaptureModule for photo taking.
1046     */
1047    private void switchToRegularCapture() {
1048        // Turn off HDR+ before switching back to normal photo mode.
1049        SettingsManager settingsManager = mAppController.getSettingsManager();
1050        settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false);
1051
1052        // Disable this button to prevent callbacks from this module from firing
1053        // while we are transitioning modules.
1054        ButtonManager buttonManager = mAppController.getButtonManager();
1055        buttonManager.disableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
1056        mAppController.getCameraAppUI().freezeScreenUntilPreviewReady();
1057        mAppController.onModeSelected(mContext.getResources().getInteger(
1058                R.integer.camera_mode_photo));
1059        buttonManager.enableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
1060    }
1061
1062    /**
1063     * Called when the preview started. Informs the app controller and queues a
1064     * transform update when the next preview frame arrives.
1065     */
1066    private void onPreviewStarted() {
1067        if (mState == ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED) {
1068            mState = ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE;
1069        }
1070        mAppController.onPreviewStarted();
1071    }
1072
1073    /**
1074     * Update the preview transform based on the new dimensions. Will not force
1075     * an update, if it's not necessary.
1076     */
1077    private void updatePreviewTransform(int incomingWidth, int incomingHeight) {
1078        updatePreviewTransform(incomingWidth, incomingHeight, false);
1079    }
1080
1081    /***
1082     * Update the preview transform based on the new dimensions. TODO: Make work
1083     * with all: aspect ratios/resolutions x screens/cameras.
1084     */
1085    private void updatePreviewTransform(int incomingWidth, int incomingHeight,
1086            boolean forceUpdate) {
1087        Log.d(TAG, "updatePreviewTransform: " + incomingWidth + " x " + incomingHeight);
1088
1089        synchronized (mDimensionLock) {
1090            int incomingRotation = CameraUtil
1091                    .getDisplayRotation(mContext);
1092            // Check for an actual change:
1093            if (mScreenHeight == incomingHeight && mScreenWidth == incomingWidth &&
1094                    incomingRotation == mDisplayRotation && !forceUpdate) {
1095                return;
1096            }
1097            // Update display rotation and dimensions
1098            mDisplayRotation = incomingRotation;
1099            mScreenWidth = incomingWidth;
1100            mScreenHeight = incomingHeight;
1101            updatePreviewBufferDimension();
1102
1103            mPreviewTranformationMatrix = mAppController.getCameraAppUI().getPreviewTransform(
1104                    mPreviewTranformationMatrix);
1105            int width = mScreenWidth;
1106            int height = mScreenHeight;
1107
1108            // Assumptions:
1109            // - Aspect ratio for the sensor buffers is in landscape
1110            // orientation,
1111            // - Dimensions of buffers received are rotated to the natural
1112            // device orientation.
1113            // - The contents of each buffer are rotated by the inverse of
1114            // the display rotation.
1115            // - Surface scales the buffer to fit the current view bounds.
1116
1117            // Get natural orientation and buffer dimensions
1118            int naturalOrientation = CaptureModuleUtil
1119                    .getDeviceNaturalOrientation(mContext);
1120            int effectiveWidth = mPreviewBufferWidth;
1121            int effectiveHeight = mPreviewBufferHeight;
1122
1123            if (DEBUG) {
1124                Log.v(TAG, "Rotation: " + mDisplayRotation);
1125                Log.v(TAG, "Screen Width: " + mScreenWidth);
1126                Log.v(TAG, "Screen Height: " + mScreenHeight);
1127                Log.v(TAG, "Buffer width: " + mPreviewBufferWidth);
1128                Log.v(TAG, "Buffer height: " + mPreviewBufferHeight);
1129                Log.v(TAG, "Natural orientation: " + naturalOrientation);
1130            }
1131
1132            // If natural orientation is portrait, rotate the buffer
1133            // dimensions
1134            if (naturalOrientation == Configuration.ORIENTATION_PORTRAIT) {
1135                int temp = effectiveWidth;
1136                effectiveWidth = effectiveHeight;
1137                effectiveHeight = temp;
1138            }
1139
1140            // Find and center view rect and buffer rect
1141            RectF viewRect = new RectF(0, 0, width, height);
1142            RectF bufRect = new RectF(0, 0, effectiveWidth, effectiveHeight);
1143            float centerX = viewRect.centerX();
1144            float centerY = viewRect.centerY();
1145            bufRect.offset(centerX - bufRect.centerX(), centerY - bufRect.centerY());
1146
1147            // Undo ScaleToFit.FILL done by the surface
1148            mPreviewTranformationMatrix.setRectToRect(viewRect, bufRect, Matrix.ScaleToFit.FILL);
1149
1150            // Rotate buffer contents to proper orientation
1151            mPreviewTranformationMatrix.postRotate(getPreviewOrientation(mDisplayRotation),
1152                    centerX, centerY);
1153
1154            // TODO: This is probably only working for the N5. Need to test
1155            // on a device like N10 with different sensor orientation.
1156            if ((mDisplayRotation % 180) == 90) {
1157                int temp = effectiveWidth;
1158                effectiveWidth = effectiveHeight;
1159                effectiveHeight = temp;
1160            }
1161
1162            // Scale to fit view, cropping the longest dimension
1163            float scale =
1164                    Math.min(width / (float) effectiveWidth, height
1165                            / (float) effectiveHeight);
1166            mPreviewTranformationMatrix.postScale(scale, scale, centerX, centerY);
1167
1168            // TODO: Take these quantities from mPreviewArea.
1169            float previewWidth = effectiveWidth * scale;
1170            float previewHeight = effectiveHeight * scale;
1171            float previewCenterX = previewWidth / 2;
1172            float previewCenterY = previewHeight / 2;
1173            mPreviewTranformationMatrix.postTranslate(previewCenterX - centerX, previewCenterY
1174                    - centerY);
1175
1176            mAppController.updatePreviewTransform(mPreviewTranformationMatrix);
1177            mAppController.getCameraAppUI().hideLetterboxing();
1178            // if (mGcamProxy != null) {
1179            // mGcamProxy.postSetAspectRatio(mFinalAspectRatio);
1180            // }
1181            // mUI.updatePreviewAreaRect(new RectF(0, 0, previewWidth,
1182            // previewHeight));
1183
1184            // TODO: Add face detection.
1185            // Characteristics info =
1186            // mapp.getCameraProvider().getCharacteristics(0);
1187            // mUI.setupFaceDetection(CameraUtil.getDisplayOrientation(incomingRotation,
1188            // info), false);
1189            // updateCamera2FaceBoundTransform(new
1190            // RectF(mEffectiveCropRegion),
1191            // new RectF(0, 0, mBufferWidth, mBufferHeight),
1192            // new RectF(0, 0, previewWidth, previewHeight), getRotation());
1193        }
1194    }
1195
1196    /**
1197     * Based on the current picture size, selects the best preview dimension and
1198     * stores it in {@link #mPreviewBufferWidth} and
1199     * {@link #mPreviewBufferHeight}.
1200     */
1201    private void updatePreviewBufferDimension() {
1202        if (mCamera == null) {
1203            return;
1204        }
1205
1206        Size pictureSize = getPictureSizeFromSettings();
1207        Size previewBufferSize = mCamera.pickPreviewSize(pictureSize, mContext);
1208        mPreviewBufferWidth = previewBufferSize.getWidth();
1209        mPreviewBufferHeight = previewBufferSize.getHeight();
1210    }
1211
1212    /**
1213     * Resets the default buffer size to the initially calculated size.
1214     */
1215    private void resetDefaultBufferSize() {
1216        synchronized (mSurfaceLock) {
1217            if (mPreviewTexture != null) {
1218                mPreviewTexture.setDefaultBufferSize(mPreviewBufferWidth, mPreviewBufferHeight);
1219            }
1220        }
1221    }
1222
1223    /**
1224     * Open camera and start the preview.
1225     */
1226    private void openCameraAndStartPreview() {
1227        // Only enable HDR on the back camera
1228        boolean useHdr = mHdrEnabled && mCameraFacing == Facing.BACK;
1229
1230        try {
1231            // TODO Given the current design, we cannot guarantee that one of
1232            // CaptureReadyCallback.onSetupFailed or onReadyForCapture will
1233            // be called (see below), so it's possible that
1234            // mCameraOpenCloseLock.release() is never called under extremely
1235            // rare cases.  If we leak the lock, this timeout ensures that we at
1236            // least crash so we don't deadlock the app.
1237            if (!mCameraOpenCloseLock.tryAcquire(CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
1238                throw new RuntimeException("Time out waiting to acquire camera-open lock.");
1239            }
1240        } catch (InterruptedException e) {
1241            throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e);
1242        }
1243        mCameraManager.open(mCameraFacing, useHdr, getPictureSizeFromSettings(),
1244                new OpenCallback() {
1245                    @Override
1246                    public void onFailure() {
1247                        Log.e(TAG, "Could not open camera.");
1248                        mCamera = null;
1249                        mCameraOpenCloseLock.release();
1250                        mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
1251                    }
1252
1253                    @Override
1254                    public void onCameraClosed() {
1255                        mCamera = null;
1256                        mCameraOpenCloseLock.release();
1257                    }
1258
1259                    @Override
1260                    public void onCameraOpened(final OneCamera camera) {
1261                        Log.d(TAG, "onCameraOpened: " + camera);
1262                        mCamera = camera;
1263                        updatePreviewBufferDimension();
1264
1265                        // If the surface texture is not destroyed, it may have
1266                        // the last frame lingering. We need to hold off setting
1267                        // transform until preview is started.
1268                        resetDefaultBufferSize();
1269                        mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED;
1270                        Log.d(TAG, "starting preview ...");
1271
1272                        // TODO: Consider rolling these two calls into one.
1273                        camera.startPreview(new Surface(mPreviewTexture),
1274                                new CaptureReadyCallback() {
1275                                    @Override
1276                                    public void onSetupFailed() {
1277                                        // We must release this lock here, before posting
1278                                        // to the main handler since we may be blocked
1279                                        // in pause(), getting ready to close the camera.
1280                                        mCameraOpenCloseLock.release();
1281                                        Log.e(TAG, "Could not set up preview.");
1282                                        mMainHandler.post(new Runnable() {
1283                                           @Override
1284                                           public void run() {
1285                                               if (mCamera == null) {
1286                                                   Log.d(TAG, "Camera closed, aborting.");
1287                                                   return;
1288                                               }
1289                                               mCamera.close(null);
1290                                               mCamera = null;
1291                                               // TODO: Show an error message and exit.
1292                                           }
1293                                        });
1294                                    }
1295
1296                                    @Override
1297                                    public void onReadyForCapture() {
1298                                        // We must release this lock here, before posting
1299                                        // to the main handler since we may be blocked
1300                                        // in pause(), getting ready to close the camera.
1301                                        mCameraOpenCloseLock.release();
1302                                        mMainHandler.post(new Runnable() {
1303                                           @Override
1304                                           public void run() {
1305                                               Log.d(TAG, "Ready for capture.");
1306                                               if (mCamera == null) {
1307                                                   Log.d(TAG, "Camera closed, aborting.");
1308                                                   return;
1309                                               }
1310                                               onPreviewStarted();
1311                                               // Enable zooming after preview has
1312                                               // started.
1313                                               mUI.initializeZoom(mCamera.getMaxZoom());
1314                                               mCamera.setFocusStateListener(CaptureModule.this);
1315                                               mCamera.setReadyStateChangedListener(CaptureModule.this);
1316                                           }
1317                                        });
1318                                    }
1319                                });
1320                    }
1321                }, mCameraHandler);
1322    }
1323
1324    private void closeCamera() {
1325        try {
1326            mCameraOpenCloseLock.acquire();
1327        } catch(InterruptedException e) {
1328            throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e);
1329        }
1330        try {
1331            if (mCamera != null) {
1332                mCamera.setFocusStateListener(null);
1333                mCamera.close(null);
1334                mCamera = null;
1335            }
1336        } finally {
1337            mCameraOpenCloseLock.release();
1338        }
1339    }
1340
1341    private int getOrientation() {
1342        if (mAppController.isAutoRotateScreen()) {
1343            return mDisplayRotation;
1344        } else {
1345            return mOrientation;
1346        }
1347    }
1348
1349    /**
1350     * @return Whether we are resuming from within the lockscreen.
1351     */
1352    private static boolean isResumeFromLockscreen(Activity activity) {
1353        String action = activity.getIntent().getAction();
1354        return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action)
1355        || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action));
1356    }
1357
1358    /**
1359     * Re-initialize the camera if e.g. the HDR mode or facing property changed.
1360     */
1361    private void switchCamera() {
1362        if (mPaused) {
1363            return;
1364        }
1365        cancelCountDown();
1366        mAppController.freezeScreenUntilPreviewReady();
1367        initSurface(mPreviewTexture);
1368
1369        // TODO: Un-comment once we have focus back.
1370        // if (mFocusManager != null) {
1371        // mFocusManager.removeMessages();
1372        // }
1373        // mFocusManager.setMirror(mMirror);
1374    }
1375
1376    private Size getPictureSizeFromSettings() {
1377        String pictureSizeKey = mCameraFacing == Facing.FRONT ? Keys.KEY_PICTURE_SIZE_FRONT
1378                : Keys.KEY_PICTURE_SIZE_BACK;
1379        return mSettingsManager.getSize(SettingsManager.SCOPE_GLOBAL, pictureSizeKey);
1380    }
1381
1382    private int getPreviewOrientation(int deviceOrientationDegrees) {
1383        // Important: Camera2 buffers are already rotated to the natural
1384        // orientation of the device (at least for the back-camera).
1385
1386        // TODO: Remove this hack for the front camera as soon as b/16637957 is
1387        // fixed.
1388        if (mCameraFacing == Facing.FRONT) {
1389            deviceOrientationDegrees += 180;
1390        }
1391        return (360 - deviceOrientationDegrees) % 360;
1392    }
1393
1394    /**
1395     * Returns which way around the camera is facing, based on it's ID.
1396     * <p>
1397     * TODO: This needs to change so that we store the direction directly in the
1398     * settings, rather than a Camera ID.
1399     */
1400    private static Facing getFacingFromCameraId(int cameraId) {
1401        return cameraId == 1 ? Facing.FRONT : Facing.BACK;
1402    }
1403
1404    private void resetTextureBufferSize() {
1405        // Reset the default buffer sizes on the shared SurfaceTexture
1406        // so they are not scaled for gcam.
1407        //
1408        // According to the documentation for
1409        // SurfaceTexture.setDefaultBufferSize,
1410        // photo and video based image producers (presumably only Camera 1 api),
1411        // override this buffer size. Any module that uses egl to render to a
1412        // SurfaceTexture must have these buffer sizes reset manually. Otherwise
1413        // the SurfaceTexture cannot be transformed by matrix set on the
1414        // TextureView.
1415        if (mPreviewTexture != null) {
1416            mPreviewTexture.setDefaultBufferSize(mAppController.getCameraAppUI().getSurfaceWidth(),
1417                    mAppController.getCameraAppUI().getSurfaceHeight());
1418        }
1419    }
1420
1421    /**
1422     * @return The currently set Flash settings. Defaults to AUTO if the setting
1423     *         could not be parsed.
1424     */
1425    private Flash getFlashModeFromSettings() {
1426        String flashSetting = mSettingsManager.getString(mAppController.getCameraScope(),
1427                Keys.KEY_FLASH_MODE);
1428        try {
1429            return Flash.valueOf(flashSetting.toUpperCase());
1430        } catch (IllegalArgumentException ex) {
1431            Log.w(TAG, "Could not parse Flash Setting. Defaulting to AUTO.");
1432            return Flash.AUTO;
1433        }
1434    }
1435}
1436