CaptureModule.java revision 9d3ec0396b71d7b9068f83b2e820eee988df8215
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    /** Worst case persistence of TTF target UI. */
206    private static final int FOCUS_UI_TIMEOUT_MILLIS = 2000;
207    /** Accelerometer data. */
208    private final float[] mGData = new float[3];
209    /** Magnetic sensor data. */
210    private final float[] mMData = new float[3];
211    /** Temporary rotation matrix. */
212    private final float[] mR = new float[16];
213    /** Current compass heading. */
214    private int mHeading = -1;
215
216    /** Whether the module is paused right now. */
217    private boolean mPaused;
218
219    /** Whether this module was resumed from lockscreen capture intent. */
220    private boolean mIsResumeFromLockScreen = false;
221
222    private final Runnable mResumeTaskRunnable = new Runnable() {
223        @Override
224        public void run() {
225            onResumeTasks();
226        }
227    };
228
229    /** Main thread handler. */
230    private Handler mMainHandler;
231
232    /** Current display rotation in degrees. */
233    private int mDisplayRotation;
234    /** Current screen width in pixels. */
235    private int mScreenWidth;
236    /** Current screen height in pixels. */
237    private int mScreenHeight;
238    /** Current width of preview frames from camera. */
239    private int mPreviewBufferWidth;
240    /** Current height of preview frames from camera.. */
241    private int mPreviewBufferHeight;
242    /** Area used by preview. */
243    RectF mPreviewArea;
244
245    /** The current preview transformation matrix. */
246    private Matrix mPreviewTranformationMatrix = new Matrix();
247    /** TODO: This is N5 specific. */
248    public static final float FULLSCREEN_ASPECT_RATIO = 16 / 9f;
249
250    /** A directory to store debug information in during development. */
251    private final File mDebugDataDir;
252
253    /** CLEAN UP START */
254    // private SoundPool mSoundPool;
255    // private int mCaptureStartSoundId;
256    // private static final int NO_SOUND_STREAM = -999;
257    // private final int mCaptureStartSoundStreamId = NO_SOUND_STREAM;
258    // private int mCaptureDoneSoundId;
259    // private SoundClips.Player mSoundPlayer;
260    // private boolean mFirstLayout;
261    // private int[] mTargetFPSRanges;
262    // private float mZoomValue;
263    // private int mSensorOrientation;
264    // private int mLensFacing;
265    // private String mFlashMode;
266    /** CLEAN UP END */
267
268    public CaptureModule(AppController appController) {
269        this(appController, false);
270    }
271
272    /** Constructs a new capture module. */
273    public CaptureModule(AppController appController, boolean stickyHdr) {
274        super(appController);
275        mAppController = appController;
276        mContext = mAppController.getAndroidContext();
277        mSettingsManager = mAppController.getSettingsManager();
278        mSettingsManager.addListener(this);
279        mDebugDataDir = mContext.getExternalCacheDir();
280        mStickyGcamCamera = stickyHdr;
281        // TODO: Read HDR setting from user preferences.
282        mHdrEnabled = stickyHdr;
283    }
284
285    @Override
286    public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
287        Log.d(TAG, "init");
288        mIsResumeFromLockScreen = isResumeFromLockscreen(activity);
289        mMainHandler = new Handler(activity.getMainLooper());
290        mCameraManager = mAppController.getCameraManager();
291        mDisplayRotation = CameraUtil.getDisplayRotation(mContext);
292        mCameraFacing = getFacingFromCameraId(mSettingsManager.getInteger(
293                mAppController.getModuleScope(),
294                Keys.KEY_CAMERA_ID));
295        mUI = new CaptureModuleUI(activity, this, mAppController.getModuleLayoutRoot(),
296                mLayoutListener);
297        mAppController.setPreviewStatusListener(mUI);
298        mPreviewTexture = mAppController.getCameraAppUI().getSurfaceTexture();
299        if (mPreviewTexture != null) {
300            initSurface(mPreviewTexture);
301        }
302    }
303
304    @Override
305    public void onShutterButtonFocus(boolean pressed) {
306        // TODO Auto-generated method stub
307    }
308
309    @Override
310    public void onShutterCoordinate(TouchCoordinate coord) {
311        // TODO Auto-generated method stub
312    }
313
314    @Override
315    public void onShutterButtonClick() {
316        // TODO: Add focusing.
317        if (mCamera == null) {
318            return;
319        }
320
321        // Set up the capture session.
322        long sessionTime = System.currentTimeMillis();
323        String title = CameraUtil.createJpegName(sessionTime);
324        CaptureSession session = getServices().getCaptureSessionManager()
325                .createNewSession(title, sessionTime, null);
326
327        // TODO: Add location.
328
329        // Set up the parameters for this capture.
330        PhotoCaptureParameters params = new PhotoCaptureParameters();
331        params.title = title;
332        params.callback = this;
333        params.orientation = getOrientation();
334        params.flashMode = getFlashModeFromSettings();
335        params.heading = mHeading;
336        params.debugDataFolder = mDebugDataDir;
337
338        // Take the picture.
339        mCamera.takePicture(params, session);
340    }
341
342    @Override
343    public void onPreviewAreaChanged(RectF previewArea) {
344        mPreviewArea = previewArea;
345        // mUI.updatePreviewAreaRect(previewArea);
346        // mUI.positionProgressOverlay(previewArea);
347    }
348
349    @Override
350    public void onSensorChanged(SensorEvent event) {
351        // This is literally the same as the GCamModule implementation.
352        int type = event.sensor.getType();
353        float[] data;
354        if (type == Sensor.TYPE_ACCELEROMETER) {
355            data = mGData;
356        } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
357            data = mMData;
358        } else {
359            Log.w(TAG, String.format("Unexpected sensor type %s", event.sensor.getName()));
360            return;
361        }
362        for (int i = 0; i < 3; i++) {
363            data[i] = event.values[i];
364        }
365        float[] orientation = new float[3];
366        SensorManager.getRotationMatrix(mR, null, mGData, mMData);
367        SensorManager.getOrientation(mR, orientation);
368        mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
369        if (mHeading < 0) {
370            mHeading += 360;
371        }
372    }
373
374    @Override
375    public void onAccuracyChanged(Sensor sensor, int accuracy) {
376        // TODO Auto-generated method stub
377    }
378
379    @Override
380    public void onQueueStatus(boolean full) {
381        // TODO Auto-generated method stub
382    }
383
384    @Override
385    public void onRemoteShutterPress() {
386        // TODO: Check whether shutter is enabled.
387        onShutterButtonClick();
388    }
389
390    @Override
391    public void onSurfaceTextureAvailable(final SurfaceTexture surface, int width, int height) {
392        Log.d(TAG, "onSurfaceTextureAvailable");
393        // Force to re-apply transform matrix here as a workaround for
394        // b/11168275
395        updatePreviewTransform(width, height, true);
396        initSurface(surface);
397    }
398
399    public void initSurface(final SurfaceTexture surface) {
400        mPreviewTexture = surface;
401        closeCamera();
402
403        // Only enable HDR on the back camera
404        boolean useHdr = mHdrEnabled && mCameraFacing == Facing.BACK;
405        mCameraManager.open(mCameraFacing, useHdr, getPictureSizeFromSettings(),
406                new OpenCallback() {
407                    @Override
408                    public void onFailure() {
409                        Log.e(TAG, "Could not open camera.");
410                        mCamera = null;
411                        mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
412                    }
413
414                    @Override
415                    public void onCameraOpened(final OneCamera camera) {
416                        Log.d(TAG, "onCameraOpened: " + camera);
417                        mCamera = camera;
418                        updatePreviewBufferDimension();
419
420                        // If the surface texture is not destroyed, it may have
421                        // the last frame lingering. We need to hold off setting
422                        // transform until preview is started.
423                        resetDefaultBufferSize();
424                        mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED;
425                        Log.d(TAG, "starting preview ...");
426
427                        // TODO: Consider rolling these two calls into one.
428                        camera.startPreview(new Surface(surface), new CaptureReadyCallback() {
429
430                            @Override
431                            public void onSetupFailed() {
432                                Log.e(TAG, "Could not set up preview.");
433                                mCamera.close(null);
434                                mCamera = null;
435                                // TODO: Show an error message and exit.
436                            }
437
438                            @Override
439                            public void onReadyForCapture() {
440                                Log.d(TAG, "Ready for capture.");
441                                onPreviewStarted();
442                                // Enable zooming after preview has started.
443                                mUI.initializeZoom(mCamera.getMaxZoom());
444                                mCamera.setFocusStateListener(CaptureModule.this);
445                                mCamera.setReadyStateChangedListener(CaptureModule.this);
446                            }
447                        });
448                    }
449                });
450    }
451
452    @Override
453    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
454        Log.d(TAG, "onSurfaceTextureSizeChanged");
455        resetDefaultBufferSize();
456    }
457
458    @Override
459    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
460        Log.d(TAG, "onSurfaceTextureDestroyed");
461        closeCamera();
462        return true;
463    }
464
465    @Override
466    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
467        if (mState == ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE) {
468            Log.d(TAG, "onSurfaceTextureUpdated --> updatePreviewTransform");
469            mState = ModuleState.IDLE;
470            CameraAppUI appUI = mAppController.getCameraAppUI();
471            updatePreviewTransform(appUI.getSurfaceWidth(), appUI.getSurfaceHeight(), true);
472        }
473    }
474
475    @Override
476    public String getModuleStringIdentifier() {
477        return PHOTO_MODULE_STRING_ID;
478    }
479
480    @Override
481    public void resume() {
482        // Add delay on resume from lock screen only, in order to to speed up
483        // the onResume --> onPause --> onResume cycle from lock screen.
484        // Don't do always because letting go of thread can cause delay.
485        if (mIsResumeFromLockScreen) {
486            Log.v(TAG, "Delayng onResumeTasks from lock screen. " + System.currentTimeMillis());
487            // Note: onPauseAfterSuper() will delete this runnable, so we will
488            // at most have 1 copy queued up.
489            mMainHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC);
490        } else {
491            onResumeTasks();
492        }
493    }
494
495    private void onResumeTasks() {
496        Log.d(TAG, "onResumeTasks + " + System.currentTimeMillis());
497        mPaused = false;
498        mAppController.getCameraAppUI().onChangeCamera();
499        mAppController.addPreviewAreaSizeChangedListener(this);
500        resetDefaultBufferSize();
501        getServices().getRemoteShutterListener().onModuleReady(this);
502        // TODO: Check if we can really take a photo right now (memory, camera
503        // state, ... ).
504        mAppController.getCameraAppUI().enableModeOptions();
505        mAppController.setShutterEnabled(true);
506    }
507
508    @Override
509    public void pause() {
510        mPaused = true;
511        resetTextureBufferSize();
512        closeCamera();
513        // Remove delayed resume trigger, if it hasn't been executed yet.
514        mMainHandler.removeCallbacksAndMessages(null);
515    }
516
517    @Override
518    public void destroy() {
519    }
520
521    @Override
522    public void onLayoutOrientationChanged(boolean isLandscape) {
523        Log.d(TAG, "onLayoutOrientationChanged");
524    }
525
526    @Override
527    public void onOrientationChanged(int orientation) {
528        // We keep the last known orientation. So if the user first orient
529        // the camera then point the camera to floor or sky, we still have
530        // the correct orientation.
531        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
532            return;
533        }
534        mOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
535    }
536
537    @Override
538    public void onCameraAvailable(CameraProxy cameraProxy) {
539        // Ignore since we manage the camera ourselves until we remove this.
540    }
541
542    @Override
543    public void hardResetSettings(SettingsManager settingsManager) {
544        if (mStickyGcamCamera) {
545            // Sitcky HDR+ mode should hard reset HDR+ to on, and camera back
546            // facing.
547            settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, true);
548            settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,
549                    getBackFacingCameraId());
550        }
551    }
552
553    @Override
554    public HardwareSpec getHardwareSpec() {
555        return new HardwareSpec() {
556            @Override
557            public boolean isFrontCameraSupported() {
558                return true;
559            }
560
561            @Override
562            public boolean isHdrSupported() {
563                // TODO: Check if the device has HDR and not HDR+.
564                return false;
565            }
566
567            @Override
568            public boolean isHdrPlusSupported() {
569                return GcamHelper.hasGcamCapture();
570            }
571
572            @Override
573            public boolean isFlashSupported() {
574                return true;
575            }
576        };
577    }
578
579    @Override
580    public BottomBarUISpec getBottomBarSpec() {
581        CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
582        bottomBarSpec.enableGridLines = true;
583        bottomBarSpec.enableCamera = true;
584        bottomBarSpec.cameraCallback = getCameraCallback();
585        bottomBarSpec.enableHdr = GcamHelper.hasGcamCapture();
586        bottomBarSpec.hdrCallback = getHdrButtonCallback();
587        // TODO: Enable once we support this.
588        bottomBarSpec.enableSelfTimer = false;
589        bottomBarSpec.showSelfTimer = false;
590        // TODO: Deal with e.g. HDR+ if it doesn't support it.
591        // bottomBarSpec.enableFlash = true;
592        return bottomBarSpec;
593    }
594
595    @Override
596    public boolean isUsingBottomBar() {
597        return true;
598    }
599
600    @Override
601    public boolean onKeyDown(int keyCode, KeyEvent event) {
602        return false;
603    }
604
605    @Override
606    public boolean onKeyUp(int keyCode, KeyEvent event) {
607        return false;
608    }
609
610    /**
611     * Focus sequence starts for zone around tap location for single tap.
612     */
613    @Override
614    public void onSingleTapUp(View view, int x, int y) {
615        Log.v(TAG, "onSingleTapUp x=" + x + " y=" + y);
616        // TODO: This should query actual capability.
617        if (mCameraFacing == Facing.FRONT) {
618            return;
619        }
620        triggerFocusAtScreenCoord(x, y);
621    }
622
623    // TODO: Consider refactoring FocusOverlayManager.
624    // Currently AF state transitions are controlled in OneCameraImpl.
625    // PhotoModule uses FocusOverlayManager which uses API1/portability
626    // logic and coordinates.
627    private void triggerFocusAtScreenCoord(int x, int y) {
628        mTapToFocusInProgress = true;
629        // Show UI immediately even though scan has not started yet.
630        mUI.setAutoFocusTarget(x, y);
631        mUI.showAutoFocusInProgress();
632
633        // TODO: Consider removing after TTF implemented in all OneCameras.
634        mMainHandler.postDelayed(new Runnable() {
635            @Override
636            public void run() {
637                mTapToFocusInProgress = false;
638                mMainHandler.post(mHideAutoFocusTargetRunnable);
639            }
640        }, FOCUS_UI_TIMEOUT_MILLIS);
641
642        // Normalize coordinates to [0,1] per CameraOne API.
643        float points[] = new float[2];
644        points[0] = (x - mPreviewArea.left) / mPreviewArea.width();
645        points[1] = (y - mPreviewArea.top) / mPreviewArea.height();
646
647        // Rotate coordinates to portrait orientation per CameraOne API.
648        Matrix rotationMatrix = new Matrix();
649        rotationMatrix.setRotate(mDisplayRotation, 0.5f, 0.5f);
650        rotationMatrix.mapPoints(points);
651        mCamera.triggerFocusAndMeterAtPoint(points[0], points[1]);
652
653        // Log touch (screen coordinates).
654        if (mZoomValue == 1f) {
655            TouchCoordinate touchCoordinate = new TouchCoordinate(x - mPreviewArea.left,
656                    y - mPreviewArea.top, mPreviewArea.width(), mPreviewArea.height());
657            // TODO: Add to logging: duration, rotation.
658            UsageStatistics.instance().tapToFocus(touchCoordinate, null);
659        }
660    }
661
662    /**
663     * This AF status listener does two things:
664     * <ol>
665     * <li>Ends tap-to-focus period when mode goes from AUTO to
666     * 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 (CAPTURE_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 (CAPTURE_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        if (readyForCapture) {
724            mAppController.getCameraAppUI().enableModeOptions();
725        }
726        mAppController.setShutterEnabled(readyForCapture);
727    }
728
729    @Override
730    public String getPeekAccessibilityString() {
731        return mAppController.getAndroidContext()
732                .getResources().getString(R.string.photo_accessibility_peek);
733    }
734
735    @Override
736    public void onThumbnailResult(Bitmap bitmap) {
737        // TODO
738    }
739
740    @Override
741    public void onPictureTaken(CaptureSession session) {
742        mAppController.getCameraAppUI().enableModeOptions();
743        mAppController.getCameraAppUI().setShutterButtonEnabled(true);
744    }
745
746    @Override
747    public void onPictureSaved(Uri uri) {
748        mAppController.notifyNewMedia(uri);
749    }
750
751    @Override
752    public void onTakePictureProgress(float progress) {
753        mUI.setPictureTakingProgress((int) (progress * 100));
754    }
755
756    @Override
757    public void onPictureTakenFailed() {
758    }
759
760    @Override
761    public void onSettingChanged(SettingsManager settingsManager, String key) {
762        // TODO Auto-generated method stub
763    }
764
765    /**
766     * Updates the preview transform matrix to adapt to the current preview
767     * width, height, and orientation.
768     */
769    public void updatePreviewTransform() {
770        int width;
771        int height;
772        synchronized (mDimensionLock) {
773            width = mScreenWidth;
774            height = mScreenHeight;
775        }
776        updatePreviewTransform(width, height);
777    }
778
779    /**
780     * Set zoom value.
781     *
782     * @param zoom Zoom value, must be between 1.0 and mCamera.getMaxZoom().
783     */
784    public void setZoom(float zoom) {
785        mZoomValue = zoom;
786        if (mCamera != null) {
787            mCamera.setZoom(zoom);
788        }
789    }
790
791    /**
792     * TODO: Remove this method once we are in pure CaptureModule land.
793     */
794    private String getBackFacingCameraId() {
795        if (!(mCameraManager instanceof OneCameraManagerImpl)) {
796            throw new IllegalStateException("This should never be called with Camera API V1");
797        }
798        OneCameraManagerImpl manager = (OneCameraManagerImpl) mCameraManager;
799        return manager.getFirstBackCameraId();
800    }
801
802    /**
803     * @return Depending on whether we're in sticky-HDR mode or not, return the
804     *         proper callback to be used for when the HDR/HDR+ button is
805     *         pressed.
806     */
807    private ButtonManager.ButtonCallback getHdrButtonCallback() {
808        if (mStickyGcamCamera) {
809            return new ButtonManager.ButtonCallback() {
810                @Override
811                public void onStateChanged(int state) {
812                    if (mPaused) {
813                        return;
814                    }
815                    if (state == ButtonManager.ON) {
816                        throw new IllegalStateException(
817                                "Can't leave hdr plus mode if switching to hdr plus mode.");
818                    }
819                    SettingsManager settingsManager = mAppController.getSettingsManager();
820                    settingsManager.set(mAppController.getModuleScope(),
821                            Keys.KEY_REQUEST_RETURN_HDR_PLUS, false);
822                    switchToRegularCapture();
823                }
824            };
825        } else {
826            return new ButtonManager.ButtonCallback() {
827                @Override
828                public void onStateChanged(int hdrEnabled) {
829                    if (mPaused) {
830                        return;
831                    }
832                    Log.d(TAG, "HDR enabled =" + hdrEnabled);
833                    mHdrEnabled = hdrEnabled == 1;
834                    switchCamera();
835                }
836            };
837        }
838    }
839
840    /**
841     * @return Depending on whether we're in sticky-HDR mode or not, this
842     *         returns the proper callback to be used for when the camera
843     *         (front/back switch) button is pressed.
844     */
845    private ButtonManager.ButtonCallback getCameraCallback() {
846        if (mStickyGcamCamera) {
847            return new ButtonManager.ButtonCallback() {
848                @Override
849                public void onStateChanged(int state) {
850                    if (mPaused) {
851                        return;
852                    }
853
854                    // At the time this callback is fired, the camera id setting
855                    // has changed to the desired camera.
856                    SettingsManager settingsManager = mAppController.getSettingsManager();
857                    if (Keys.isCameraBackFacing(settingsManager,
858                            mAppController.getModuleScope())) {
859                        throw new IllegalStateException(
860                                "Hdr plus should never be switching from front facing camera.");
861                    }
862
863                    // Switch to photo mode, but request a return to hdr plus on
864                    // switching to back camera again.
865                    settingsManager.set(mAppController.getModuleScope(),
866                            Keys.KEY_REQUEST_RETURN_HDR_PLUS, true);
867                    switchToRegularCapture();
868                }
869            };
870        } else {
871            return new ButtonManager.ButtonCallback() {
872                @Override
873                public void onStateChanged(int cameraId) {
874                    if (mPaused) {
875                        return;
876                    }
877
878                    // At the time this callback is fired, the camera id
879                    // has be set to the desired camera.
880                    mSettingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,
881                            cameraId);
882
883                    Log.d(TAG, "Start to switch camera. cameraId=" + cameraId);
884                    mCameraFacing = getFacingFromCameraId(cameraId);
885                    switchCamera();
886                }
887            };
888        }
889    }
890
891    /**
892     * Switches to PhotoModule to do regular photo captures.
893     * <p>
894     * TODO: Remove this once we use CaptureModule for photo taking.
895     */
896    private void switchToRegularCapture() {
897        // Turn off HDR+ before switching back to normal photo mode.
898        SettingsManager settingsManager = mAppController.getSettingsManager();
899        settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false);
900
901        // Disable this button to prevent callbacks from this module from firing
902        // while we are transitioning modules.
903        ButtonManager buttonManager = mAppController.getButtonManager();
904        buttonManager.disableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
905        mAppController.getCameraAppUI().freezeScreenUntilPreviewReady();
906        mAppController.onModeSelected(mContext.getResources().getInteger(
907                R.integer.camera_mode_photo));
908        buttonManager.enableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
909    }
910
911    /**
912     * Called when the preview started. Informs the app controller and queues a
913     * transform update when the next preview frame arrives.
914     */
915    private void onPreviewStarted() {
916        if (mState == ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED) {
917            mState = ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE;
918        }
919        mAppController.onPreviewStarted();
920    }
921
922    /**
923     * Update the preview transform based on the new dimensions. Will not force
924     * an update, if it's not necessary.
925     */
926    private void updatePreviewTransform(int incomingWidth, int incomingHeight) {
927        updatePreviewTransform(incomingWidth, incomingHeight, false);
928    }
929
930    /***
931     * Update the preview transform based on the new dimensions. TODO: Make work
932     * with all: aspect ratios/resolutions x screens/cameras.
933     */
934    private void updatePreviewTransform(int incomingWidth, int incomingHeight,
935            boolean forceUpdate) {
936        Log.d(TAG, "updatePreviewTransform: " + incomingWidth + " x " + incomingHeight);
937
938        synchronized (mDimensionLock) {
939            int incomingRotation = CameraUtil
940                    .getDisplayRotation(mContext);
941            // Check for an actual change:
942            if (mScreenHeight == incomingHeight && mScreenWidth == incomingWidth &&
943                    incomingRotation == mDisplayRotation && !forceUpdate) {
944                return;
945            }
946            // Update display rotation and dimensions
947            mDisplayRotation = incomingRotation;
948            mScreenWidth = incomingWidth;
949            mScreenHeight = incomingHeight;
950            updatePreviewBufferDimension();
951
952            mPreviewTranformationMatrix = mAppController.getCameraAppUI().getPreviewTransform(
953                    mPreviewTranformationMatrix);
954            int width = mScreenWidth;
955            int height = mScreenHeight;
956
957            // Assumptions:
958            // - Aspect ratio for the sensor buffers is in landscape
959            // orientation,
960            // - Dimensions of buffers received are rotated to the natural
961            // device orientation.
962            // - The contents of each buffer are rotated by the inverse of
963            // the display rotation.
964            // - Surface scales the buffer to fit the current view bounds.
965
966            // Get natural orientation and buffer dimensions
967            int naturalOrientation = CaptureModuleUtil
968                    .getDeviceNaturalOrientation(mContext);
969            int effectiveWidth = mPreviewBufferWidth;
970            int effectiveHeight = mPreviewBufferHeight;
971
972            if (DEBUG) {
973                Log.v(TAG, "Rotation: " + mDisplayRotation);
974                Log.v(TAG, "Screen Width: " + mScreenWidth);
975                Log.v(TAG, "Screen Height: " + mScreenHeight);
976                Log.v(TAG, "Buffer width: " + mPreviewBufferWidth);
977                Log.v(TAG, "Buffer height: " + mPreviewBufferHeight);
978                Log.v(TAG, "Natural orientation: " + naturalOrientation);
979            }
980
981            // If natural orientation is portrait, rotate the buffer
982            // dimensions
983            if (naturalOrientation == Configuration.ORIENTATION_PORTRAIT) {
984                int temp = effectiveWidth;
985                effectiveWidth = effectiveHeight;
986                effectiveHeight = temp;
987            }
988
989            // Find and center view rect and buffer rect
990            RectF viewRect = new RectF(0, 0, width, height);
991            RectF bufRect = new RectF(0, 0, effectiveWidth, effectiveHeight);
992            float centerX = viewRect.centerX();
993            float centerY = viewRect.centerY();
994            bufRect.offset(centerX - bufRect.centerX(), centerY - bufRect.centerY());
995
996            // Undo ScaleToFit.FILL done by the surface
997            mPreviewTranformationMatrix.setRectToRect(viewRect, bufRect, Matrix.ScaleToFit.FILL);
998
999            // Rotate buffer contents to proper orientation
1000            mPreviewTranformationMatrix.postRotate(getPreviewOrientation(mDisplayRotation),
1001                    centerX, centerY);
1002
1003            // TODO: This is probably only working for the N5. Need to test
1004            // on a device like N10 with different sensor orientation.
1005            if ((mDisplayRotation % 180) == 90) {
1006                int temp = effectiveWidth;
1007                effectiveWidth = effectiveHeight;
1008                effectiveHeight = temp;
1009            }
1010
1011            // Scale to fit view, cropping the longest dimension
1012            float scale =
1013                    Math.min(width / (float) effectiveWidth, height
1014                            / (float) effectiveHeight);
1015            mPreviewTranformationMatrix.postScale(scale, scale, centerX, centerY);
1016
1017            // TODO: Take these quantities from mPreviewArea.
1018            float previewWidth = effectiveWidth * scale;
1019            float previewHeight = effectiveHeight * scale;
1020            float previewCenterX = previewWidth / 2;
1021            float previewCenterY = previewHeight / 2;
1022            mPreviewTranformationMatrix.postTranslate(previewCenterX - centerX, previewCenterY
1023                    - centerY);
1024
1025            mAppController.updatePreviewTransform(mPreviewTranformationMatrix);
1026            mAppController.getCameraAppUI().hideLetterboxing();
1027            // if (mGcamProxy != null) {
1028            // mGcamProxy.postSetAspectRatio(mFinalAspectRatio);
1029            // }
1030            // mUI.updatePreviewAreaRect(new RectF(0, 0, previewWidth,
1031            // previewHeight));
1032
1033            // TODO: Add face detection.
1034            // Characteristics info =
1035            // mapp.getCameraProvider().getCharacteristics(0);
1036            // mUI.setupFaceDetection(CameraUtil.getDisplayOrientation(incomingRotation,
1037            // info), false);
1038            // updateCamera2FaceBoundTransform(new
1039            // RectF(mEffectiveCropRegion),
1040            // new RectF(0, 0, mBufferWidth, mBufferHeight),
1041            // new RectF(0, 0, previewWidth, previewHeight), getRotation());
1042        }
1043    }
1044
1045    /**
1046     * Based on the current picture size, selects the best preview dimension and
1047     * stores it in {@link #mPreviewBufferWidth} and
1048     * {@link #mPreviewBufferHeight}.
1049     */
1050    private void updatePreviewBufferDimension() {
1051        if (mCamera == null) {
1052            return;
1053        }
1054
1055        Size pictureSize = getPictureSizeFromSettings();
1056        Size previewBufferSize = mCamera.pickPreviewSize(pictureSize, mContext);
1057        mPreviewBufferWidth = previewBufferSize.getWidth();
1058        mPreviewBufferHeight = previewBufferSize.getHeight();
1059    }
1060
1061    /**
1062     * Resets the default buffer size to the initially calculated size.
1063     */
1064    private void resetDefaultBufferSize() {
1065        synchronized (mSurfaceLock) {
1066            if (mPreviewTexture != null) {
1067                mPreviewTexture.setDefaultBufferSize(mPreviewBufferWidth, mPreviewBufferHeight);
1068            }
1069        }
1070    }
1071
1072    private void closeCamera() {
1073        if (mCamera != null) {
1074            mCamera.setFocusStateListener(null);
1075            mCamera.close(null);
1076            mCamera = null;
1077        }
1078    }
1079
1080    private int getOrientation() {
1081        if (mAppController.isAutoRotateScreen()) {
1082            return mDisplayRotation;
1083        } else {
1084            return mOrientation;
1085        }
1086    }
1087
1088    /**
1089     * @return Whether we are resuming from within the lockscreen.
1090     */
1091    private static boolean isResumeFromLockscreen(Activity activity) {
1092        String action = activity.getIntent().getAction();
1093        return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action)
1094        || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action));
1095    }
1096
1097    /**
1098     * Re-initialize the camera if e.g. the HDR mode or facing property changed.
1099     */
1100    private void switchCamera() {
1101        if (mPaused) {
1102            return;
1103        }
1104        // TODO: Un-comment once we have timer back.
1105        // cancelCountDown();
1106
1107        mAppController.freezeScreenUntilPreviewReady();
1108
1109        initSurface(mPreviewTexture);
1110
1111        // TODO: Un-comment once we have focus back.
1112        // if (mFocusManager != null) {
1113        // mFocusManager.removeMessages();
1114        // }
1115        // mFocusManager.setMirror(mMirror);
1116    }
1117
1118    private Size getPictureSizeFromSettings() {
1119        String pictureSizeKey = mCameraFacing == Facing.FRONT ? Keys.KEY_PICTURE_SIZE_FRONT
1120                : Keys.KEY_PICTURE_SIZE_BACK;
1121        return mSettingsManager.getSize(SettingsManager.SCOPE_GLOBAL, pictureSizeKey);
1122    }
1123
1124    private int getPreviewOrientation(int deviceOrientationDegrees) {
1125        // Important: Camera2 buffers are already rotated to the natural
1126        // orientation of the device (at least for the back-camera).
1127
1128        // TODO: Remove this hack for the front camera as soon as b/16637957 is
1129        // fixed.
1130        if (mCameraFacing == Facing.FRONT) {
1131            deviceOrientationDegrees += 180;
1132        }
1133        return (360 - deviceOrientationDegrees) % 360;
1134    }
1135
1136    /**
1137     * Returns which way around the camera is facing, based on it's ID.
1138     * <p>
1139     * TODO: This needs to change so that we store the direction directly in the
1140     * settings, rather than a Camera ID.
1141     */
1142    private static Facing getFacingFromCameraId(int cameraId) {
1143        return cameraId == 1 ? Facing.FRONT : Facing.BACK;
1144    }
1145
1146    private void resetTextureBufferSize() {
1147        // Reset the default buffer sizes on the shared SurfaceTexture
1148        // so they are not scaled for gcam.
1149        //
1150        // According to the documentation for
1151        // SurfaceTexture.setDefaultBufferSize,
1152        // photo and video based image producers (presumably only Camera 1 api),
1153        // override this buffer size. Any module that uses egl to render to a
1154        // SurfaceTexture must have these buffer sizes reset manually. Otherwise
1155        // the SurfaceTexture cannot be transformed by matrix set on the
1156        // TextureView.
1157        if (mPreviewTexture != null) {
1158            mPreviewTexture.setDefaultBufferSize(mAppController.getCameraAppUI().getSurfaceWidth(),
1159                    mAppController.getCameraAppUI().getSurfaceHeight());
1160        }
1161    }
1162
1163    /**
1164     * @return The currently set Flash settings. Defaults to AUTO if the setting
1165     *         could not be parsed.
1166     */
1167    private Flash getFlashModeFromSettings() {
1168        String flashSetting = mSettingsManager.getString(mAppController.getCameraScope(),
1169                Keys.KEY_FLASH_MODE);
1170        try {
1171            return Flash.valueOf(flashSetting.toUpperCase());
1172        } catch (IllegalArgumentException ex) {
1173            Log.w(TAG, "Could not parse Flash Setting. Defaulting to AUTO.");
1174            return Flash.AUTO;
1175        }
1176    }
1177}
1178