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