CaptureModule.java revision 0024b57e54c674d86ff15011867a0f44029e0c48
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.ContentResolver;
21import android.content.Context;
22import android.content.res.Configuration;
23import android.graphics.Bitmap;
24import android.graphics.Matrix;
25import android.graphics.RectF;
26import android.graphics.SurfaceTexture;
27import android.hardware.Sensor;
28import android.hardware.SensorEvent;
29import android.hardware.SensorEventListener;
30import android.hardware.SensorManager;
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;
40
41import com.android.camera.app.AppController;
42import com.android.camera.app.CameraAppUI;
43import com.android.camera.app.CameraAppUI.BottomBarUISpec;
44import com.android.camera.app.MediaSaver;
45import com.android.camera.debug.Log;
46import com.android.camera.debug.Log.Tag;
47import com.android.camera.hardware.HardwareSpec;
48import com.android.camera.module.ModuleController;
49import com.android.camera.one.OneCamera;
50import com.android.camera.one.OneCamera.CaptureReadyCallback;
51import com.android.camera.one.OneCamera.Facing;
52import com.android.camera.one.OneCamera.OpenCallback;
53import com.android.camera.one.OneCamera.PhotoCaptureParameters;
54import com.android.camera.one.OneCamera.PhotoCaptureParameters.Flash;
55import com.android.camera.one.OneCameraManager;
56import com.android.camera.remote.RemoteCameraModule;
57import com.android.camera.session.CaptureSession;
58import com.android.camera.settings.Keys;
59import com.android.camera.settings.ResolutionUtil;
60import com.android.camera.settings.SettingsManager;
61import com.android.camera.ui.PreviewStatusListener;
62import com.android.camera.ui.TouchCoordinate;
63import com.android.camera.util.CameraUtil;
64import com.android.camera.util.Size;
65import com.android.camera2.R;
66import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
67
68/**
69 * New Capture module that is made to support photo and video capture on top of
70 * the OneCamera API, to transparently support GCam.
71 * <p>
72 * This has been a re-write with pieces taken and improved from GCamModule and
73 * PhotoModule, which are to be retired eventually.
74 * <p>
75 * TODO:
76 * <ul>
77 * <li>Server-side logging
78 * <li>Focusing
79 * <li>Show location dialog
80 * <li>Show resolution dialog on certain devices
81 * <li>Store location
82 * <li>Timer
83 * <li>Capture intent
84 * </ul>
85 */
86public class CaptureModule extends CameraModule
87        implements MediaSaver.QueueListener,
88        ModuleController,
89        OneCamera.PictureCallback,
90        PreviewStatusListener.PreviewAreaChangedListener,
91        RemoteCameraModule,
92        SensorEventListener,
93        SettingsManager.OnSettingChangedListener,
94        TextureView.SurfaceTextureListener {
95
96    /**
97     * Called on layout changes.
98     */
99    private final OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
100        @Override
101        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
102                int oldTop, int oldRight, int oldBottom) {
103            int width = right - left;
104            int height = bottom - top;
105            updatePreviewTransform(width, height, false);
106        }
107    };
108
109    /**
110     * Called when the captured media has been saved.
111     */
112    private final MediaSaver.OnMediaSavedListener mOnMediaSavedListener =
113            new MediaSaver.OnMediaSavedListener() {
114                @Override
115                public void onMediaSaved(Uri uri) {
116                    if (uri != null) {
117                        mAppController.notifyNewMedia(uri);
118                    }
119                }
120            };
121
122    /**
123     * Called when the user pressed the back/front camera switch button.
124     */
125    private final ButtonManager.ButtonCallback mCameraSwitchCallback =
126            new ButtonManager.ButtonCallback() {
127                @Override
128                public void onStateChanged(int cameraId) {
129                    // At the time this callback is fired, the camera id
130                    // has be set to the desired camera.
131                    if (mPaused) {
132                        return;
133                    }
134
135                    mSettingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,
136                            cameraId);
137
138                    Log.d(TAG, "Start to switch camera. cameraId=" + cameraId);
139                    switchCamera(getFacingFromCameraId(cameraId));
140                }
141            };
142
143    private static final Tag TAG = new Tag("CaptureModule");
144    private static final String PHOTO_MODULE_STRING_ID = "PhotoModule";
145    /** Enable additional debug output. */
146    private static final boolean DEBUG = true;
147    /**
148     * This is the delay before we execute onResume tasks when coming from the
149     * lock screen, to allow time for onPause to execute.
150     * <p>
151     * TODO: Make sure this value is in sync with what we see on L.
152     */
153    private static final int ON_RESUME_TASKS_DELAY_MSEC = 20;
154
155    private final Object mDimensionLock = new Object();
156    /**
157     * Lock for race conditions in the SurfaceTextureListener callbacks.
158     */
159    private final Object mSurfaceLock = new Object();
160    /** Controller giving us access to other services. */
161    private final AppController mAppController;
162    /** The applications settings manager. */
163    private final SettingsManager mSettingsManager;
164    /** Application context. */
165    private final Context mContext;
166    private CaptureModuleUI mUI;
167    /** Your standard content resolver. */
168    private ContentResolver mContentResolver;
169    /** The camera manager used to open cameras. */
170    private OneCameraManager mCameraManager;
171    /** The currently opened camera device. */
172    private OneCamera mCamera;
173    /** The direction the currently opened camera is facing to. */
174    private Facing mCameraFacing = Facing.BACK;
175    /** The texture used to render the preview in. */
176    private SurfaceTexture mPreviewTexture;
177
178    /** State by the module state machine. */
179    private static enum ModuleState {
180        IDLE,
181        WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED,
182        UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE,
183    }
184
185    /** The current state of the module. */
186    private ModuleState mState = ModuleState.IDLE;
187    /** Current orientation of the device. */
188    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
189
190    /** Accelerometer data. */
191    private final float[] mGData = new float[3];
192    /** Magnetic sensor data. */
193    private final float[] mMData = new float[3];
194    /** Temporary rotation matrix. */
195    private final float[] mR = new float[16];
196    /** Current compass heading. */
197    private int mHeading = -1;
198
199    /** Whether the module is paused right now. */
200    private boolean mPaused;
201
202    /** Whether this module was resumed from lockscreen capture intent. */
203    private boolean mIsResumeFromLockScreen = false;
204
205    private final Runnable mResumeTaskRunnable = new Runnable() {
206        @Override
207        public void run() {
208            onResumeTasks();
209        }
210    };
211
212    /** Main thread handler. */
213    private Handler mMainHandler;
214
215    /** Current display rotation in degrees. */
216    private int mDisplayRotation;
217    /** Current width of the screen, in pixels. */
218    private int mScreenWidth;
219    /** Current height of the screen, in pixels. */
220    private int mScreenHeight;
221    /** Current preview width, in pixels. */
222    private int mPreviewBufferWidth;
223    /** Current preview height, in pixels. */
224    private int mPreviewBufferHeight;
225
226    // /** Current preview area width. */
227    // private float mFullPreviewWidth;
228    // /** Current preview area height. */
229    // private float mFullPreviewHeight;
230
231    /** The current preview transformation matrix. */
232    private Matrix mPreviewTranformationMatrix = new Matrix();
233    /** TODO: This is N5 specific. */
234    public static final float FULLSCREEN_ASPECT_RATIO = 16 / 9f;
235
236    /**
237     * Desires aspect ratio of the final image.
238     * <p>
239     * TODO: Can't we deduct this from the final image's resolution?
240     */
241    private Float mFinalAspectRatio;
242
243    /** CLEAN UP START */
244    // private SoundPool mSoundPool;
245    // private int mCaptureStartSoundId;
246    // private static final int NO_SOUND_STREAM = -999;
247    // private final int mCaptureStartSoundStreamId = NO_SOUND_STREAM;
248    // private int mCaptureDoneSoundId;
249    // private SoundClips.Player mSoundPlayer;
250    // private boolean mFirstLayout;
251    // private int[] mTargetFPSRanges;
252    // private float mZoomValue;
253    // private int mSensorOrientation;
254    // private int mLensFacing;
255    // private volatile float mMaxZoomRatio = 1.0f;
256    // private String mFlashMode;
257    /** CLEAN UP END */
258
259    /** Constructs a new capture module. */
260    public CaptureModule(AppController appController) {
261        super(appController);
262        mAppController = appController;
263        mContext = mAppController.getAndroidContext();
264        mSettingsManager = mAppController.getSettingsManager();
265        mSettingsManager.addListener(this);
266    }
267
268    @Override
269    public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
270        Log.d(TAG, "init");
271        mIsResumeFromLockScreen = isResumeFromLockscreen(activity);
272        mMainHandler = new Handler(activity.getMainLooper());
273        mCameraManager = mAppController.getCameraManager();
274        mContentResolver = activity.getContentResolver();
275        mDisplayRotation = CameraUtil.getDisplayRotation(mContext);
276        mCameraFacing = getFacingFromCameraId(mSettingsManager.getInteger(
277                mAppController.getModuleScope(),
278                Keys.KEY_CAMERA_ID));
279        mUI = new CaptureModuleUI(activity, this, mAppController.getModuleLayoutRoot(),
280                mLayoutListener);
281        mAppController.setPreviewStatusListener(mUI);
282        mPreviewTexture = mAppController.getCameraAppUI().getSurfaceTexture();
283        if (mPreviewTexture != null) {
284            initSurface(mPreviewTexture);
285        }
286    }
287
288    @Override
289    public void onShutterButtonFocus(boolean pressed) {
290        // TODO Auto-generated method stub
291    }
292
293    @Override
294    public void onShutterCoordinate(TouchCoordinate coord) {
295        // TODO Auto-generated method stub
296    }
297
298    @Override
299    public void onShutterButtonClick() {
300        // TODO: Add focusing.
301        if (mCamera == null) {
302            return;
303        }
304        mAppController.setShutterEnabled(false);
305
306        // Set up the capture session.
307        long sessionTime = System.currentTimeMillis();
308        String title = CameraUtil.createJpegName(sessionTime);
309        CaptureSession session = getServices().getCaptureSessionManager()
310                .createNewSession(title, sessionTime, null);
311
312        // TODO: Add location.
313
314        // Set up the parameters for this capture.
315        PhotoCaptureParameters params = new PhotoCaptureParameters();
316        params.title = title;
317        params.callback = this;
318        params.orientation = getOrientation();
319        params.flashMode = getFlashModeFromSettings();
320        params.heading = mHeading;
321
322        // Take the picture.
323        mCamera.takePicture(params, session);
324    }
325
326    @Override
327    public void onPreviewAreaChanged(RectF previewArea) {
328        // mUI.updatePreviewAreaRect(previewArea);
329        // mUI.positionProgressOverlay(previewArea);
330    }
331
332    @Override
333    public void onSensorChanged(SensorEvent event) {
334        // This is literally the same as the GCamModule implementation.
335        int type = event.sensor.getType();
336        float[] data;
337        if (type == Sensor.TYPE_ACCELEROMETER) {
338            data = mGData;
339        } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
340            data = mMData;
341        } else {
342            Log.w(TAG, String.format("Unexpected sensor type %s", event.sensor.getName()));
343            return;
344        }
345        for (int i = 0; i < 3; i++) {
346            data[i] = event.values[i];
347        }
348        float[] orientation = new float[3];
349        SensorManager.getRotationMatrix(mR, null, mGData, mMData);
350        SensorManager.getOrientation(mR, orientation);
351        mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
352        if (mHeading < 0) {
353            mHeading += 360;
354        }
355    }
356
357    @Override
358    public void onAccuracyChanged(Sensor sensor, int accuracy) {
359        // TODO Auto-generated method stub
360    }
361
362    @Override
363    public void onQueueStatus(boolean full) {
364        // TODO Auto-generated method stub
365    }
366
367    @Override
368    public void onRemoteShutterPress() {
369        // TODO: Check whether shutter is enabled.
370        onShutterButtonClick();
371    }
372
373    @Override
374    public void onSurfaceTextureAvailable(final SurfaceTexture surface, int width, int height) {
375        Log.d(TAG, "onSurfaceTextureAvailable");
376        // Force to re-apply transform matrix here as a workaround for
377        // b/11168275
378        updatePreviewTransform(width, height, true);
379        initSurface(surface);
380    }
381
382    public void initSurface(final SurfaceTexture surface) {
383        mPreviewTexture = surface;
384        closeCamera();
385
386        mCameraManager.open(mCameraFacing, getPictureSizeFromSettings(), new OpenCallback() {
387            @Override
388            public void onFailure() {
389                Log.e(TAG, "Could not open camera.");
390                mCamera = null;
391                mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
392            }
393
394            @Override
395            public void onCameraOpened(final OneCamera camera) {
396                Log.d(TAG, "onCameraOpened: " + camera);
397                mCamera = camera;
398                updateBufferDimension();
399
400                // If the surface texture is not destroyed, it may have the last
401                // frame lingering.
402                // We need to hold off setting transform until preview is
403                // started.
404                resetDefaultBufferSize();
405                mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED;
406
407                Log.d(TAG, "starting preview ...");
408
409                // TODO: Consider rolling these two calls into one.
410                camera.startPreview(new Surface(surface), new CaptureReadyCallback() {
411
412                    @Override
413                    public void onSetupFailed() {
414                        Log.e(TAG, "Could not set up preview.");
415                        mCamera.close(null);
416                        mCamera = null;
417                        // TODO: Show an error message and exit.
418                    }
419
420                    @Override
421                    public void onReadyForCapture() {
422                        Log.d(TAG, "Ready for capture.");
423                        onPreviewStarted();
424                    }
425                });
426            }
427        });
428    }
429
430    @Override
431    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
432        Log.d(TAG, "onSurfaceTextureSizeChanged");
433        resetDefaultBufferSize();
434    }
435
436    @Override
437    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
438        Log.d(TAG, "onSurfaceTextureDestroyed");
439        closeCamera();
440        return true;
441    }
442
443    @Override
444    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
445        if (mState == ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE) {
446            Log.d(TAG, "onSurfaceTextureUpdated --> updatePreviewTransform");
447            mState = ModuleState.IDLE;
448            CameraAppUI appUI = mAppController.getCameraAppUI();
449            updatePreviewTransform(appUI.getSurfaceWidth(), appUI.getSurfaceHeight(), true);
450        }
451    }
452
453    @Override
454    public String getModuleStringIdentifier() {
455        return PHOTO_MODULE_STRING_ID;
456    }
457
458    @Override
459    public void resume() {
460        // Add delay on resume from lock screen only, in order to to speed up
461        // the onResume --> onPause --> onResume cycle from lock screen.
462        // Don't do always because letting go of thread can cause delay.
463        if (mIsResumeFromLockScreen) {
464            Log.v(TAG, "Delayng onResumeTasks from lock screen. " + System.currentTimeMillis());
465            // Note: onPauseAfterSuper() will delete this runnable, so we will
466            // at most have 1 copy queued up.
467            mMainHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC);
468        } else {
469            onResumeTasks();
470        }
471    }
472
473    private void onResumeTasks() {
474        Log.d(TAG, "onResumeTasks + " + System.currentTimeMillis());
475        mPaused = false;
476        mAppController.getCameraAppUI().onChangeCamera();
477        mAppController.addPreviewAreaSizeChangedListener(this);
478        resetDefaultBufferSize();
479        getServices().getRemoteShutterListener().onModuleReady(this);
480        mAppController.setShutterEnabled(true);
481    }
482
483    @Override
484    public void pause() {
485        mPaused = true;
486        resetTextureBufferSize();
487        closeCamera();
488        // Remove delayed resume trigger, if it hasn't been executed yet.
489        mMainHandler.removeCallbacksAndMessages(null);
490    }
491
492    @Override
493    public void destroy() {
494    }
495
496    @Override
497    public void onLayoutOrientationChanged(boolean isLandscape) {
498        Log.d(TAG, "onLayoutOrientationChanged");
499    }
500
501    @Override
502    public void onOrientationChanged(int orientation) {
503        // We keep the last known orientation. So if the user first orient
504        // the camera then point the camera to floor or sky, we still have
505        // the correct orientation.
506        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
507            return;
508        }
509        mOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
510    }
511
512    @Override
513    public void onCameraAvailable(CameraProxy cameraProxy) {
514        // Ignore since we manage the camera ourselves until we remove this.
515    }
516
517    @Override
518    public void hardResetSettings(SettingsManager settingsManager) {
519        // TODO Auto-generated method stub
520    }
521
522    @Override
523    public HardwareSpec getHardwareSpec() {
524        return new HardwareSpec() {
525            @Override
526            public boolean isFrontCameraSupported() {
527                return true;
528            }
529
530            @Override
531            public boolean isHdrSupported() {
532                return false;
533            }
534
535            @Override
536            public boolean isHdrPlusSupported() {
537                // TODO: Enable once we support this.
538                return false;
539            }
540
541            @Override
542            public boolean isFlashSupported() {
543                return true;
544            }
545        };
546    }
547
548    @Override
549    public BottomBarUISpec getBottomBarSpec() {
550        CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
551        bottomBarSpec.enableGridLines = true;
552        bottomBarSpec.enableCamera = true;
553        bottomBarSpec.cameraCallback = mCameraSwitchCallback;
554        // TODO: Enable once we support this.
555        bottomBarSpec.enableHdr = false;
556        // TODO: Enable once we support this.
557        bottomBarSpec.hdrCallback = null;
558        // TODO: Enable once we support this.
559        bottomBarSpec.enableSelfTimer = false;
560        bottomBarSpec.showSelfTimer = false;
561        // TODO: Deal with e.g. HDR+ if it doesn't support it.
562        bottomBarSpec.enableFlash = true;
563        return bottomBarSpec;
564    }
565
566    @Override
567    public boolean isUsingBottomBar() {
568        return true;
569    }
570
571    @Override
572    public boolean onKeyDown(int keyCode, KeyEvent event) {
573        return false;
574    }
575
576    @Override
577    public boolean onKeyUp(int keyCode, KeyEvent event) {
578        return false;
579    }
580
581    @Override
582    public void onSingleTapUp(View view, int x, int y) {
583    }
584
585    @Override
586    public String getPeekAccessibilityString() {
587        return mAppController.getAndroidContext()
588                .getResources().getString(R.string.photo_accessibility_peek);
589    }
590
591    @Override
592    public void onThumbnailResult(Bitmap bitmap) {
593        // TODO
594    }
595
596    @Override
597    public void onPictureTaken(CaptureSession session) {
598        // TODO, enough memory available? ProcessingService status, etc.
599        mAppController.setShutterEnabled(true);
600    }
601
602    @Override
603    public void onPictureSaved(Uri uri) {
604        mAppController.notifyNewMedia(uri);
605    }
606
607    @Override
608    public void onTakePictureProgress(int progressPercent) {
609        // TODO once we have HDR+ hooked up.
610    }
611
612    @Override
613    public void onPictureTakenFailed() {
614        // TODO
615    }
616
617    @Override
618    public void onSettingChanged(SettingsManager settingsManager, String key) {
619        // TODO Auto-generated method stub
620    }
621
622    /**
623     * Updates the preview transform matrix to adapt to the current preview
624     * width, height, and orientation.
625     */
626    public void updatePreviewTransform() {
627        int width;
628        int height;
629        synchronized (mDimensionLock) {
630            width = mScreenWidth;
631            height = mScreenHeight;
632        }
633        updatePreviewTransform(width, height);
634    }
635
636    /**
637     * Called when the preview started. Informs the app controller and queues a
638     * transform update when the next preview frame arrives.
639     */
640    private void onPreviewStarted() {
641        if (mState == ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED) {
642            mState = ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE;
643        }
644        mAppController.onPreviewStarted();
645    }
646
647    /**
648     * Update the preview transform based on the new dimensions. Will not force
649     * an update, if it's not necessary.
650     */
651    private void updatePreviewTransform(int incomingWidth, int incomingHeight) {
652        updatePreviewTransform(incomingWidth, incomingHeight, false);
653    }
654
655    /***
656     * Update the preview transform based on the new dimensions.
657     */
658    private void updatePreviewTransform(int incomingWidth, int incomingHeight,
659            boolean forceUpdate) {
660        Log.d(TAG, "updatePreviewTransform: " + incomingWidth + " x " + incomingHeight);
661
662        synchronized (mDimensionLock) {
663            int incomingRotation = CameraUtil
664                    .getDisplayRotation(mContext);
665            // Check for an actual change:
666            if (mScreenHeight == incomingHeight && mScreenWidth == incomingWidth &&
667                    incomingRotation == mDisplayRotation && !forceUpdate) {
668                return;
669            }
670            // Update display rotation and dimensions
671            mDisplayRotation = incomingRotation;
672            mScreenWidth = incomingWidth;
673            mScreenHeight = incomingHeight;
674            updateBufferDimension();
675
676            mPreviewTranformationMatrix = mAppController.getCameraAppUI().getPreviewTransform(
677                    mPreviewTranformationMatrix);
678            int width = mScreenWidth;
679            int height = mScreenHeight;
680
681            // Assumptions:
682            // - Aspect ratio for the sensor buffers is in landscape
683            // orientation,
684            // - Dimensions of buffers received are rotated to the natural
685            // device orientation.
686            // - The contents of each buffer are rotated by the inverse of
687            // the display rotation.
688            // - Surface scales the buffer to fit the current view bounds.
689
690            // Get natural orientation and buffer dimensions
691            int naturalOrientation = CaptureModuleUtil
692                    .getDeviceNaturalOrientation(mContext);
693            int effectiveWidth = mPreviewBufferWidth;
694            int effectiveHeight = mPreviewBufferHeight;
695
696            if (DEBUG) {
697                Log.v(TAG, "Rotation: " + mDisplayRotation);
698                Log.v(TAG, "Screen Width: " + mScreenWidth);
699                Log.v(TAG, "Screen Height: " + mScreenHeight);
700                Log.v(TAG, "Buffer width: " + mPreviewBufferWidth);
701                Log.v(TAG, "Buffer height: " + mPreviewBufferHeight);
702                Log.v(TAG, "Natural orientation: " + naturalOrientation);
703            }
704
705            // If natural orientation is portrait, rotate the buffer
706            // dimensions
707            if (naturalOrientation == Configuration.ORIENTATION_PORTRAIT) {
708                int temp = effectiveWidth;
709                effectiveWidth = effectiveHeight;
710                effectiveHeight = temp;
711            }
712
713            // Find and center view rect and buffer rect
714            RectF viewRect = new RectF(0, 0, width, height);
715            RectF bufRect = new RectF(0, 0, effectiveWidth, effectiveHeight);
716            float centerX = viewRect.centerX();
717            float centerY = viewRect.centerY();
718            bufRect.offset(centerX - bufRect.centerX(), centerY - bufRect.centerY());
719
720            // Undo ScaleToFit.FILL done by the surface
721            mPreviewTranformationMatrix.setRectToRect(viewRect, bufRect, Matrix.ScaleToFit.FILL);
722
723            // Rotate buffer contents to proper orientation
724            mPreviewTranformationMatrix.postRotate(getPreviewOrientation(mDisplayRotation),
725                    centerX, centerY);
726
727            // TODO: This is probably only working for the N5. Need to test
728            // on a device like N10 with different sensor orientation.
729            if ((mDisplayRotation % 180) == 90) {
730                int temp = effectiveWidth;
731                effectiveWidth = effectiveHeight;
732                effectiveHeight = temp;
733            }
734
735            boolean is16by9 = false;
736
737            // TODO: BACK/FRONT.
738            Size pictureSize = getPictureSizeFromSettings();
739            if (pictureSize != null) {
740                pictureSize = ResolutionUtil.getApproximateSize(pictureSize);
741                if (pictureSize.equals(new Size(16, 9))) {
742                    is16by9 = true;
743                }
744            }
745
746            float scale;
747            if (is16by9) {
748                // We are going to be clipping off edges to achieve the 16
749                // by 9 aspect ratio so we will choose the max here to fill,
750                // instead of fit.
751                scale =
752                        Math.max(width / (float) effectiveWidth, height
753                                / (float) effectiveHeight);
754            } else {
755                // Scale to fit view, cropping the longest dimension
756                scale =
757                        Math.min(width / (float) effectiveWidth, height
758                                / (float) effectiveHeight);
759            }
760            mPreviewTranformationMatrix.postScale(scale, scale, centerX, centerY);
761
762            float previewWidth = effectiveWidth * scale;
763            float previewHeight = effectiveHeight * scale;
764            // mFullPreviewWidth = previewWidth;
765            // mFullPreviewHeight = previewHeight;
766
767            float previewCenterX = previewWidth / 2;
768            float previewCenterY = previewHeight / 2;
769            mPreviewTranformationMatrix.postTranslate(previewCenterX - centerX, previewCenterY
770                    - centerY);
771
772            if (is16by9) {
773                float aspectRatio = FULLSCREEN_ASPECT_RATIO;
774                RectF renderedPreviewRect = mAppController.getFullscreenRect();
775                float desiredPreviewWidth = Math.max(renderedPreviewRect.height(),
776                        renderedPreviewRect.width()) * 1 / aspectRatio;
777                int letterBoxWidth = (int) Math.ceil((Math.min(renderedPreviewRect.width(),
778                        renderedPreviewRect.height()) - desiredPreviewWidth) / 2.0f);
779                mAppController.getCameraAppUI().addLetterboxing(letterBoxWidth);
780
781                float wOffset = -(previewWidth - renderedPreviewRect.width()) / 2.0f;
782                float hOffset = -(previewHeight - renderedPreviewRect.height()) / 2.0f;
783                mPreviewTranformationMatrix.postTranslate(wOffset, hOffset);
784                mAppController.updatePreviewTransformFullscreen(mPreviewTranformationMatrix,
785                        aspectRatio);
786                mFinalAspectRatio = aspectRatio;
787            } else {
788                mAppController.updatePreviewTransform(mPreviewTranformationMatrix);
789                mFinalAspectRatio = null;
790                mAppController.getCameraAppUI().hideLetterboxing();
791            }
792            // if (mGcamProxy != null) {
793            // mGcamProxy.postSetAspectRatio(mFinalAspectRatio);
794            // }
795            // mUI.updatePreviewAreaRect(new RectF(0, 0, previewWidth,
796            // previewHeight));
797
798            // TODO: Add face detection.
799            // Characteristics info =
800            // mapp.getCameraProvider().getCharacteristics(0);
801            // mUI.setupFaceDetection(CameraUtil.getDisplayOrientation(incomingRotation,
802            // info), false);
803            // updateCamera2FaceBoundTransform(new
804            // RectF(mEffectiveCropRegion),
805            // new RectF(0, 0, mBufferWidth, mBufferHeight),
806            // new RectF(0, 0, previewWidth, previewHeight), getRotation());
807        }
808    }
809
810    private void updateBufferDimension() {
811        if (mCamera == null) {
812            return;
813        }
814
815        Size picked = CaptureModuleUtil.pickBufferDimensions(
816                mCamera.getSupportedSizes(),
817                mCamera.getFullSizeAspectRatio(),
818                mContext);
819        mPreviewBufferWidth = picked.getWidth();
820        mPreviewBufferHeight = picked.getHeight();
821    }
822
823    /**
824     * Resets the default buffer size to the initially calculated size.
825     */
826    private void resetDefaultBufferSize() {
827        synchronized (mSurfaceLock) {
828            if (mPreviewTexture != null) {
829                mPreviewTexture.setDefaultBufferSize(mPreviewBufferWidth, mPreviewBufferHeight);
830            }
831        }
832    }
833
834    private void closeCamera() {
835        if (mCamera != null) {
836            mCamera.close(null);
837            mCamera = null;
838        }
839    }
840
841    private int getOrientation() {
842        // We need to be consistent with the framework orientation (i.e. the
843        // orientation of the UI.) when the auto-rotate screen setting is on.
844        if (mAppController.isAutoRotateScreen()) {
845            return (360 - mDisplayRotation) % 360;
846        } else {
847            return mOrientation;
848        }
849    }
850
851    /**
852     * @return Whether we are resuming from within the lockscreen.
853     */
854    private static boolean isResumeFromLockscreen(Activity activity) {
855        String action = activity.getIntent().getAction();
856        return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action)
857        || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action));
858    }
859
860    private void switchCamera(Facing switchTo) {
861        if (mPaused || mCameraFacing == switchTo) {
862            return;
863        }
864        // TODO: Un-comment once we have timer back.
865        // cancelCountDown();
866
867        mAppController.freezeScreenUntilPreviewReady();
868
869        mCameraFacing = switchTo;
870        initSurface(mPreviewTexture);
871
872        // TODO: Un-comment once we have focus back.
873        // if (mFocusManager != null) {
874        // mFocusManager.removeMessages();
875        // }
876        // mFocusManager.setMirror(mMirror);
877    }
878
879    private Size getPictureSizeFromSettings() {
880        String pictureSizeKey = mCameraFacing == Facing.FRONT ? Keys.KEY_PICTURE_SIZE_FRONT
881                : Keys.KEY_PICTURE_SIZE_BACK;
882        return mSettingsManager.getSize(SettingsManager.SCOPE_GLOBAL, pictureSizeKey);
883    }
884
885    private int getPreviewOrientation(int deviceOrientationDegrees) {
886        // Important: Camera2 buffers are already rotated to the natural
887        // orientation of the device (at least for the back-camera).
888
889        // TODO: Remove this hack for the front camera as soon as b/16637957 is
890        // fixed.
891        if (mCameraFacing == Facing.FRONT) {
892            deviceOrientationDegrees += 180;
893        }
894        return (360 - deviceOrientationDegrees) % 360;
895    }
896
897    /**
898     * Returns which way around the camera is facing, based on it's ID.
899     * <p>
900     * TODO: This needs to change so that we store the direction directly in the
901     * settings, rather than a Camera ID.
902     */
903    private static Facing getFacingFromCameraId(int cameraId) {
904        return cameraId == 1 ? Facing.FRONT : Facing.BACK;
905    }
906
907    private void resetTextureBufferSize() {
908        // Reset the default buffer sizes on the shared SurfaceTexture
909        // so they are not scaled for gcam.
910        //
911        // According to the documentation for
912        // SurfaceTexture.setDefaultBufferSize,
913        // photo and video based image producers (presumably only Camera 1 api),
914        // override this buffer size. Any module that uses egl to render to a
915        // SurfaceTexture must have these buffer sizes reset manually. Otherwise
916        // the SurfaceTexture cannot be transformed by matrix set on the
917        // TextureView.
918        if (mPreviewTexture != null) {
919            mPreviewTexture.setDefaultBufferSize(mAppController.getCameraAppUI().getSurfaceWidth(),
920                    mAppController.getCameraAppUI().getSurfaceHeight());
921        }
922    }
923
924    /**
925     * @return The currently set Flash settings. Defaults to AUTO if the setting
926     *         could not be parsed.
927     */
928    private Flash getFlashModeFromSettings() {
929        String flashSetting = mSettingsManager.getString(mAppController.getCameraScope(),
930                Keys.KEY_FLASH_MODE);
931        try {
932            return Flash.valueOf(flashSetting.toUpperCase());
933        } catch (IllegalArgumentException ex) {
934            Log.w(TAG, "Could not parse Flash Setting. Defaulting to AUTO.");
935            return Flash.AUTO;
936        }
937    }
938}
939