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