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