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