CaptureModule.java revision dc0369f37b07adb4355113540af3c1f7c82a404e
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 onQuickExpose() {
412        mMainHandler.post(new Runnable() {
413            @Override
414            public void run() {
415                // Starts the short version of the capture animation UI.
416                mAppController.startPreCaptureAnimation(true);
417            }
418        });
419    }
420
421    @Override
422    public void onPreviewAreaChanged(RectF previewArea) {
423        mPreviewArea = previewArea;
424        mUI.onPreviewAreaChanged(previewArea);
425        // mUI.updatePreviewAreaRect(previewArea);
426        mUI.positionProgressOverlay(previewArea);
427    }
428
429    @Override
430    public void onSensorChanged(SensorEvent event) {
431        // This is literally the same as the GCamModule implementation.
432        int type = event.sensor.getType();
433        float[] data;
434        if (type == Sensor.TYPE_ACCELEROMETER) {
435            data = mGData;
436        } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
437            data = mMData;
438        } else {
439            Log.w(TAG, String.format("Unexpected sensor type %s", event.sensor.getName()));
440            return;
441        }
442        for (int i = 0; i < 3; i++) {
443            data[i] = event.values[i];
444        }
445        float[] orientation = new float[3];
446        SensorManager.getRotationMatrix(mR, null, mGData, mMData);
447        SensorManager.getOrientation(mR, orientation);
448        mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
449
450        if (mHeading < 0) {
451            mHeading += 360;
452        }
453    }
454
455    @Override
456    public void onAccuracyChanged(Sensor sensor, int accuracy) {
457        // TODO Auto-generated method stub
458    }
459
460    @Override
461    public void onQueueStatus(boolean full) {
462        // TODO Auto-generated method stub
463    }
464
465    @Override
466    public void onRemoteShutterPress() {
467        // TODO: Check whether shutter is enabled.
468        onShutterButtonClick();
469    }
470
471    @Override
472    public void onSurfaceTextureAvailable(final SurfaceTexture surface, int width, int height) {
473        Log.d(TAG, "onSurfaceTextureAvailable");
474        // Force to re-apply transform matrix here as a workaround for
475        // b/11168275
476        updatePreviewTransform(width, height, true);
477        initSurface(surface);
478    }
479
480    public void initSurface(final SurfaceTexture surface) {
481        mPreviewTexture = surface;
482        closeCamera();
483        openCameraAndStartPreview();
484    }
485
486    @Override
487    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
488        Log.d(TAG, "onSurfaceTextureSizeChanged");
489        resetDefaultBufferSize();
490    }
491
492    @Override
493    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
494        Log.d(TAG, "onSurfaceTextureDestroyed");
495        mPreviewTexture = null;
496        closeCamera();
497        return true;
498    }
499
500    @Override
501    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
502        if (mState == ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE) {
503            Log.d(TAG, "onSurfaceTextureUpdated --> updatePreviewTransform");
504            mState = ModuleState.IDLE;
505            CameraAppUI appUI = mAppController.getCameraAppUI();
506            updatePreviewTransform(appUI.getSurfaceWidth(), appUI.getSurfaceHeight(), true);
507        }
508    }
509
510    @Override
511    public String getModuleStringIdentifier() {
512        return PHOTO_MODULE_STRING_ID;
513    }
514
515    @Override
516    public void resume() {
517        // Add delay on resume from lock screen only, in order to to speed up
518        // the onResume --> onPause --> onResume cycle from lock screen.
519        // Don't do always because letting go of thread can cause delay.
520        if (mIsResumeFromLockScreen) {
521            Log.v(TAG, "Delayng onResumeTasks from lock screen. " + System.currentTimeMillis());
522            // Note: onPauseAfterSuper() will delete this runnable, so we will
523            // at most have 1 copy queued up.
524            mMainHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC);
525        } else {
526            onResumeTasks();
527        }
528    }
529
530    private void onResumeTasks() {
531        Log.d(TAG, "onResumeTasks + " + System.currentTimeMillis());
532        mPaused = false;
533        mAppController.getCameraAppUI().onChangeCamera();
534        mAppController.addPreviewAreaSizeChangedListener(this);
535        resetDefaultBufferSize();
536        getServices().getRemoteShutterListener().onModuleReady(this);
537        // TODO: Check if we can really take a photo right now (memory, camera
538        // state, ... ).
539        mAppController.getCameraAppUI().enableModeOptions();
540        mAppController.setShutterEnabled(true);
541
542        // TODO: Remove once Gcam is properly tuned on Shamu and ready for
543        // quality feedback.
544        if (mStickyGcamCamera && ApiHelper.IS_NEXUS_6) {
545            Toast.makeText(mContext,
546                    "Shamu HDR+ still in tuning, don't file image quality issues yet",
547                    Toast.LENGTH_SHORT).show();
548        }
549        // Get events from the accelerometer and magnetic sensor.
550        if (mAccelerometerSensor != null) {
551            mSensorManager.registerListener(this, mAccelerometerSensor,
552                    SensorManager.SENSOR_DELAY_NORMAL);
553        }
554        if (mMagneticSensor != null) {
555            mSensorManager.registerListener(this, mMagneticSensor,
556                    SensorManager.SENSOR_DELAY_NORMAL);
557        }
558        mHdrEnabled = mStickyGcamCamera || mAppController.getSettingsManager().getInteger(
559                SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS) == 1;
560
561        // This means we are resuming with an existing preview texture. This
562        // means we will never get the onSurfaceTextureAvailable call. So we
563        // have to open the camera and start the preview here.
564        if (mPreviewTexture != null) {
565            initSurface(mPreviewTexture);
566        }
567
568        mCountdownSoundPlayer.loadSound(R.raw.beep_once);
569        mCountdownSoundPlayer.loadSound(R.raw.beep_twice);
570    }
571
572    @Override
573    public void pause() {
574        mPaused = true;
575        cancelCountDown();
576        resetTextureBufferSize();
577        closeCamera();
578        mCountdownSoundPlayer.unloadSound(R.raw.beep_once);
579        mCountdownSoundPlayer.unloadSound(R.raw.beep_twice);
580        // Remove delayed resume trigger, if it hasn't been executed yet.
581        mMainHandler.removeCallbacksAndMessages(null);
582
583        // Unregister the sensors.
584        if (mAccelerometerSensor != null) {
585            mSensorManager.unregisterListener(this, mAccelerometerSensor);
586        }
587        if (mMagneticSensor != null) {
588            mSensorManager.unregisterListener(this, mMagneticSensor);
589        }
590    }
591
592    @Override
593    public void destroy() {
594        mCountdownSoundPlayer.release();
595    }
596
597    @Override
598    public void onLayoutOrientationChanged(boolean isLandscape) {
599        Log.d(TAG, "onLayoutOrientationChanged");
600    }
601
602    @Override
603    public void onOrientationChanged(int orientation) {
604        // We keep the last known orientation. So if the user first orient
605        // the camera then point the camera to floor or sky, we still have
606        // the correct orientation.
607        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
608            return;
609        }
610        mOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
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    }
772
773    /**
774     * Update UI based on AF state changes.
775     */
776    @Override
777    public void onFocusStatusUpdate(final AutoFocusState state) {
778        Log.v(TAG, "AF status is state:" + state);
779
780        switch (state) {
781            case PASSIVE_SCAN:
782                mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
783                mMainHandler.post(new Runnable() {
784                    @Override
785                    public void run() {
786                        setAutoFocusTargetPassive();
787                        mUI.showAutoFocusInProgress();
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                        setAutoFocusTargetPassive();
800                        mMainHandler.post(mHideAutoFocusTargetRunnable);
801                    }
802                });
803                break;
804            case ACTIVE_FOCUSED:
805            case ACTIVE_UNFOCUSED:
806                if (!mTapToFocusWaitForActiveScan) {
807                    mFocusedAtEnd = state != AutoFocusState.ACTIVE_UNFOCUSED;
808                    mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
809                    mMainHandler.post(mHideAutoFocusTargetRunnable);
810                }
811                break;
812        }
813    }
814
815    @Override
816    public void onReadyStateChanged(boolean readyForCapture) {
817        if (readyForCapture) {
818            mAppController.getCameraAppUI().enableModeOptions();
819        }
820        mAppController.setShutterEnabled(readyForCapture);
821    }
822
823    @Override
824    public String getPeekAccessibilityString() {
825        return mAppController.getAndroidContext()
826                .getResources().getString(R.string.photo_accessibility_peek);
827    }
828
829    @Override
830    public void onThumbnailResult(Bitmap bitmap) {
831        // TODO
832    }
833
834    @Override
835    public void onPictureTaken(CaptureSession session) {
836        mAppController.getCameraAppUI().enableModeOptions();
837        mAppController.getCameraAppUI().setShutterButtonEnabled(true);
838    }
839
840    @Override
841    public void onPictureSaved(Uri uri) {
842        mAppController.notifyNewMedia(uri);
843    }
844
845    @Override
846    public void onTakePictureProgress(float progress) {
847        mUI.setPictureTakingProgress((int) (progress * 100));
848    }
849
850    @Override
851    public void onPictureTakenFailed() {
852    }
853
854    @Override
855    public void onSettingChanged(SettingsManager settingsManager, String key) {
856        // TODO Auto-generated method stub
857    }
858
859    /**
860     * Updates the preview transform matrix to adapt to the current preview
861     * width, height, and orientation.
862     */
863    public void updatePreviewTransform() {
864        int width;
865        int height;
866        synchronized (mDimensionLock) {
867            width = mScreenWidth;
868            height = mScreenHeight;
869        }
870        updatePreviewTransform(width, height);
871    }
872
873    /**
874     * Set zoom value.
875     *
876     * @param zoom Zoom value, must be between 1.0 and mCamera.getMaxZoom().
877     */
878    public void setZoom(float zoom) {
879        mZoomValue = zoom;
880        if (mCamera != null) {
881            mCamera.setZoom(zoom);
882        }
883    }
884
885    /**
886     * TODO: Remove this method once we are in pure CaptureModule land.
887     */
888    private String getBackFacingCameraId() {
889        if (!(mCameraManager instanceof OneCameraManagerImpl)) {
890            throw new IllegalStateException("This should never be called with Camera API V1");
891        }
892        OneCameraManagerImpl manager = (OneCameraManagerImpl) mCameraManager;
893        return manager.getFirstBackCameraId();
894    }
895
896    /**
897     * @return Depending on whether we're in sticky-HDR mode or not, return the
898     *         proper callback to be used for when the HDR/HDR+ button is
899     *         pressed.
900     */
901    private ButtonManager.ButtonCallback getHdrButtonCallback() {
902        if (mStickyGcamCamera) {
903            return new ButtonManager.ButtonCallback() {
904                @Override
905                public void onStateChanged(int state) {
906                    if (mPaused) {
907                        return;
908                    }
909                    if (state == ButtonManager.ON) {
910                        throw new IllegalStateException(
911                                "Can't leave hdr plus mode if switching to hdr plus mode.");
912                    }
913                    SettingsManager settingsManager = mAppController.getSettingsManager();
914                    settingsManager.set(mAppController.getModuleScope(),
915                            Keys.KEY_REQUEST_RETURN_HDR_PLUS, false);
916                    switchToRegularCapture();
917                }
918            };
919        } else {
920            return new ButtonManager.ButtonCallback() {
921                @Override
922                public void onStateChanged(int hdrEnabled) {
923                    if (mPaused) {
924                        return;
925                    }
926                    Log.d(TAG, "HDR enabled =" + hdrEnabled);
927                    mHdrEnabled = hdrEnabled == 1;
928                    switchCamera();
929                }
930            };
931        }
932    }
933
934    /**
935     * @return Depending on whether we're in sticky-HDR mode or not, this
936     *         returns the proper callback to be used for when the camera
937     *         (front/back switch) button is pressed.
938     */
939    private ButtonManager.ButtonCallback getCameraCallback() {
940        if (mStickyGcamCamera) {
941            return new ButtonManager.ButtonCallback() {
942                @Override
943                public void onStateChanged(int state) {
944                    if (mPaused) {
945                        return;
946                    }
947
948                    // At the time this callback is fired, the camera id setting
949                    // has changed to the desired camera.
950                    SettingsManager settingsManager = mAppController.getSettingsManager();
951                    if (Keys.isCameraBackFacing(settingsManager,
952                            mAppController.getModuleScope())) {
953                        throw new IllegalStateException(
954                                "Hdr plus should never be switching from front facing camera.");
955                    }
956
957                    // Switch to photo mode, but request a return to hdr plus on
958                    // switching to back camera again.
959                    settingsManager.set(mAppController.getModuleScope(),
960                            Keys.KEY_REQUEST_RETURN_HDR_PLUS, true);
961                    switchToRegularCapture();
962                }
963            };
964        } else {
965            return new ButtonManager.ButtonCallback() {
966                @Override
967                public void onStateChanged(int cameraId) {
968                    if (mPaused) {
969                        return;
970                    }
971
972                    // At the time this callback is fired, the camera id
973                    // has be set to the desired camera.
974                    mSettingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,
975                            cameraId);
976
977                    Log.d(TAG, "Start to switch camera. cameraId=" + cameraId);
978                    mCameraFacing = getFacingFromCameraId(cameraId);
979                    switchCamera();
980                }
981            };
982        }
983    }
984
985    /**
986     * Switches to PhotoModule to do regular photo captures.
987     * <p>
988     * TODO: Remove this once we use CaptureModule for photo taking.
989     */
990    private void switchToRegularCapture() {
991        // Turn off HDR+ before switching back to normal photo mode.
992        SettingsManager settingsManager = mAppController.getSettingsManager();
993        settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false);
994
995        // Disable this button to prevent callbacks from this module from firing
996        // while we are transitioning modules.
997        ButtonManager buttonManager = mAppController.getButtonManager();
998        buttonManager.disableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
999        mAppController.getCameraAppUI().freezeScreenUntilPreviewReady();
1000        mAppController.onModeSelected(mContext.getResources().getInteger(
1001                R.integer.camera_mode_photo));
1002        buttonManager.enableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
1003    }
1004
1005    /**
1006     * Called when the preview started. Informs the app controller and queues a
1007     * transform update when the next preview frame arrives.
1008     */
1009    private void onPreviewStarted() {
1010        if (mState == ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED) {
1011            mState = ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE;
1012        }
1013        mAppController.onPreviewStarted();
1014    }
1015
1016    /**
1017     * Update the preview transform based on the new dimensions. Will not force
1018     * an update, if it's not necessary.
1019     */
1020    private void updatePreviewTransform(int incomingWidth, int incomingHeight) {
1021        updatePreviewTransform(incomingWidth, incomingHeight, false);
1022    }
1023
1024    /***
1025     * Update the preview transform based on the new dimensions. TODO: Make work
1026     * with all: aspect ratios/resolutions x screens/cameras.
1027     */
1028    private void updatePreviewTransform(int incomingWidth, int incomingHeight,
1029            boolean forceUpdate) {
1030        Log.d(TAG, "updatePreviewTransform: " + incomingWidth + " x " + incomingHeight);
1031
1032        synchronized (mDimensionLock) {
1033            int incomingRotation = CameraUtil
1034                    .getDisplayRotation(mContext);
1035            // Check for an actual change:
1036            if (mScreenHeight == incomingHeight && mScreenWidth == incomingWidth &&
1037                    incomingRotation == mDisplayRotation && !forceUpdate) {
1038                return;
1039            }
1040            // Update display rotation and dimensions
1041            mDisplayRotation = incomingRotation;
1042            mScreenWidth = incomingWidth;
1043            mScreenHeight = incomingHeight;
1044            updatePreviewBufferDimension();
1045
1046            mPreviewTranformationMatrix = mAppController.getCameraAppUI().getPreviewTransform(
1047                    mPreviewTranformationMatrix);
1048            int width = mScreenWidth;
1049            int height = mScreenHeight;
1050
1051            // Assumptions:
1052            // - Aspect ratio for the sensor buffers is in landscape
1053            // orientation,
1054            // - Dimensions of buffers received are rotated to the natural
1055            // device orientation.
1056            // - The contents of each buffer are rotated by the inverse of
1057            // the display rotation.
1058            // - Surface scales the buffer to fit the current view bounds.
1059
1060            // Get natural orientation and buffer dimensions
1061            int naturalOrientation = CaptureModuleUtil
1062                    .getDeviceNaturalOrientation(mContext);
1063            int effectiveWidth = mPreviewBufferWidth;
1064            int effectiveHeight = mPreviewBufferHeight;
1065
1066            if (DEBUG) {
1067                Log.v(TAG, "Rotation: " + mDisplayRotation);
1068                Log.v(TAG, "Screen Width: " + mScreenWidth);
1069                Log.v(TAG, "Screen Height: " + mScreenHeight);
1070                Log.v(TAG, "Buffer width: " + mPreviewBufferWidth);
1071                Log.v(TAG, "Buffer height: " + mPreviewBufferHeight);
1072                Log.v(TAG, "Natural orientation: " + naturalOrientation);
1073            }
1074
1075            // If natural orientation is portrait, rotate the buffer
1076            // dimensions
1077            if (naturalOrientation == Configuration.ORIENTATION_PORTRAIT) {
1078                int temp = effectiveWidth;
1079                effectiveWidth = effectiveHeight;
1080                effectiveHeight = temp;
1081            }
1082
1083            // Find and center view rect and buffer rect
1084            RectF viewRect = new RectF(0, 0, width, height);
1085            RectF bufRect = new RectF(0, 0, effectiveWidth, effectiveHeight);
1086            float centerX = viewRect.centerX();
1087            float centerY = viewRect.centerY();
1088            bufRect.offset(centerX - bufRect.centerX(), centerY - bufRect.centerY());
1089
1090            // Undo ScaleToFit.FILL done by the surface
1091            mPreviewTranformationMatrix.setRectToRect(viewRect, bufRect, Matrix.ScaleToFit.FILL);
1092
1093            // Rotate buffer contents to proper orientation
1094            mPreviewTranformationMatrix.postRotate(getPreviewOrientation(mDisplayRotation),
1095                    centerX, centerY);
1096
1097            // TODO: This is probably only working for the N5. Need to test
1098            // on a device like N10 with different sensor orientation.
1099            if ((mDisplayRotation % 180) == 90) {
1100                int temp = effectiveWidth;
1101                effectiveWidth = effectiveHeight;
1102                effectiveHeight = temp;
1103            }
1104
1105            // Scale to fit view, cropping the longest dimension
1106            float scale =
1107                    Math.min(width / (float) effectiveWidth, height
1108                            / (float) effectiveHeight);
1109            mPreviewTranformationMatrix.postScale(scale, scale, centerX, centerY);
1110
1111            // TODO: Take these quantities from mPreviewArea.
1112            float previewWidth = effectiveWidth * scale;
1113            float previewHeight = effectiveHeight * scale;
1114            float previewCenterX = previewWidth / 2;
1115            float previewCenterY = previewHeight / 2;
1116            mPreviewTranformationMatrix.postTranslate(previewCenterX - centerX, previewCenterY
1117                    - centerY);
1118
1119            mAppController.updatePreviewTransform(mPreviewTranformationMatrix);
1120            mAppController.getCameraAppUI().hideLetterboxing();
1121            // if (mGcamProxy != null) {
1122            // mGcamProxy.postSetAspectRatio(mFinalAspectRatio);
1123            // }
1124            // mUI.updatePreviewAreaRect(new RectF(0, 0, previewWidth,
1125            // previewHeight));
1126
1127            // TODO: Add face detection.
1128            // Characteristics info =
1129            // mapp.getCameraProvider().getCharacteristics(0);
1130            // mUI.setupFaceDetection(CameraUtil.getDisplayOrientation(incomingRotation,
1131            // info), false);
1132            // updateCamera2FaceBoundTransform(new
1133            // RectF(mEffectiveCropRegion),
1134            // new RectF(0, 0, mBufferWidth, mBufferHeight),
1135            // new RectF(0, 0, previewWidth, previewHeight), getRotation());
1136        }
1137    }
1138
1139    /**
1140     * Based on the current picture size, selects the best preview dimension and
1141     * stores it in {@link #mPreviewBufferWidth} and
1142     * {@link #mPreviewBufferHeight}.
1143     */
1144    private void updatePreviewBufferDimension() {
1145        if (mCamera == null) {
1146            return;
1147        }
1148
1149        Size pictureSize = getPictureSizeFromSettings();
1150        Size previewBufferSize = mCamera.pickPreviewSize(pictureSize, mContext);
1151        mPreviewBufferWidth = previewBufferSize.getWidth();
1152        mPreviewBufferHeight = previewBufferSize.getHeight();
1153    }
1154
1155    /**
1156     * Resets the default buffer size to the initially calculated size.
1157     */
1158    private void resetDefaultBufferSize() {
1159        synchronized (mSurfaceLock) {
1160            if (mPreviewTexture != null) {
1161                mPreviewTexture.setDefaultBufferSize(mPreviewBufferWidth, mPreviewBufferHeight);
1162            }
1163        }
1164    }
1165
1166    /**
1167     * Open camera and start the preview.
1168     */
1169    private void openCameraAndStartPreview() {
1170        // Only enable HDR on the back camera
1171        boolean useHdr = mHdrEnabled && mCameraFacing == Facing.BACK;
1172        mCameraManager.open(mCameraFacing, useHdr, getPictureSizeFromSettings(),
1173                new OpenCallback() {
1174                    @Override
1175                    public void onFailure() {
1176                        Log.e(TAG, "Could not open camera.");
1177                        mCamera = null;
1178                        mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
1179                    }
1180
1181                    @Override
1182                    public void onCameraOpened(final OneCamera camera) {
1183                        Log.d(TAG, "onCameraOpened: " + camera);
1184                        mCamera = camera;
1185                        updatePreviewBufferDimension();
1186
1187                        // If the surface texture is not destroyed, it may have
1188                        // the last frame lingering. We need to hold off setting
1189                        // transform until preview is started.
1190                        resetDefaultBufferSize();
1191                        mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED;
1192                        Log.d(TAG, "starting preview ...");
1193
1194                        // TODO: Consider rolling these two calls into one.
1195                        camera.startPreview(new Surface(mPreviewTexture),
1196                                new CaptureReadyCallback() {
1197                                    @Override
1198                                    public void onSetupFailed() {
1199                                        Log.e(TAG, "Could not set up preview.");
1200                                        mCamera.close(null);
1201                                        mCamera = null;
1202                                        // TODO: Show an error message and exit.
1203                                    }
1204
1205                                    @Override
1206                                    public void onReadyForCapture() {
1207                                        Log.d(TAG, "Ready for capture.");
1208                                        onPreviewStarted();
1209                                        // Enable zooming after preview has
1210                                        // started.
1211                                        mUI.initializeZoom(mCamera.getMaxZoom());
1212                                        mCamera.setFocusStateListener(CaptureModule.this);
1213                                        mCamera.setReadyStateChangedListener(CaptureModule.this);
1214                                    }
1215                                });
1216                    }
1217                });
1218    }
1219
1220    private void closeCamera() {
1221        if (mCamera != null) {
1222            mCamera.setFocusStateListener(null);
1223            mCamera.close(null);
1224            mCamera = null;
1225        }
1226    }
1227
1228    private int getOrientation() {
1229        if (mAppController.isAutoRotateScreen()) {
1230            return mDisplayRotation;
1231        } else {
1232            return mOrientation;
1233        }
1234    }
1235
1236    /**
1237     * @return Whether we are resuming from within the lockscreen.
1238     */
1239    private static boolean isResumeFromLockscreen(Activity activity) {
1240        String action = activity.getIntent().getAction();
1241        return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action)
1242        || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action));
1243    }
1244
1245    /**
1246     * Re-initialize the camera if e.g. the HDR mode or facing property changed.
1247     */
1248    private void switchCamera() {
1249        if (mPaused) {
1250            return;
1251        }
1252        cancelCountDown();
1253        mAppController.freezeScreenUntilPreviewReady();
1254        initSurface(mPreviewTexture);
1255
1256        // TODO: Un-comment once we have focus back.
1257        // if (mFocusManager != null) {
1258        // mFocusManager.removeMessages();
1259        // }
1260        // mFocusManager.setMirror(mMirror);
1261    }
1262
1263    private Size getPictureSizeFromSettings() {
1264        String pictureSizeKey = mCameraFacing == Facing.FRONT ? Keys.KEY_PICTURE_SIZE_FRONT
1265                : Keys.KEY_PICTURE_SIZE_BACK;
1266        return mSettingsManager.getSize(SettingsManager.SCOPE_GLOBAL, pictureSizeKey);
1267    }
1268
1269    private int getPreviewOrientation(int deviceOrientationDegrees) {
1270        // Important: Camera2 buffers are already rotated to the natural
1271        // orientation of the device (at least for the back-camera).
1272
1273        // TODO: Remove this hack for the front camera as soon as b/16637957 is
1274        // fixed.
1275        if (mCameraFacing == Facing.FRONT) {
1276            deviceOrientationDegrees += 180;
1277        }
1278        return (360 - deviceOrientationDegrees) % 360;
1279    }
1280
1281    /**
1282     * Returns which way around the camera is facing, based on it's ID.
1283     * <p>
1284     * TODO: This needs to change so that we store the direction directly in the
1285     * settings, rather than a Camera ID.
1286     */
1287    private static Facing getFacingFromCameraId(int cameraId) {
1288        return cameraId == 1 ? Facing.FRONT : Facing.BACK;
1289    }
1290
1291    private void resetTextureBufferSize() {
1292        // Reset the default buffer sizes on the shared SurfaceTexture
1293        // so they are not scaled for gcam.
1294        //
1295        // According to the documentation for
1296        // SurfaceTexture.setDefaultBufferSize,
1297        // photo and video based image producers (presumably only Camera 1 api),
1298        // override this buffer size. Any module that uses egl to render to a
1299        // SurfaceTexture must have these buffer sizes reset manually. Otherwise
1300        // the SurfaceTexture cannot be transformed by matrix set on the
1301        // TextureView.
1302        if (mPreviewTexture != null) {
1303            mPreviewTexture.setDefaultBufferSize(mAppController.getCameraAppUI().getSurfaceWidth(),
1304                    mAppController.getCameraAppUI().getSurfaceHeight());
1305        }
1306    }
1307
1308    /**
1309     * @return The currently set Flash settings. Defaults to AUTO if the setting
1310     *         could not be parsed.
1311     */
1312    private Flash getFlashModeFromSettings() {
1313        String flashSetting = mSettingsManager.getString(mAppController.getCameraScope(),
1314                Keys.KEY_FLASH_MODE);
1315        try {
1316            return Flash.valueOf(flashSetting.toUpperCase());
1317        } catch (IllegalArgumentException ex) {
1318            Log.w(TAG, "Could not parse Flash Setting. Defaulting to AUTO.");
1319            return Flash.AUTO;
1320        }
1321    }
1322}
1323