CaptureModule.java revision fa7b92c1ee99bc6734c06bb50445cffae94e8197
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.remote.RemoteCameraModule;
59import com.android.camera.session.CaptureSession;
60import com.android.camera.settings.Keys;
61import com.android.camera.settings.ResolutionUtil;
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.Size;
67import com.android.camera.util.SystemProperties;
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     * Called when the captured media has been saved.
119     */
120    private final MediaSaver.OnMediaSavedListener mOnMediaSavedListener =
121            new MediaSaver.OnMediaSavedListener() {
122                @Override
123                public void onMediaSaved(Uri uri) {
124                    if (uri != null) {
125                        mAppController.notifyNewMedia(uri);
126                    }
127                }
128            };
129
130    /**
131     * Called when the user pressed the back/front camera switch button.
132     */
133    private final ButtonManager.ButtonCallback mCameraSwitchCallback =
134            new ButtonManager.ButtonCallback() {
135                @Override
136                public void onStateChanged(int cameraId) {
137                    // At the time this callback is fired, the camera id
138                    // has be set to the desired camera.
139                    if (mPaused) {
140                        return;
141                    }
142
143                    mSettingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,
144                            cameraId);
145
146                    Log.d(TAG, "Start to switch camera. cameraId=" + cameraId);
147                    switchCamera(getFacingFromCameraId(cameraId));
148                }
149            };
150
151    /**
152     * Show AF target in center of preview and start animation.
153     */
154    Runnable mShowAutoFocusTargetInCenterRunnable = new Runnable() {
155        @Override
156        public void run() {
157            mUI.setAutoFocusTarget(((int) (mPreviewArea.left + mPreviewArea.right)) / 2,
158                    ((int) (mPreviewArea.top + mPreviewArea.bottom)) / 2);
159            mUI.showAutoFocusInProgress();
160        }
161    };
162
163    /**
164     * Hide AF target UI element.
165     */
166    Runnable mHideAutoFocusTargetRunnable = new Runnable() {
167        @Override
168        public void run() {
169            // showAutoFocusSuccess() just hides the AF UI.
170            mUI.showAutoFocusSuccess();
171        }
172    };
173
174    private static final Tag TAG = new Tag("CaptureModule");
175    private static final String PHOTO_MODULE_STRING_ID = "PhotoModule";
176    /** Enable additional debug output. */
177    private static final boolean DEBUG = true;
178    /**
179     * This is the delay before we execute onResume tasks when coming from the
180     * lock screen, to allow time for onPause to execute.
181     * <p>
182     * TODO: Make sure this value is in sync with what we see on L.
183     */
184    private static final int ON_RESUME_TASKS_DELAY_MSEC = 20;
185
186    /** System Properties switch to enable debugging focus UI. */
187    private static final boolean FOCUS_DEBUG_UI = DebugPropertyHelper.showFocusDebugUI();
188
189    private final Object mDimensionLock = new Object();
190    /**
191     * Lock for race conditions in the SurfaceTextureListener callbacks.
192     */
193    private final Object mSurfaceLock = new Object();
194    /** Controller giving us access to other services. */
195    private final AppController mAppController;
196    /** The applications settings manager. */
197    private final SettingsManager mSettingsManager;
198    /** Application context. */
199    private final Context mContext;
200    private CaptureModuleUI mUI;
201    /** The camera manager used to open cameras. */
202    private OneCameraManager mCameraManager;
203    /** The currently opened camera device. */
204    private OneCamera mCamera;
205    /** The direction the currently opened camera is facing to. */
206    private Facing mCameraFacing = Facing.BACK;
207    /** The texture used to render the preview in. */
208    private SurfaceTexture mPreviewTexture;
209
210    /** State by the module state machine. */
211    private static enum ModuleState {
212        IDLE,
213        WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED,
214        UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE,
215    }
216
217    /** The current state of the module. */
218    private ModuleState mState = ModuleState.IDLE;
219    /** Current orientation of the device. */
220    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
221    /** Current zoom value. */
222    private final float mZoomValue = 1f;
223
224    /** True if in AF tap-to-focus sequence. */
225    private boolean mTapToFocusInProgress = false;
226
227    /** Persistence of Tap to Focus target UI after scan complete. */
228    private static final int FOCUS_HOLD_UI_MILLIS = 500;
229
230    /** Accelerometer data. */
231    private final float[] mGData = new float[3];
232    /** Magnetic sensor data. */
233    private final float[] mMData = new float[3];
234    /** Temporary rotation matrix. */
235    private final float[] mR = new float[16];
236    /** Current compass heading. */
237    private int mHeading = -1;
238
239    /** Whether the module is paused right now. */
240    private boolean mPaused;
241
242    /** Whether this module was resumed from lockscreen capture intent. */
243    private boolean mIsResumeFromLockScreen = false;
244
245    private final Runnable mResumeTaskRunnable = new Runnable() {
246        @Override
247        public void run() {
248            onResumeTasks();
249        }
250    };
251
252    /** Main thread handler. */
253    private Handler mMainHandler;
254
255    /** Current display rotation in degrees. */
256    private int mDisplayRotation;
257    /** Current screen width in pixels. */
258    private int mScreenWidth;
259    /** Current screen height in pixels. */
260    private int mScreenHeight;
261    /** Current width of preview frames from camera. */
262    private int mPreviewBufferWidth;
263    /** Current height of preview frames from camera.. */
264    private int mPreviewBufferHeight;
265    /** Area used by preview. */
266    RectF mPreviewArea;
267
268    /** The current preview transformation matrix. */
269    private Matrix mPreviewTranformationMatrix = new Matrix();
270    /** TODO: This is N5 specific. */
271    public static final float FULLSCREEN_ASPECT_RATIO = 16 / 9f;
272
273    /** A directory to store debug information in during development. */
274    private final File mDebugDataDir;
275
276    /** CLEAN UP START */
277    // private SoundPool mSoundPool;
278    // private int mCaptureStartSoundId;
279    // private static final int NO_SOUND_STREAM = -999;
280    // private final int mCaptureStartSoundStreamId = NO_SOUND_STREAM;
281    // private int mCaptureDoneSoundId;
282    // private SoundClips.Player mSoundPlayer;
283    // private boolean mFirstLayout;
284    // private int[] mTargetFPSRanges;
285    // private float mZoomValue;
286    // private int mSensorOrientation;
287    // private int mLensFacing;
288    // private volatile float mMaxZoomRatio = 1.0f;
289    // private String mFlashMode;
290    /** CLEAN UP END */
291
292    /** Constructs a new capture module. */
293    public CaptureModule(AppController appController) {
294        super(appController);
295        mAppController = appController;
296        mContext = mAppController.getAndroidContext();
297        mSettingsManager = mAppController.getSettingsManager();
298        mSettingsManager.addListener(this);
299        mDebugDataDir = mContext.getExternalCacheDir();
300    }
301
302    @Override
303    public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
304        Log.d(TAG, "init");
305        mIsResumeFromLockScreen = isResumeFromLockscreen(activity);
306        mMainHandler = new Handler(activity.getMainLooper());
307        mCameraManager = mAppController.getCameraManager();
308        mDisplayRotation = CameraUtil.getDisplayRotation(mContext);
309        mCameraFacing = getFacingFromCameraId(mSettingsManager.getInteger(
310                mAppController.getModuleScope(),
311                Keys.KEY_CAMERA_ID));
312        mUI = new CaptureModuleUI(activity, this, mAppController.getModuleLayoutRoot(),
313                mLayoutListener);
314        mAppController.setPreviewStatusListener(mUI);
315        mPreviewTexture = mAppController.getCameraAppUI().getSurfaceTexture();
316        if (mPreviewTexture != null) {
317            initSurface(mPreviewTexture);
318        }
319    }
320
321    @Override
322    public void onShutterButtonFocus(boolean pressed) {
323        // TODO Auto-generated method stub
324    }
325
326    @Override
327    public void onShutterCoordinate(TouchCoordinate coord) {
328        // TODO Auto-generated method stub
329    }
330
331    @Override
332    public void onShutterButtonClick() {
333        // TODO: Add focusing.
334        if (mCamera == null) {
335            return;
336        }
337
338        // Set up the capture session.
339        long sessionTime = System.currentTimeMillis();
340        String title = CameraUtil.createJpegName(sessionTime);
341        CaptureSession session = getServices().getCaptureSessionManager()
342                .createNewSession(title, sessionTime, null);
343
344        // TODO: Add location.
345
346        // Set up the parameters for this capture.
347        PhotoCaptureParameters params = new PhotoCaptureParameters();
348        params.title = title;
349        params.callback = this;
350        params.orientation = getOrientation();
351        params.flashMode = getFlashModeFromSettings();
352        params.heading = mHeading;
353        params.debugDataFolder = mDebugDataDir;
354
355        // Take the picture.
356        mCamera.takePicture(params, session);
357    }
358
359    @Override
360    public void onPreviewAreaChanged(RectF previewArea) {
361        mPreviewArea = previewArea;
362        // mUI.updatePreviewAreaRect(previewArea);
363        // mUI.positionProgressOverlay(previewArea);
364    }
365
366    @Override
367    public void onSensorChanged(SensorEvent event) {
368        // This is literally the same as the GCamModule implementation.
369        int type = event.sensor.getType();
370        float[] data;
371        if (type == Sensor.TYPE_ACCELEROMETER) {
372            data = mGData;
373        } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
374            data = mMData;
375        } else {
376            Log.w(TAG, String.format("Unexpected sensor type %s", event.sensor.getName()));
377            return;
378        }
379        for (int i = 0; i < 3; i++) {
380            data[i] = event.values[i];
381        }
382        float[] orientation = new float[3];
383        SensorManager.getRotationMatrix(mR, null, mGData, mMData);
384        SensorManager.getOrientation(mR, orientation);
385        mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
386        if (mHeading < 0) {
387            mHeading += 360;
388        }
389    }
390
391    @Override
392    public void onAccuracyChanged(Sensor sensor, int accuracy) {
393        // TODO Auto-generated method stub
394    }
395
396    @Override
397    public void onQueueStatus(boolean full) {
398        // TODO Auto-generated method stub
399    }
400
401    @Override
402    public void onRemoteShutterPress() {
403        // TODO: Check whether shutter is enabled.
404        onShutterButtonClick();
405    }
406
407    @Override
408    public void onSurfaceTextureAvailable(final SurfaceTexture surface, int width, int height) {
409        Log.d(TAG, "onSurfaceTextureAvailable");
410        // Force to re-apply transform matrix here as a workaround for
411        // b/11168275
412        updatePreviewTransform(width, height, true);
413        initSurface(surface);
414    }
415
416    public void initSurface(final SurfaceTexture surface) {
417        mPreviewTexture = surface;
418        closeCamera();
419
420        mCameraManager.open(mCameraFacing, getPictureSizeFromSettings(), new OpenCallback() {
421            @Override
422            public void onFailure() {
423                Log.e(TAG, "Could not open camera.");
424                mCamera = null;
425                mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
426            }
427
428            @Override
429            public void onCameraOpened(final OneCamera camera) {
430                Log.d(TAG, "onCameraOpened: " + camera);
431                mCamera = camera;
432                updateBufferDimension();
433
434                // If the surface texture is not destroyed, it may have the last
435                // frame lingering.
436                // We need to hold off setting transform until preview is
437                // started.
438                resetDefaultBufferSize();
439                mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED;
440
441                Log.d(TAG, "starting preview ...");
442
443                // TODO: Consider rolling these two calls into one.
444                camera.startPreview(new Surface(surface), new CaptureReadyCallback() {
445
446                    @Override
447                    public void onSetupFailed() {
448                        Log.e(TAG, "Could not set up preview.");
449                        mCamera.close(null);
450                        mCamera = null;
451                        // TODO: Show an error message and exit.
452                    }
453
454                    @Override
455                    public void onReadyForCapture() {
456                        Log.d(TAG, "Ready for capture.");
457                        onPreviewStarted();
458                        mCamera.setFocusStateListener(CaptureModule.this);
459                        mCamera.setReadyStateChangedListener(CaptureModule.this);
460                    }
461                });
462            }
463        });
464    }
465
466    @Override
467    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
468        Log.d(TAG, "onSurfaceTextureSizeChanged");
469        resetDefaultBufferSize();
470    }
471
472    @Override
473    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
474        Log.d(TAG, "onSurfaceTextureDestroyed");
475        closeCamera();
476        return true;
477    }
478
479    @Override
480    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
481        if (mState == ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE) {
482            Log.d(TAG, "onSurfaceTextureUpdated --> updatePreviewTransform");
483            mState = ModuleState.IDLE;
484            CameraAppUI appUI = mAppController.getCameraAppUI();
485            updatePreviewTransform(appUI.getSurfaceWidth(), appUI.getSurfaceHeight(), true);
486        }
487    }
488
489    @Override
490    public String getModuleStringIdentifier() {
491        return PHOTO_MODULE_STRING_ID;
492    }
493
494    @Override
495    public void resume() {
496        // Add delay on resume from lock screen only, in order to to speed up
497        // the onResume --> onPause --> onResume cycle from lock screen.
498        // Don't do always because letting go of thread can cause delay.
499        if (mIsResumeFromLockScreen) {
500            Log.v(TAG, "Delayng onResumeTasks from lock screen. " + System.currentTimeMillis());
501            // Note: onPauseAfterSuper() will delete this runnable, so we will
502            // at most have 1 copy queued up.
503            mMainHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC);
504        } else {
505            onResumeTasks();
506        }
507    }
508
509    private void onResumeTasks() {
510        Log.d(TAG, "onResumeTasks + " + System.currentTimeMillis());
511        mPaused = false;
512        mAppController.getCameraAppUI().onChangeCamera();
513        mAppController.addPreviewAreaSizeChangedListener(this);
514        resetDefaultBufferSize();
515        getServices().getRemoteShutterListener().onModuleReady(this);
516        // TODO: Check if we can really take a photo right now (memory, camera
517        // state, ... ).
518        mAppController.setShutterEnabled(true);
519    }
520
521    @Override
522    public void pause() {
523        mPaused = true;
524        resetTextureBufferSize();
525        closeCamera();
526        // Remove delayed resume trigger, if it hasn't been executed yet.
527        mMainHandler.removeCallbacksAndMessages(null);
528    }
529
530    @Override
531    public void destroy() {
532    }
533
534    @Override
535    public void onLayoutOrientationChanged(boolean isLandscape) {
536        Log.d(TAG, "onLayoutOrientationChanged");
537    }
538
539    @Override
540    public void onOrientationChanged(int orientation) {
541        // We keep the last known orientation. So if the user first orient
542        // the camera then point the camera to floor or sky, we still have
543        // the correct orientation.
544        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
545            return;
546        }
547        mOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
548    }
549
550    @Override
551    public void onCameraAvailable(CameraProxy cameraProxy) {
552        // Ignore since we manage the camera ourselves until we remove this.
553    }
554
555    @Override
556    public void hardResetSettings(SettingsManager settingsManager) {
557        // TODO Auto-generated method stub
558    }
559
560    @Override
561    public HardwareSpec getHardwareSpec() {
562        return new HardwareSpec() {
563            @Override
564            public boolean isFrontCameraSupported() {
565                return true;
566            }
567
568            @Override
569            public boolean isHdrSupported() {
570                return false;
571            }
572
573            @Override
574            public boolean isHdrPlusSupported() {
575                // TODO: Enable once we support this.
576                return false;
577            }
578
579            @Override
580            public boolean isFlashSupported() {
581                return true;
582            }
583        };
584    }
585
586    @Override
587    public BottomBarUISpec getBottomBarSpec() {
588        CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
589        bottomBarSpec.enableGridLines = true;
590        bottomBarSpec.enableCamera = true;
591        bottomBarSpec.cameraCallback = mCameraSwitchCallback;
592        // TODO: Enable once we support this.
593        bottomBarSpec.enableHdr = false;
594        // TODO: Enable once we support this.
595        bottomBarSpec.hdrCallback = null;
596        // TODO: Enable once we support this.
597        bottomBarSpec.enableSelfTimer = false;
598        bottomBarSpec.showSelfTimer = false;
599        // TODO: Deal with e.g. HDR+ if it doesn't support it.
600        bottomBarSpec.enableFlash = true;
601        return bottomBarSpec;
602    }
603
604    @Override
605    public boolean isUsingBottomBar() {
606        return true;
607    }
608
609    @Override
610    public boolean onKeyDown(int keyCode, KeyEvent event) {
611        return false;
612    }
613
614    @Override
615    public boolean onKeyUp(int keyCode, KeyEvent event) {
616        return false;
617    }
618
619    /**
620     * Focus sequence starts for zone around tap location for single tap.
621     */
622    @Override
623    public void onSingleTapUp(View view, int x, int y) {
624        Log.v(TAG, "onSingleTapUp x=" + x + " y=" + y);
625        // TODO: This should query actual capability.
626        if (mCameraFacing == Facing.FRONT) {
627            return;
628        }
629        triggerFocusAtScreenCoord(x, y);
630    }
631
632    // TODO: Consider refactoring FocusOverlayManager.
633    // Currently AF state transitions are controlled in OneCameraImpl.
634    // PhotoModule uses FocusOverlayManager which uses API1/portability
635    // logic and coordinates.
636
637    private void triggerFocusAtScreenCoord(int x, int y) {
638        mTapToFocusInProgress = true;
639        // Show UI immediately even though scan has not started yet.
640        mUI.setAutoFocusTarget(x, y);
641        mUI.showAutoFocusInProgress();
642
643        // Normalize coordinates to [0,1] per CameraOne API.
644        float points[] = new float[2];
645        points[0] = (x - mPreviewArea.left) / mPreviewArea.width();
646        points[1] = (y - mPreviewArea.top) / mPreviewArea.height();
647
648        // Rotate coordinates to portrait orientation per CameraOne API.
649        Matrix rotationMatrix = new Matrix();
650        rotationMatrix.setRotate(mDisplayRotation, 0.5f, 0.5f);
651        rotationMatrix.mapPoints(points);
652        mCamera.triggerFocusAndMeterAtPoint(points[0], points[1]);
653
654        // Log touch (screen coordinates).
655        if (mZoomValue == 1f) {
656            TouchCoordinate touchCoordinate = new TouchCoordinate(x - mPreviewArea.left,
657                    y - mPreviewArea.top, mPreviewArea.width(), mPreviewArea.height());
658            // TODO: Add to logging: duration, rotation.
659            UsageStatistics.instance().tapToFocus(touchCoordinate, null);
660        }
661    }
662
663    /**
664     * This AF status listener does two things:
665     * <ol>
666     * <li>Ends tap-to-focus period when mode goes from AUTO to CONTINUOUS_PICTURE.</li>
667     * <li>Updates AF UI if tap-to-focus is not in progress.</li>
668     * </ol>
669     */
670    @Override
671    public void onFocusStatusUpdate(final AutoFocusMode mode, final AutoFocusState state) {
672        Log.v(TAG, "AF status is mode:" + mode + " state:" + state);
673
674        if (FOCUS_DEBUG_UI) {
675            // TODO: Add debug circle radius+color UI to FocusOverlay.
676            // mMainHandler.post(...)
677        }
678
679        // If mTapToFocusInProgress, clear UI.
680        if (mTapToFocusInProgress) {
681            // Clear UI on return to CONTINUOUS_PICTURE (debug mode).
682            if (FOCUS_DEBUG_UI) {
683                if (mode == AutoFocusMode.CONTINUOUS_PICTURE) {
684                    mTapToFocusInProgress = false;
685                    mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
686                    mMainHandler.post(mHideAutoFocusTargetRunnable);
687                }
688            } else { // Clear UI FOCUS_HOLD_UI_MILLIS after scan end (normal).
689                if (mode == AutoFocusMode.AUTO && (state == AutoFocusState.STOPPED_FOCUSED ||
690                        state == AutoFocusState.STOPPED_UNFOCUSED)) {
691                    mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
692                    mMainHandler.postDelayed(new Runnable() {
693                        @Override
694                        public void run() {
695                            mTapToFocusInProgress = false;
696                            mMainHandler.post(mHideAutoFocusTargetRunnable);
697                        }
698                    }, FOCUS_HOLD_UI_MILLIS);
699                }
700            }
701        }
702
703        // Use the OneCamera auto focus callbacks to show the UI, except for
704        // tap to focus where we show UI right away at touch, and then turn
705        // it off early at 0.5 sec, before the focus lock expires at 3 sec.
706        if (!mTapToFocusInProgress) {
707            switch (state) {
708                case SCANNING:
709                    mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
710                    mMainHandler.post(mShowAutoFocusTargetInCenterRunnable);
711                    break;
712                case STOPPED_FOCUSED:
713                case STOPPED_UNFOCUSED:
714                    mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
715                    mMainHandler.post(mHideAutoFocusTargetRunnable);
716                    break;
717            }
718        }
719    }
720
721    @Override
722    public void onReadyStateChanged(boolean readyForCapture) {
723        mAppController.setShutterEnabled(readyForCapture);
724    }
725
726    @Override
727    public String getPeekAccessibilityString() {
728        return mAppController.getAndroidContext()
729                .getResources().getString(R.string.photo_accessibility_peek);
730    }
731
732    @Override
733    public void onThumbnailResult(Bitmap bitmap) {
734        // TODO
735    }
736
737    @Override
738    public void onPictureTaken(CaptureSession session) {
739    }
740
741    @Override
742    public void onPictureSaved(Uri uri) {
743        mAppController.notifyNewMedia(uri);
744    }
745
746    @Override
747    public void onTakePictureProgress(int progressPercent) {
748        // TODO once we have HDR+ hooked up.
749    }
750
751    @Override
752    public void onPictureTakenFailed() {
753    }
754
755    @Override
756    public void onSettingChanged(SettingsManager settingsManager, String key) {
757        // TODO Auto-generated method stub
758    }
759
760    /**
761     * Updates the preview transform matrix to adapt to the current preview
762     * width, height, and orientation.
763     */
764    public void updatePreviewTransform() {
765        int width;
766        int height;
767        synchronized (mDimensionLock) {
768            width = mScreenWidth;
769            height = mScreenHeight;
770        }
771        updatePreviewTransform(width, height);
772    }
773
774    /**
775     * Called when the preview started. Informs the app controller and queues a
776     * transform update when the next preview frame arrives.
777     */
778    private void onPreviewStarted() {
779        if (mState == ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED) {
780            mState = ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE;
781        }
782        mAppController.onPreviewStarted();
783    }
784
785    /**
786     * Update the preview transform based on the new dimensions. Will not force
787     * an update, if it's not necessary.
788     */
789    private void updatePreviewTransform(int incomingWidth, int incomingHeight) {
790        updatePreviewTransform(incomingWidth, incomingHeight, false);
791    }
792
793    /***
794     * Update the preview transform based on the new dimensions.
795     * TODO: Make work with all: aspect ratios/resolutions x screens/cameras.
796     */
797    private void updatePreviewTransform(int incomingWidth, int incomingHeight,
798            boolean forceUpdate) {
799        Log.d(TAG, "updatePreviewTransform: " + incomingWidth + " x " + incomingHeight);
800
801        synchronized (mDimensionLock) {
802            int incomingRotation = CameraUtil
803                    .getDisplayRotation(mContext);
804            // Check for an actual change:
805            if (mScreenHeight == incomingHeight && mScreenWidth == incomingWidth &&
806                    incomingRotation == mDisplayRotation && !forceUpdate) {
807                return;
808            }
809            // Update display rotation and dimensions
810            mDisplayRotation = incomingRotation;
811            mScreenWidth = incomingWidth;
812            mScreenHeight = incomingHeight;
813            updateBufferDimension();
814
815            mPreviewTranformationMatrix = mAppController.getCameraAppUI().getPreviewTransform(
816                    mPreviewTranformationMatrix);
817            int width = mScreenWidth;
818            int height = mScreenHeight;
819
820            // Assumptions:
821            // - Aspect ratio for the sensor buffers is in landscape
822            // orientation,
823            // - Dimensions of buffers received are rotated to the natural
824            // device orientation.
825            // - The contents of each buffer are rotated by the inverse of
826            // the display rotation.
827            // - Surface scales the buffer to fit the current view bounds.
828
829            // Get natural orientation and buffer dimensions
830            int naturalOrientation = CaptureModuleUtil
831                    .getDeviceNaturalOrientation(mContext);
832            int effectiveWidth = mPreviewBufferWidth;
833            int effectiveHeight = mPreviewBufferHeight;
834
835            if (DEBUG) {
836                Log.v(TAG, "Rotation: " + mDisplayRotation);
837                Log.v(TAG, "Screen Width: " + mScreenWidth);
838                Log.v(TAG, "Screen Height: " + mScreenHeight);
839                Log.v(TAG, "Buffer width: " + mPreviewBufferWidth);
840                Log.v(TAG, "Buffer height: " + mPreviewBufferHeight);
841                Log.v(TAG, "Natural orientation: " + naturalOrientation);
842            }
843
844            // If natural orientation is portrait, rotate the buffer
845            // dimensions
846            if (naturalOrientation == Configuration.ORIENTATION_PORTRAIT) {
847                int temp = effectiveWidth;
848                effectiveWidth = effectiveHeight;
849                effectiveHeight = temp;
850            }
851
852            // Find and center view rect and buffer rect
853            RectF viewRect = new RectF(0, 0, width, height);
854            RectF bufRect = new RectF(0, 0, effectiveWidth, effectiveHeight);
855            float centerX = viewRect.centerX();
856            float centerY = viewRect.centerY();
857            bufRect.offset(centerX - bufRect.centerX(), centerY - bufRect.centerY());
858
859            // Undo ScaleToFit.FILL done by the surface
860            mPreviewTranformationMatrix.setRectToRect(viewRect, bufRect, Matrix.ScaleToFit.FILL);
861
862            // Rotate buffer contents to proper orientation
863            mPreviewTranformationMatrix.postRotate(getPreviewOrientation(mDisplayRotation),
864                    centerX, centerY);
865
866            // TODO: This is probably only working for the N5. Need to test
867            // on a device like N10 with different sensor orientation.
868            if ((mDisplayRotation % 180) == 90) {
869                int temp = effectiveWidth;
870                effectiveWidth = effectiveHeight;
871                effectiveHeight = temp;
872            }
873
874            boolean is16by9 = false;
875
876            // TODO: BACK/FRONT.
877            Size pictureSize = getPictureSizeFromSettings();
878            if (pictureSize != null) {
879                pictureSize = ResolutionUtil.getApproximateSize(pictureSize);
880                if (pictureSize.equals(new Size(16, 9))) {
881                    is16by9 = true;
882                }
883            }
884
885            float scale;
886            if (is16by9) {
887                // We are going to be clipping off edges to achieve the 16
888                // by 9 aspect ratio so we will choose the max here to fill,
889                // instead of fit.
890                scale =
891                        Math.max(width / (float) effectiveWidth, height
892                                / (float) effectiveHeight);
893            } else {
894                // Scale to fit view, cropping the longest dimension
895                scale =
896                        Math.min(width / (float) effectiveWidth, height
897                                / (float) effectiveHeight);
898            }
899            mPreviewTranformationMatrix.postScale(scale, scale, centerX, centerY);
900
901            // TODO: Take these quantities from mPreviewArea.
902            float previewWidth = effectiveWidth * scale;
903            float previewHeight = effectiveHeight * scale;
904            float previewCenterX = previewWidth / 2;
905            float previewCenterY = previewHeight / 2;
906            mPreviewTranformationMatrix.postTranslate(previewCenterX - centerX, previewCenterY
907                    - centerY);
908
909            if (is16by9) {
910                float aspectRatio = FULLSCREEN_ASPECT_RATIO;
911                RectF renderedPreviewRect = mAppController.getFullscreenRect();
912                float desiredPreviewWidth = Math.max(renderedPreviewRect.height(),
913                        renderedPreviewRect.width()) * 1 / aspectRatio;
914                int letterBoxWidth = (int) Math.ceil((Math.min(renderedPreviewRect.width(),
915                        renderedPreviewRect.height()) - desiredPreviewWidth) / 2.0f);
916                mAppController.getCameraAppUI().addLetterboxing(letterBoxWidth);
917
918                float wOffset = -(previewWidth - renderedPreviewRect.width()) / 2.0f;
919                float hOffset = -(previewHeight - renderedPreviewRect.height()) / 2.0f;
920                mPreviewTranformationMatrix.postTranslate(wOffset, hOffset);
921                mAppController.updatePreviewTransformFullscreen(mPreviewTranformationMatrix,
922                        aspectRatio);
923            } else {
924                mAppController.updatePreviewTransform(mPreviewTranformationMatrix);
925                mAppController.getCameraAppUI().hideLetterboxing();
926            }
927            // if (mGcamProxy != null) {
928            // mGcamProxy.postSetAspectRatio(mFinalAspectRatio);
929            // }
930            // mUI.updatePreviewAreaRect(new RectF(0, 0, previewWidth,
931            // previewHeight));
932
933            // TODO: Add face detection.
934            // Characteristics info =
935            // mapp.getCameraProvider().getCharacteristics(0);
936            // mUI.setupFaceDetection(CameraUtil.getDisplayOrientation(incomingRotation,
937            // info), false);
938            // updateCamera2FaceBoundTransform(new
939            // RectF(mEffectiveCropRegion),
940            // new RectF(0, 0, mBufferWidth, mBufferHeight),
941            // new RectF(0, 0, previewWidth, previewHeight), getRotation());
942        }
943    }
944
945    private void updateBufferDimension() {
946        if (mCamera == null) {
947            return;
948        }
949
950        Size picked = CaptureModuleUtil.pickBufferDimensions(
951                mCamera.getSupportedSizes(),
952                mCamera.getFullSizeAspectRatio(),
953                mContext);
954        mPreviewBufferWidth = picked.getWidth();
955        mPreviewBufferHeight = picked.getHeight();
956    }
957
958    /**
959     * Resets the default buffer size to the initially calculated size.
960     */
961    private void resetDefaultBufferSize() {
962        synchronized (mSurfaceLock) {
963            if (mPreviewTexture != null) {
964                mPreviewTexture.setDefaultBufferSize(mPreviewBufferWidth, mPreviewBufferHeight);
965            }
966        }
967    }
968
969    private void closeCamera() {
970        if (mCamera != null) {
971            mCamera.setFocusStateListener(null);
972            mCamera.close(null);
973            mCamera = null;
974        }
975    }
976
977    private int getOrientation() {
978        if (mAppController.isAutoRotateScreen()) {
979            return mDisplayRotation;
980        } else {
981            return mOrientation;
982        }
983    }
984
985    /**
986     * @return Whether we are resuming from within the lockscreen.
987     */
988    private static boolean isResumeFromLockscreen(Activity activity) {
989        String action = activity.getIntent().getAction();
990        return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action)
991        || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action));
992    }
993
994    private void switchCamera(Facing switchTo) {
995        if (mPaused || mCameraFacing == switchTo) {
996            return;
997        }
998        // TODO: Un-comment once we have timer back.
999        // cancelCountDown();
1000
1001        mAppController.freezeScreenUntilPreviewReady();
1002
1003        mCameraFacing = switchTo;
1004        initSurface(mPreviewTexture);
1005
1006        // TODO: Un-comment once we have focus back.
1007        // if (mFocusManager != null) {
1008        // mFocusManager.removeMessages();
1009        // }
1010        // mFocusManager.setMirror(mMirror);
1011    }
1012
1013    private Size getPictureSizeFromSettings() {
1014        String pictureSizeKey = mCameraFacing == Facing.FRONT ? Keys.KEY_PICTURE_SIZE_FRONT
1015                : Keys.KEY_PICTURE_SIZE_BACK;
1016        return mSettingsManager.getSize(SettingsManager.SCOPE_GLOBAL, pictureSizeKey);
1017    }
1018
1019    private int getPreviewOrientation(int deviceOrientationDegrees) {
1020        // Important: Camera2 buffers are already rotated to the natural
1021        // orientation of the device (at least for the back-camera).
1022
1023        // TODO: Remove this hack for the front camera as soon as b/16637957 is
1024        // fixed.
1025        if (mCameraFacing == Facing.FRONT) {
1026            deviceOrientationDegrees += 180;
1027        }
1028        return (360 - deviceOrientationDegrees) % 360;
1029    }
1030
1031    /**
1032     * Returns which way around the camera is facing, based on it's ID.
1033     * <p>
1034     * TODO: This needs to change so that we store the direction directly in the
1035     * settings, rather than a Camera ID.
1036     */
1037    private static Facing getFacingFromCameraId(int cameraId) {
1038        return cameraId == 1 ? Facing.FRONT : Facing.BACK;
1039    }
1040
1041    private void resetTextureBufferSize() {
1042        // Reset the default buffer sizes on the shared SurfaceTexture
1043        // so they are not scaled for gcam.
1044        //
1045        // According to the documentation for
1046        // SurfaceTexture.setDefaultBufferSize,
1047        // photo and video based image producers (presumably only Camera 1 api),
1048        // override this buffer size. Any module that uses egl to render to a
1049        // SurfaceTexture must have these buffer sizes reset manually. Otherwise
1050        // the SurfaceTexture cannot be transformed by matrix set on the
1051        // TextureView.
1052        if (mPreviewTexture != null) {
1053            mPreviewTexture.setDefaultBufferSize(mAppController.getCameraAppUI().getSurfaceWidth(),
1054                    mAppController.getCameraAppUI().getSurfaceHeight());
1055        }
1056    }
1057
1058    /**
1059     * @return The currently set Flash settings. Defaults to AUTO if the setting
1060     *         could not be parsed.
1061     */
1062    private Flash getFlashModeFromSettings() {
1063        String flashSetting = mSettingsManager.getString(mAppController.getCameraScope(),
1064                Keys.KEY_FLASH_MODE);
1065        try {
1066            return Flash.valueOf(flashSetting.toUpperCase());
1067        } catch (IllegalArgumentException ex) {
1068            Log.w(TAG, "Could not parse Flash Setting. Defaulting to AUTO.");
1069            return Flash.AUTO;
1070        }
1071    }
1072}
1073