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