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