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