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