CaptureModule.java revision ea77c6b2f9a602712c19ed0f441c6439b2f168de
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        mAppController.getCameraAppUI().transitionToCapture();
381        mAppController.getCameraAppUI().showModeOptions();
382        if (mPaused) {
383            return;
384        }
385        takePictureNow();
386    }
387
388    @Override
389    public void onRemainingSecondsChanged(int remainingSeconds) {
390        if (remainingSeconds == 1) {
391            mCountdownSoundPlayer.play(R.raw.timer_final_second, 0.6f);
392        } else if (remainingSeconds == 2 || remainingSeconds == 3) {
393            mCountdownSoundPlayer.play(R.raw.timer_increment, 0.6f);
394        }
395    }
396
397    private void cancelCountDown() {
398        if (mUI.isCountingDown()) {
399            // Cancel on-going countdown.
400            mUI.cancelCountDown();
401        }
402        mAppController.getCameraAppUI().showModeOptions();
403        mAppController.getCameraAppUI().transitionToCapture();
404    }
405
406    @Override
407    public void onQuickExpose() {
408        mMainHandler.post(new Runnable() {
409            @Override
410            public void run() {
411                // Starts the short version of the capture animation UI.
412                mAppController.startPreCaptureAnimation(true);
413            }
414        });
415    }
416
417    @Override
418    public void onPreviewAreaChanged(RectF previewArea) {
419        mPreviewArea = previewArea;
420        mUI.onPreviewAreaChanged(previewArea);
421        // mUI.updatePreviewAreaRect(previewArea);
422        mUI.positionProgressOverlay(previewArea);
423    }
424
425    @Override
426    public void onSensorChanged(SensorEvent event) {
427        // This is literally the same as the GCamModule implementation.
428        int type = event.sensor.getType();
429        float[] data;
430        if (type == Sensor.TYPE_ACCELEROMETER) {
431            data = mGData;
432        } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
433            data = mMData;
434        } else {
435            Log.w(TAG, String.format("Unexpected sensor type %s", event.sensor.getName()));
436            return;
437        }
438        for (int i = 0; i < 3; i++) {
439            data[i] = event.values[i];
440        }
441        float[] orientation = new float[3];
442        SensorManager.getRotationMatrix(mR, null, mGData, mMData);
443        SensorManager.getOrientation(mR, orientation);
444        mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
445
446        if (mHeading < 0) {
447            mHeading += 360;
448        }
449    }
450
451    @Override
452    public void onAccuracyChanged(Sensor sensor, int accuracy) {
453        // TODO Auto-generated method stub
454    }
455
456    @Override
457    public void onQueueStatus(boolean full) {
458        // TODO Auto-generated method stub
459    }
460
461    @Override
462    public void onRemoteShutterPress() {
463        // TODO: Check whether shutter is enabled.
464        onShutterButtonClick();
465    }
466
467    @Override
468    public void onSurfaceTextureAvailable(final SurfaceTexture surface, int width, int height) {
469        Log.d(TAG, "onSurfaceTextureAvailable");
470        // Force to re-apply transform matrix here as a workaround for
471        // b/11168275
472        updatePreviewTransform(width, height, true);
473        initSurface(surface);
474    }
475
476    public void initSurface(final SurfaceTexture surface) {
477        mPreviewTexture = surface;
478        closeCamera();
479        openCameraAndStartPreview();
480    }
481
482    @Override
483    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
484        Log.d(TAG, "onSurfaceTextureSizeChanged");
485        resetDefaultBufferSize();
486    }
487
488    @Override
489    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
490        Log.d(TAG, "onSurfaceTextureDestroyed");
491        mPreviewTexture = null;
492        closeCamera();
493        return true;
494    }
495
496    @Override
497    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
498        if (mState == ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE) {
499            Log.d(TAG, "onSurfaceTextureUpdated --> updatePreviewTransform");
500            mState = ModuleState.IDLE;
501            CameraAppUI appUI = mAppController.getCameraAppUI();
502            updatePreviewTransform(appUI.getSurfaceWidth(), appUI.getSurfaceHeight(), true);
503        }
504    }
505
506    @Override
507    public String getModuleStringIdentifier() {
508        return PHOTO_MODULE_STRING_ID;
509    }
510
511    @Override
512    public void resume() {
513        mPaused = false;
514        mAppController.getCameraAppUI().onChangeCamera();
515        mAppController.addPreviewAreaSizeChangedListener(this);
516        resetDefaultBufferSize();
517        getServices().getRemoteShutterListener().onModuleReady(this);
518        // TODO: Check if we can really take a photo right now (memory, camera
519        // state, ... ).
520        mAppController.getCameraAppUI().enableModeOptions();
521        mAppController.setShutterEnabled(true);
522
523        // Get events from the accelerometer and magnetic sensor.
524        if (mAccelerometerSensor != null) {
525            mSensorManager.registerListener(this, mAccelerometerSensor,
526                    SensorManager.SENSOR_DELAY_NORMAL);
527        }
528        if (mMagneticSensor != null) {
529            mSensorManager.registerListener(this, mMagneticSensor,
530                    SensorManager.SENSOR_DELAY_NORMAL);
531        }
532        mHdrEnabled = mStickyGcamCamera || mAppController.getSettingsManager().getInteger(
533                SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS) == 1;
534
535        // This means we are resuming with an existing preview texture. This
536        // means we will never get the onSurfaceTextureAvailable call. So we
537        // have to open the camera and start the preview here.
538        if (mPreviewTexture != null) {
539            initSurface(mPreviewTexture);
540        }
541
542        mCountdownSoundPlayer.loadSound(R.raw.timer_final_second);
543        mCountdownSoundPlayer.loadSound(R.raw.timer_increment);
544    }
545
546    @Override
547    public void pause() {
548        mPaused = true;
549        cancelCountDown();
550        closeCamera();
551        resetTextureBufferSize();
552        mCountdownSoundPlayer.unloadSound(R.raw.timer_final_second);
553        mCountdownSoundPlayer.unloadSound(R.raw.timer_increment);
554        // Remove delayed resume trigger, if it hasn't been executed yet.
555        mMainHandler.removeCallbacksAndMessages(null);
556
557        // Unregister the sensors.
558        if (mAccelerometerSensor != null) {
559            mSensorManager.unregisterListener(this, mAccelerometerSensor);
560        }
561        if (mMagneticSensor != null) {
562            mSensorManager.unregisterListener(this, mMagneticSensor);
563        }
564    }
565
566    @Override
567    public void destroy() {
568        mCountdownSoundPlayer.release();
569        mCameraHandler.getLooper().quitSafely();
570    }
571
572    @Override
573    public void onLayoutOrientationChanged(boolean isLandscape) {
574        Log.d(TAG, "onLayoutOrientationChanged");
575    }
576
577    @Override
578    public void onOrientationChanged(int orientation) {
579        // We keep the last known orientation. So if the user first orient
580        // the camera then point the camera to floor or sky, we still have
581        // the correct orientation.
582        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
583            return;
584        }
585
586        // TODO: Document orientation compute logic and unify them in OrientationManagerImpl.
587        // b/17443789
588        // Flip to counter-clockwise orientation.
589        mOrientation = (360 - orientation) % 360;
590    }
591
592    @Override
593    public void onCameraAvailable(CameraProxy cameraProxy) {
594        // Ignore since we manage the camera ourselves until we remove this.
595    }
596
597    @Override
598    public void hardResetSettings(SettingsManager settingsManager) {
599        if (mStickyGcamCamera) {
600            // Sitcky HDR+ mode should hard reset HDR+ to on, and camera back
601            // facing.
602            settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, true);
603            settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,
604                    getBackFacingCameraId());
605        }
606    }
607
608    @Override
609    public HardwareSpec getHardwareSpec() {
610        return new HardwareSpec() {
611            @Override
612            public boolean isFrontCameraSupported() {
613                return true;
614            }
615
616            @Override
617            public boolean isHdrSupported() {
618                // TODO: Check if the device has HDR and not HDR+.
619                return false;
620            }
621
622            @Override
623            public boolean isHdrPlusSupported() {
624                return GcamHelper.hasGcamCapture();
625            }
626
627            @Override
628            public boolean isFlashSupported() {
629                return true;
630            }
631        };
632    }
633
634    @Override
635    public BottomBarUISpec getBottomBarSpec() {
636        CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
637        bottomBarSpec.enableGridLines = true;
638        bottomBarSpec.enableCamera = true;
639        bottomBarSpec.cameraCallback = getCameraCallback();
640        bottomBarSpec.enableHdr = GcamHelper.hasGcamCapture();
641        bottomBarSpec.hdrCallback = getHdrButtonCallback();
642        bottomBarSpec.enableSelfTimer = true;
643        bottomBarSpec.showSelfTimer = true;
644        if (!mHdrEnabled) {
645            bottomBarSpec.enableFlash = true;
646        }
647        // Added to handle case of CaptureModule being used only for Gcam.
648        if (mStickyGcamCamera) {
649            bottomBarSpec.enableFlash = false;
650        }
651        return bottomBarSpec;
652    }
653
654    @Override
655    public boolean isUsingBottomBar() {
656        return true;
657    }
658
659    @Override
660    public boolean onKeyDown(int keyCode, KeyEvent event) {
661        switch (keyCode) {
662            case KeyEvent.KEYCODE_CAMERA:
663            case KeyEvent.KEYCODE_DPAD_CENTER:
664                if (mUI.isCountingDown()) {
665                    cancelCountDown();
666                } else if (event.getRepeatCount() == 0) {
667                    onShutterButtonClick();
668                }
669                return true;
670            case KeyEvent.KEYCODE_VOLUME_UP:
671            case KeyEvent.KEYCODE_VOLUME_DOWN:
672                // Prevent default.
673                return true;
674        }
675        return false;
676    }
677
678    @Override
679    public boolean onKeyUp(int keyCode, KeyEvent event) {
680        switch (keyCode) {
681            case KeyEvent.KEYCODE_VOLUME_UP:
682            case KeyEvent.KEYCODE_VOLUME_DOWN:
683                onShutterButtonClick();
684                return true;
685        }
686        return false;
687    }
688
689    /**
690     * Focus sequence starts for zone around tap location for single tap.
691     */
692    @Override
693    public void onSingleTapUp(View view, int x, int y) {
694        Log.v(TAG, "onSingleTapUp x=" + x + " y=" + y);
695        // TODO: This should query actual capability.
696        if (mCameraFacing == Facing.FRONT) {
697            return;
698        }
699        triggerFocusAtScreenCoord(x, y);
700    }
701
702    // TODO: Consider refactoring FocusOverlayManager.
703    // Currently AF state transitions are controlled in OneCameraImpl.
704    // PhotoModule uses FocusOverlayManager which uses API1/portability
705    // logic and coordinates.
706    private void triggerFocusAtScreenCoord(int x, int y) {
707        if (mCamera == null) {
708            // If we receive this after the camera is closed, do nothing.
709            return;
710        }
711
712        mTapToFocusWaitForActiveScan = true;
713        // Show UI immediately even though scan has not started yet.
714        float minEdge = Math.min(mPreviewArea.width(), mPreviewArea.height());
715        mUI.setAutoFocusTarget(x, y, false,
716                (int) (Settings3A.getAutoFocusRegionWidth() * mZoomValue * minEdge),
717                (int) (Settings3A.getMeteringRegionWidth() * mZoomValue * minEdge));
718        mUI.showAutoFocusInProgress();
719
720        // Cancel any scheduled auto focus target UI actions.
721        mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
722        // Timeout in case camera fails to stop (unlikely).
723        mMainHandler.postDelayed(new Runnable() {
724            @Override
725            public void run() {
726                mMainHandler.post(mHideAutoFocusTargetRunnable);
727            }
728        }, FOCUS_UI_TIMEOUT_MILLIS);
729
730        // Normalize coordinates to [0,1] per CameraOne API.
731        float points[] = new float[2];
732        points[0] = (x - mPreviewArea.left) / mPreviewArea.width();
733        points[1] = (y - mPreviewArea.top) / mPreviewArea.height();
734
735        // Rotate coordinates to portrait orientation per CameraOne API.
736        Matrix rotationMatrix = new Matrix();
737        rotationMatrix.setRotate(mDisplayRotation, 0.5f, 0.5f);
738        rotationMatrix.mapPoints(points);
739        mCamera.triggerFocusAndMeterAtPoint(points[0], points[1]);
740
741        // Log touch (screen coordinates).
742        if (mZoomValue == 1f) {
743            TouchCoordinate touchCoordinate = new TouchCoordinate(x - mPreviewArea.left,
744                    y - mPreviewArea.top, mPreviewArea.width(), mPreviewArea.height());
745            // TODO: Add to logging: duration, rotation.
746            UsageStatistics.instance().tapToFocus(touchCoordinate, null);
747        }
748    }
749
750    /**
751     * Show AF target in center of preview.
752     */
753    private void setAutoFocusTargetPassive() {
754        float minEdge = Math.min(mPreviewArea.width(), mPreviewArea.height());
755        mUI.setAutoFocusTarget((int) mPreviewArea.centerX(), (int) mPreviewArea.centerY(),
756                true,
757                (int) (Settings3A.getAutoFocusRegionWidth() * mZoomValue * minEdge),
758                (int) (Settings3A.getMeteringRegionWidth() * mZoomValue * minEdge));
759        mUI.showAutoFocusInProgress();
760    }
761
762    /**
763     * Update UI based on AF state changes.
764     */
765    @Override
766    public void onFocusStatusUpdate(final AutoFocusState state, long frameNumber) {
767        Log.v(TAG, "AF status is state:" + state);
768
769        switch (state) {
770            case PASSIVE_SCAN:
771                mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
772                mMainHandler.post(new Runnable() {
773                    @Override
774                    public void run() {
775                        setAutoFocusTargetPassive();
776                    }
777                });
778                break;
779            case ACTIVE_SCAN:
780                mTapToFocusWaitForActiveScan = false;
781                break;
782            case PASSIVE_FOCUSED:
783            case PASSIVE_UNFOCUSED:
784                mMainHandler.post(new Runnable() {
785                    @Override
786                    public void run() {
787                        mUI.setPassiveFocusSuccess(state == AutoFocusState.PASSIVE_FOCUSED);
788                    }
789                });
790                break;
791            case ACTIVE_FOCUSED:
792            case ACTIVE_UNFOCUSED:
793                if (!mTapToFocusWaitForActiveScan) {
794                    mFocusedAtEnd = state != AutoFocusState.ACTIVE_UNFOCUSED;
795                    mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
796                    mMainHandler.post(mHideAutoFocusTargetRunnable);
797                }
798                break;
799        }
800
801        if (CAPTURE_DEBUG_UI) {
802            measureAutoFocusScans(state, frameNumber);
803        }
804    }
805
806    private void measureAutoFocusScans(final AutoFocusState state, long frameNumber) {
807        // Log AF scan lengths.
808        boolean passive = false;
809        switch (state) {
810            case PASSIVE_SCAN:
811            case ACTIVE_SCAN:
812                if (mAutoFocusScanStartFrame == -1) {
813                    mAutoFocusScanStartFrame = frameNumber;
814                    mAutoFocusScanStartTime = SystemClock.uptimeMillis();
815                }
816                break;
817            case PASSIVE_FOCUSED:
818            case PASSIVE_UNFOCUSED:
819                passive = true;
820            case ACTIVE_FOCUSED:
821            case ACTIVE_UNFOCUSED:
822                if (mAutoFocusScanStartFrame != -1) {
823                    long frames = frameNumber - mAutoFocusScanStartFrame;
824                    long dt = SystemClock.uptimeMillis() - mAutoFocusScanStartTime;
825                    int fps = Math.round(frames * 1000f / dt);
826                    String report = String.format("%s scan: fps=%d frames=%d",
827                            passive ? "CAF" : "AF", fps, frames);
828                    Log.v(TAG, report);
829                    mUI.showDebugMessage(String.format("%d / %d", frames, fps));
830                    mAutoFocusScanStartFrame = -1;
831                }
832                break;
833        }
834    }
835
836    @Override
837    public void onReadyStateChanged(boolean readyForCapture) {
838        if (readyForCapture) {
839            mAppController.getCameraAppUI().enableModeOptions();
840        }
841        mAppController.setShutterEnabled(readyForCapture);
842    }
843
844    @Override
845    public String getPeekAccessibilityString() {
846        return mAppController.getAndroidContext()
847                .getResources().getString(R.string.photo_accessibility_peek);
848    }
849
850    @Override
851    public void onThumbnailResult(Bitmap bitmap) {
852        // TODO
853    }
854
855    @Override
856    public void onPictureTaken(CaptureSession session) {
857        mAppController.getCameraAppUI().enableModeOptions();
858    }
859
860    @Override
861    public void onPictureSaved(Uri uri) {
862        mAppController.notifyNewMedia(uri);
863    }
864
865    @Override
866    public void onTakePictureProgress(float progress) {
867        mUI.setPictureTakingProgress((int) (progress * 100));
868    }
869
870    @Override
871    public void onPictureTakenFailed() {
872    }
873
874    @Override
875    public void onSettingChanged(SettingsManager settingsManager, String key) {
876        // TODO Auto-generated method stub
877    }
878
879    /**
880     * Updates the preview transform matrix to adapt to the current preview
881     * width, height, and orientation.
882     */
883    public void updatePreviewTransform() {
884        int width;
885        int height;
886        synchronized (mDimensionLock) {
887            width = mScreenWidth;
888            height = mScreenHeight;
889        }
890        updatePreviewTransform(width, height);
891    }
892
893    /**
894     * Set zoom value.
895     *
896     * @param zoom Zoom value, must be between 1.0 and mCamera.getMaxZoom().
897     */
898    public void setZoom(float zoom) {
899        mZoomValue = zoom;
900        if (mCamera != null) {
901            mCamera.setZoom(zoom);
902        }
903    }
904
905    /**
906     * TODO: Remove this method once we are in pure CaptureModule land.
907     */
908    private String getBackFacingCameraId() {
909        if (!(mCameraManager instanceof OneCameraManagerImpl)) {
910            throw new IllegalStateException("This should never be called with Camera API V1");
911        }
912        OneCameraManagerImpl manager = (OneCameraManagerImpl) mCameraManager;
913        return manager.getFirstBackCameraId();
914    }
915
916    /**
917     * @return Depending on whether we're in sticky-HDR mode or not, return the
918     *         proper callback to be used for when the HDR/HDR+ button is
919     *         pressed.
920     */
921    private ButtonManager.ButtonCallback getHdrButtonCallback() {
922        if (mStickyGcamCamera) {
923            return new ButtonManager.ButtonCallback() {
924                @Override
925                public void onStateChanged(int state) {
926                    if (mPaused) {
927                        return;
928                    }
929                    if (state == ButtonManager.ON) {
930                        throw new IllegalStateException(
931                                "Can't leave hdr plus mode if switching to hdr plus mode.");
932                    }
933                    SettingsManager settingsManager = mAppController.getSettingsManager();
934                    settingsManager.set(mAppController.getModuleScope(),
935                            Keys.KEY_REQUEST_RETURN_HDR_PLUS, false);
936                    switchToRegularCapture();
937                }
938            };
939        } else {
940            return new ButtonManager.ButtonCallback() {
941                @Override
942                public void onStateChanged(int hdrEnabled) {
943                    if (mPaused) {
944                        return;
945                    }
946                    Log.d(TAG, "HDR enabled =" + hdrEnabled);
947                    mHdrEnabled = hdrEnabled == 1;
948                    switchCamera();
949                }
950            };
951        }
952    }
953
954    /**
955     * @return Depending on whether we're in sticky-HDR mode or not, this
956     *         returns the proper callback to be used for when the camera
957     *         (front/back switch) button is pressed.
958     */
959    private ButtonManager.ButtonCallback getCameraCallback() {
960        if (mStickyGcamCamera) {
961            return new ButtonManager.ButtonCallback() {
962                @Override
963                public void onStateChanged(int state) {
964                    if (mPaused) {
965                        return;
966                    }
967
968                    // At the time this callback is fired, the camera id setting
969                    // has changed to the desired camera.
970                    SettingsManager settingsManager = mAppController.getSettingsManager();
971                    if (Keys.isCameraBackFacing(settingsManager,
972                            mAppController.getModuleScope())) {
973                        throw new IllegalStateException(
974                                "Hdr plus should never be switching from front facing camera.");
975                    }
976
977                    // Switch to photo mode, but request a return to hdr plus on
978                    // switching to back camera again.
979                    settingsManager.set(mAppController.getModuleScope(),
980                            Keys.KEY_REQUEST_RETURN_HDR_PLUS, true);
981                    switchToRegularCapture();
982                }
983            };
984        } else {
985            return new ButtonManager.ButtonCallback() {
986                @Override
987                public void onStateChanged(int cameraId) {
988                    if (mPaused) {
989                        return;
990                    }
991
992                    // At the time this callback is fired, the camera id
993                    // has be set to the desired camera.
994                    mSettingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,
995                            cameraId);
996
997                    Log.d(TAG, "Start to switch camera. cameraId=" + cameraId);
998                    mCameraFacing = getFacingFromCameraId(cameraId);
999                    switchCamera();
1000                }
1001            };
1002        }
1003    }
1004
1005    /**
1006     * Switches to PhotoModule to do regular photo captures.
1007     * <p>
1008     * TODO: Remove this once we use CaptureModule for photo taking.
1009     */
1010    private void switchToRegularCapture() {
1011        // Turn off HDR+ before switching back to normal photo mode.
1012        SettingsManager settingsManager = mAppController.getSettingsManager();
1013        settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false);
1014
1015        // Disable this button to prevent callbacks from this module from firing
1016        // while we are transitioning modules.
1017        ButtonManager buttonManager = mAppController.getButtonManager();
1018        buttonManager.disableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
1019        mAppController.getCameraAppUI().freezeScreenUntilPreviewReady();
1020        mAppController.onModeSelected(mContext.getResources().getInteger(
1021                R.integer.camera_mode_photo));
1022        buttonManager.enableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
1023    }
1024
1025    /**
1026     * Called when the preview started. Informs the app controller and queues a
1027     * transform update when the next preview frame arrives.
1028     */
1029    private void onPreviewStarted() {
1030        if (mState == ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED) {
1031            mState = ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE;
1032        }
1033        mAppController.onPreviewStarted();
1034    }
1035
1036    /**
1037     * Update the preview transform based on the new dimensions. Will not force
1038     * an update, if it's not necessary.
1039     */
1040    private void updatePreviewTransform(int incomingWidth, int incomingHeight) {
1041        updatePreviewTransform(incomingWidth, incomingHeight, false);
1042    }
1043
1044    /***
1045     * Update the preview transform based on the new dimensions. TODO: Make work
1046     * with all: aspect ratios/resolutions x screens/cameras.
1047     */
1048    private void updatePreviewTransform(int incomingWidth, int incomingHeight,
1049            boolean forceUpdate) {
1050        Log.d(TAG, "updatePreviewTransform: " + incomingWidth + " x " + incomingHeight);
1051
1052        synchronized (mDimensionLock) {
1053            int incomingRotation = CameraUtil
1054                    .getDisplayRotation(mContext);
1055            // Check for an actual change:
1056            if (mScreenHeight == incomingHeight && mScreenWidth == incomingWidth &&
1057                    incomingRotation == mDisplayRotation && !forceUpdate) {
1058                return;
1059            }
1060            // Update display rotation and dimensions
1061            mDisplayRotation = incomingRotation;
1062            mScreenWidth = incomingWidth;
1063            mScreenHeight = incomingHeight;
1064            updatePreviewBufferDimension();
1065
1066            mPreviewTranformationMatrix = mAppController.getCameraAppUI().getPreviewTransform(
1067                    mPreviewTranformationMatrix);
1068            int width = mScreenWidth;
1069            int height = mScreenHeight;
1070
1071            // Assumptions:
1072            // - Aspect ratio for the sensor buffers is in landscape
1073            // orientation,
1074            // - Dimensions of buffers received are rotated to the natural
1075            // device orientation.
1076            // - The contents of each buffer are rotated by the inverse of
1077            // the display rotation.
1078            // - Surface scales the buffer to fit the current view bounds.
1079
1080            // Get natural orientation and buffer dimensions
1081            int naturalOrientation = CaptureModuleUtil
1082                    .getDeviceNaturalOrientation(mContext);
1083            int effectiveWidth = mPreviewBufferWidth;
1084            int effectiveHeight = mPreviewBufferHeight;
1085
1086            if (DEBUG) {
1087                Log.v(TAG, "Rotation: " + mDisplayRotation);
1088                Log.v(TAG, "Screen Width: " + mScreenWidth);
1089                Log.v(TAG, "Screen Height: " + mScreenHeight);
1090                Log.v(TAG, "Buffer width: " + mPreviewBufferWidth);
1091                Log.v(TAG, "Buffer height: " + mPreviewBufferHeight);
1092                Log.v(TAG, "Natural orientation: " + naturalOrientation);
1093            }
1094
1095            // If natural orientation is portrait, rotate the buffer
1096            // dimensions
1097            if (naturalOrientation == Configuration.ORIENTATION_PORTRAIT) {
1098                int temp = effectiveWidth;
1099                effectiveWidth = effectiveHeight;
1100                effectiveHeight = temp;
1101            }
1102
1103            // Find and center view rect and buffer rect
1104            RectF viewRect = new RectF(0, 0, width, height);
1105            RectF bufRect = new RectF(0, 0, effectiveWidth, effectiveHeight);
1106            float centerX = viewRect.centerX();
1107            float centerY = viewRect.centerY();
1108            bufRect.offset(centerX - bufRect.centerX(), centerY - bufRect.centerY());
1109
1110            // Undo ScaleToFit.FILL done by the surface
1111            mPreviewTranformationMatrix.setRectToRect(viewRect, bufRect, Matrix.ScaleToFit.FILL);
1112
1113            // Rotate buffer contents to proper orientation
1114            mPreviewTranformationMatrix.postRotate(getPreviewOrientation(mDisplayRotation),
1115                    centerX, centerY);
1116
1117            // TODO: This is probably only working for the N5. Need to test
1118            // on a device like N10 with different sensor orientation.
1119            if ((mDisplayRotation % 180) == 90) {
1120                int temp = effectiveWidth;
1121                effectiveWidth = effectiveHeight;
1122                effectiveHeight = temp;
1123            }
1124
1125            // Scale to fit view, cropping the longest dimension
1126            float scale =
1127                    Math.min(width / (float) effectiveWidth, height
1128                            / (float) effectiveHeight);
1129            mPreviewTranformationMatrix.postScale(scale, scale, centerX, centerY);
1130
1131            // TODO: Take these quantities from mPreviewArea.
1132            float previewWidth = effectiveWidth * scale;
1133            float previewHeight = effectiveHeight * scale;
1134            float previewCenterX = previewWidth / 2;
1135            float previewCenterY = previewHeight / 2;
1136            mPreviewTranformationMatrix.postTranslate(previewCenterX - centerX, previewCenterY
1137                    - centerY);
1138
1139            mAppController.updatePreviewTransform(mPreviewTranformationMatrix);
1140            // if (mGcamProxy != null) {
1141            // mGcamProxy.postSetAspectRatio(mFinalAspectRatio);
1142            // }
1143            // mUI.updatePreviewAreaRect(new RectF(0, 0, previewWidth,
1144            // previewHeight));
1145
1146            // TODO: Add face detection.
1147            // Characteristics info =
1148            // mapp.getCameraProvider().getCharacteristics(0);
1149            // mUI.setupFaceDetection(CameraUtil.getDisplayOrientation(incomingRotation,
1150            // info), false);
1151            // updateCamera2FaceBoundTransform(new
1152            // RectF(mEffectiveCropRegion),
1153            // new RectF(0, 0, mBufferWidth, mBufferHeight),
1154            // new RectF(0, 0, previewWidth, previewHeight), getRotation());
1155        }
1156    }
1157
1158    /**
1159     * Based on the current picture size, selects the best preview dimension and
1160     * stores it in {@link #mPreviewBufferWidth} and
1161     * {@link #mPreviewBufferHeight}.
1162     */
1163    private void updatePreviewBufferDimension() {
1164        if (mCamera == null) {
1165            return;
1166        }
1167
1168        Size pictureSize = getPictureSizeFromSettings();
1169        Size previewBufferSize = mCamera.pickPreviewSize(pictureSize, mContext);
1170        mPreviewBufferWidth = previewBufferSize.getWidth();
1171        mPreviewBufferHeight = previewBufferSize.getHeight();
1172    }
1173
1174    /**
1175     * Resets the default buffer size to the initially calculated size.
1176     */
1177    private void resetDefaultBufferSize() {
1178        synchronized (mSurfaceLock) {
1179            if (mPreviewTexture != null) {
1180                mPreviewTexture.setDefaultBufferSize(mPreviewBufferWidth, mPreviewBufferHeight);
1181            }
1182        }
1183    }
1184
1185    /**
1186     * Open camera and start the preview.
1187     */
1188    private void openCameraAndStartPreview() {
1189        // Only enable HDR on the back camera
1190        boolean useHdr = mHdrEnabled && mCameraFacing == Facing.BACK;
1191
1192        try {
1193            // TODO Given the current design, we cannot guarantee that one of
1194            // CaptureReadyCallback.onSetupFailed or onReadyForCapture will
1195            // be called (see below), so it's possible that
1196            // mCameraOpenCloseLock.release() is never called under extremely
1197            // rare cases.  If we leak the lock, this timeout ensures that we at
1198            // least crash so we don't deadlock the app.
1199            if (!mCameraOpenCloseLock.tryAcquire(CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
1200                throw new RuntimeException("Time out waiting to acquire camera-open lock.");
1201            }
1202        } catch (InterruptedException e) {
1203            throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e);
1204        }
1205        if (mCamera != null) {
1206            // If the camera is already open, do nothing.
1207            Log.d(TAG, "Camera already open, not re-opening.");
1208            mCameraOpenCloseLock.release();
1209            return;
1210        }
1211        mCameraManager.open(mCameraFacing, useHdr, getPictureSizeFromSettings(),
1212                new OpenCallback() {
1213                    @Override
1214                    public void onFailure() {
1215                        Log.e(TAG, "Could not open camera.");
1216                        mCamera = null;
1217                        mCameraOpenCloseLock.release();
1218                        mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
1219                    }
1220
1221                    @Override
1222                    public void onCameraClosed() {
1223                        mCamera = null;
1224                        mCameraOpenCloseLock.release();
1225                    }
1226
1227                    @Override
1228                    public void onCameraOpened(final OneCamera camera) {
1229                        Log.d(TAG, "onCameraOpened: " + camera);
1230                        mCamera = camera;
1231                        updatePreviewBufferDimension();
1232
1233                        // If the surface texture is not destroyed, it may have
1234                        // the last frame lingering. We need to hold off setting
1235                        // transform until preview is started.
1236                        resetDefaultBufferSize();
1237                        mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED;
1238                        Log.d(TAG, "starting preview ...");
1239
1240                        // TODO: Consider rolling these two calls into one.
1241                        camera.startPreview(new Surface(mPreviewTexture),
1242                                new CaptureReadyCallback() {
1243                                    @Override
1244                                    public void onSetupFailed() {
1245                                        // We must release this lock here, before posting
1246                                        // to the main handler since we may be blocked
1247                                        // in pause(), getting ready to close the camera.
1248                                        mCameraOpenCloseLock.release();
1249                                        Log.e(TAG, "Could not set up preview.");
1250                                        mMainHandler.post(new Runnable() {
1251                                           @Override
1252                                           public void run() {
1253                                               if (mCamera == null) {
1254                                                   Log.d(TAG, "Camera closed, aborting.");
1255                                                   return;
1256                                               }
1257                                               mCamera.close(null);
1258                                               mCamera = null;
1259                                               // TODO: Show an error message and exit.
1260                                           }
1261                                        });
1262                                    }
1263
1264                                    @Override
1265                                    public void onReadyForCapture() {
1266                                        // We must release this lock here, before posting
1267                                        // to the main handler since we may be blocked
1268                                        // in pause(), getting ready to close the camera.
1269                                        mCameraOpenCloseLock.release();
1270                                        mMainHandler.post(new Runnable() {
1271                                           @Override
1272                                           public void run() {
1273                                               Log.d(TAG, "Ready for capture.");
1274                                               if (mCamera == null) {
1275                                                   Log.d(TAG, "Camera closed, aborting.");
1276                                                   return;
1277                                               }
1278                                               onPreviewStarted();
1279                                               // Enable zooming after preview has
1280                                               // started.
1281                                               mUI.initializeZoom(mCamera.getMaxZoom());
1282                                               mCamera.setFocusStateListener(CaptureModule.this);
1283                                               mCamera.setReadyStateChangedListener(CaptureModule.this);
1284                                           }
1285                                        });
1286                                    }
1287                                });
1288                    }
1289                }, mCameraHandler);
1290    }
1291
1292    private void closeCamera() {
1293        try {
1294            mCameraOpenCloseLock.acquire();
1295        } catch(InterruptedException e) {
1296            throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e);
1297        }
1298        try {
1299            if (mCamera != null) {
1300                mCamera.close(null);
1301                mCamera.setFocusStateListener(null);
1302                mCamera = null;
1303            }
1304        } finally {
1305            mCameraOpenCloseLock.release();
1306        }
1307    }
1308
1309    private int getOrientation() {
1310        if (mAppController.isAutoRotateScreen()) {
1311            return mDisplayRotation;
1312        } else {
1313            return mOrientation;
1314        }
1315    }
1316
1317    /**
1318     * @return Whether we are resuming from within the lockscreen.
1319     */
1320    private static boolean isResumeFromLockscreen(Activity activity) {
1321        String action = activity.getIntent().getAction();
1322        return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action)
1323        || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action));
1324    }
1325
1326    /**
1327     * Re-initialize the camera if e.g. the HDR mode or facing property changed.
1328     */
1329    private void switchCamera() {
1330        if (mPaused) {
1331            return;
1332        }
1333        cancelCountDown();
1334        mAppController.freezeScreenUntilPreviewReady();
1335        initSurface(mPreviewTexture);
1336
1337        // TODO: Un-comment once we have focus back.
1338        // if (mFocusManager != null) {
1339        // mFocusManager.removeMessages();
1340        // }
1341        // mFocusManager.setMirror(mMirror);
1342    }
1343
1344    private Size getPictureSizeFromSettings() {
1345        String pictureSizeKey = mCameraFacing == Facing.FRONT ? Keys.KEY_PICTURE_SIZE_FRONT
1346                : Keys.KEY_PICTURE_SIZE_BACK;
1347        return mSettingsManager.getSize(SettingsManager.SCOPE_GLOBAL, pictureSizeKey);
1348    }
1349
1350    private int getPreviewOrientation(int deviceOrientationDegrees) {
1351        // Important: Camera2 buffers are already rotated to the natural
1352        // orientation of the device (at least for the back-camera).
1353
1354        // TODO: Remove this hack for the front camera as soon as b/16637957 is
1355        // fixed.
1356        if (mCameraFacing == Facing.FRONT) {
1357            deviceOrientationDegrees += 180;
1358        }
1359        return (360 - deviceOrientationDegrees) % 360;
1360    }
1361
1362    /**
1363     * Returns which way around the camera is facing, based on it's ID.
1364     * <p>
1365     * TODO: This needs to change so that we store the direction directly in the
1366     * settings, rather than a Camera ID.
1367     */
1368    private static Facing getFacingFromCameraId(int cameraId) {
1369        return cameraId == 1 ? Facing.FRONT : Facing.BACK;
1370    }
1371
1372    private void resetTextureBufferSize() {
1373        // Reset the default buffer sizes on the shared SurfaceTexture
1374        // so they are not scaled for gcam.
1375        //
1376        // According to the documentation for
1377        // SurfaceTexture.setDefaultBufferSize,
1378        // photo and video based image producers (presumably only Camera 1 api),
1379        // override this buffer size. Any module that uses egl to render to a
1380        // SurfaceTexture must have these buffer sizes reset manually. Otherwise
1381        // the SurfaceTexture cannot be transformed by matrix set on the
1382        // TextureView.
1383        if (mPreviewTexture != null) {
1384            mPreviewTexture.setDefaultBufferSize(mAppController.getCameraAppUI().getSurfaceWidth(),
1385                    mAppController.getCameraAppUI().getSurfaceHeight());
1386        }
1387    }
1388
1389    /**
1390     * @return The currently set Flash settings. Defaults to AUTO if the setting
1391     *         could not be parsed.
1392     */
1393    private Flash getFlashModeFromSettings() {
1394        String flashSetting = mSettingsManager.getString(mAppController.getCameraScope(),
1395                Keys.KEY_FLASH_MODE);
1396        try {
1397            return Flash.valueOf(flashSetting.toUpperCase());
1398        } catch (IllegalArgumentException ex) {
1399            Log.w(TAG, "Could not parse Flash Setting. Defaulting to AUTO.");
1400            return Flash.AUTO;
1401        }
1402    }
1403}
1404