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