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