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