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