OSDN Git Service

Merge "Log capture session canceled events." into ub-camera-haleakala
[android-x86/packages-apps-Camera2.git] / src / com / android / camera / CaptureModule.java
index f67799b..79dfe09 100644 (file)
@@ -47,6 +47,7 @@ import com.android.camera.captureintent.PreviewTransformCalculator;
 import com.android.camera.debug.DebugPropertyHelper;
 import com.android.camera.debug.Log;
 import com.android.camera.debug.Log.Tag;
+import com.android.camera.device.CameraId;
 import com.android.camera.hardware.HardwareSpec;
 import com.android.camera.hardware.HeadingSensor;
 import com.android.camera.module.ModuleController;
@@ -57,15 +58,20 @@ import com.android.camera.one.OneCamera.Facing;
 import com.android.camera.one.OneCamera.OpenCallback;
 import com.android.camera.one.OneCamera.PhotoCaptureParameters;
 import com.android.camera.one.OneCameraAccessException;
+import com.android.camera.one.OneCameraCaptureSetting;
 import com.android.camera.one.OneCameraCharacteristics;
+import com.android.camera.one.OneCameraException;
 import com.android.camera.one.OneCameraManager;
-import com.android.camera.one.v2.OneCameraManagerImpl;
+import com.android.camera.one.OneCameraModule;
+import com.android.camera.one.OneCameraOpener;
+import com.android.camera.one.config.OneCameraFeatureConfig;
 import com.android.camera.one.v2.photo.ImageRotationCalculator;
 import com.android.camera.one.v2.photo.ImageRotationCalculatorImpl;
 import com.android.camera.remote.RemoteCameraModule;
 import com.android.camera.session.CaptureSession;
 import com.android.camera.settings.Keys;
 import com.android.camera.settings.SettingsManager;
+import com.android.camera.stats.CaptureStats;
 import com.android.camera.stats.UsageStatistics;
 import com.android.camera.stats.profiler.Profile;
 import com.android.camera.stats.profiler.Profiler;
@@ -76,15 +82,19 @@ import com.android.camera.ui.TouchCoordinate;
 import com.android.camera.ui.focus.FocusController;
 import com.android.camera.ui.focus.FocusSound;
 import com.android.camera.util.AndroidServices;
+import com.android.camera.util.ApiHelper;
 import com.android.camera.util.CameraUtil;
 import com.android.camera.util.GcamHelper;
 import com.android.camera.util.Size;
 import com.android.camera2.R;
 import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
+import com.google.common.logging.eventprotos;
 
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
+import javax.annotation.Nonnull;
+
 /**
  * New Capture module that is made to support photo and video capture on top of
  * the OneCamera API, to transparently support GCam.
@@ -102,9 +112,10 @@ public class CaptureModule extends CameraModule implements
         RemoteCameraModule {
 
     private static final Tag TAG = new Tag("CaptureModule");
-    private static final String PHOTO_MODULE_STRING_ID = "PhotoModule";
     /** Enable additional debug output. */
     private static final boolean DEBUG = true;
+    /** Workaround Flag for b/19271661 to use autotransformation in Capture Layout in Nexus4 **/
+    private static final boolean USE_AUTOTRANSFORM_UI_LAYOUT = ApiHelper.IS_NEXUS_4;
 
     /** Timeout for camera open/close operations. */
     private static final int CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS = 2500;
@@ -130,7 +141,9 @@ public class CaptureModule extends CameraModule implements
     /** Module UI. */
     private CaptureModuleUI mUI;
     /** The camera manager used to open cameras. */
-    private OneCameraManager mCameraManager;
+    private OneCameraOpener mOneCameraOpener;
+    /** The manager to query for camera device information */
+    private OneCameraManager mOneCameraManager;
     /** The currently opened camera device, or null if the camera is closed. */
     private OneCamera mCamera;
     /** The selected picture size. */
@@ -143,6 +156,12 @@ public class CaptureModule extends CameraModule implements
     private boolean mHdrSceneEnabled = false;
     private boolean mHdrPlusEnabled = false;
     private final Object mSurfaceTextureLock = new Object();
+    /**
+     * Flag that is used when Fatal Error Handler is running and the app should
+     * not continue execution
+     */
+    private boolean mShowErrorAndFinish;
+    private TouchCoordinate mLastShutterTouchCoordinate = null;
 
     private FocusController mFocusController;
     private OneCameraCharacteristics mCameraCharacteristics;
@@ -182,7 +201,7 @@ public class CaptureModule extends CameraModule implements
 
         @Override
         public boolean shouldAutoAdjustTransformMatrixOnLayout() {
-            return false;
+            return USE_AUTOTRANSFORM_UI_LAYOUT;
         }
 
         @Override
@@ -199,8 +218,8 @@ public class CaptureModule extends CameraModule implements
                 public boolean onSingleTapUp(MotionEvent ev) {
                     Point tapPoint = new Point((int) ev.getX(), (int) ev.getY());
                     Log.v(TAG, "onSingleTapUpPreview location=" + tapPoint);
-                    // TODO: This should query actual capability.
-                    if (mCameraFacing == Facing.FRONT) {
+                    if (!mCameraCharacteristics.isAutoExposureSupported() &&
+                          !mCameraCharacteristics.isAutoFocusSupported()) {
                         return false;
                     }
                     startActiveFocusAt(tapPoint.x, tapPoint.y);
@@ -358,34 +377,46 @@ public class CaptureModule extends CameraModule implements
                     public void onBurstReadyStateChanged(boolean ready) {
                         // TODO: This needs to take into account the state of
                         // the whole system, not just burst.
-                        mAppController.setShutterEnabled(ready);
+                       onReadyStateChanged(false);
                     }
                 });
         mMediaActionSound = new MediaActionSound();
         guard.stop();
     }
 
-    private void updateCameraCharacteristics() {
+    private boolean updateCameraCharacteristics() {
         try {
-            mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraFacing);
-        } catch (OneCameraAccessException ocae) {
-            mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
-            return;
-        }
+            CameraId cameraId = mOneCameraManager.findFirstCameraFacing(mCameraFacing);
+            if (cameraId != null && cameraId.getValue() != null) {
+                mCameraCharacteristics = mOneCameraManager.getOneCameraCharacteristics(cameraId);
+                return mCameraCharacteristics != null;
+            }
+        } catch (OneCameraAccessException ignored) { }
+            mAppController.getFatalErrorHandler().onGenericCameraAccessFailure();
+            return false;
     }
 
     @Override
     public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
         Profile guard = mProfiler.create("CaptureModule.init").start();
-        Log.d(TAG, "init");
+        Log.d(TAG, "init UseAutotransformUiLayout = " + USE_AUTOTRANSFORM_UI_LAYOUT);
         HandlerThread thread = new HandlerThread("CaptureModule.mCameraHandler");
         thread.start();
         mCameraHandler = new Handler(thread.getLooper());
-        mCameraManager = mAppController.getCameraManager();
+        mOneCameraOpener = mAppController.getCameraOpener();
+
+        try {
+            mOneCameraManager = OneCameraModule.provideOneCameraManager();
+        } catch (OneCameraException e) {
+            Log.e(TAG, "Unable to provide a OneCameraManager. ", e);
+        }
         mDisplayRotation = CameraUtil.getDisplayRotation();
         mCameraFacing = getFacingFromCameraId(
               mSettingsManager.getInteger(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID));
-        updateCameraCharacteristics();
+        mShowErrorAndFinish = !updateCameraCharacteristics();
+        if (mShowErrorAndFinish) {
+            return;
+        }
         mUI = new CaptureModuleUI(activity, mAppController.getModuleLayoutRoot(), mUIListener);
         mAppController.setPreviewStatusListener(mPreviewStatusListener);
         synchronized (mSurfaceTextureLock) {
@@ -413,16 +444,21 @@ public class CaptureModule extends CameraModule implements
     @Override
     public void onShutterButtonLongPressed() {
         try {
-            CaptureSession session = createAndStartCaptureSession();
-
             OneCameraCharacteristics cameraCharacteristics;
-            cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraFacing);
+            CameraId cameraId = mOneCameraManager.findFirstCameraFacing(mCameraFacing);
+            cameraCharacteristics = mOneCameraManager.getOneCameraCharacteristics(cameraId);
             DeviceOrientation deviceOrientation = mAppController.getOrientationManager()
                     .getDeviceOrientation();
             ImageRotationCalculator imageRotationCalculator = ImageRotationCalculatorImpl
                     .from(mAppController.getOrientationManager(), cameraCharacteristics);
 
-            mBurstController.startBurst(session,
+            mBurstController.startBurst(
+                    new CaptureSession.CaptureSessionCreator() {
+                        @Override
+                        public CaptureSession createAndStartEmpty() {
+                            return createAndStartUntrackedCaptureSession();
+                        }
+                    },
                     deviceOrientation,
                     mCamera.getDirection(),
                     imageRotationCalculator.toImageRotation().getDegrees());
@@ -443,7 +479,7 @@ public class CaptureModule extends CameraModule implements
 
     @Override
     public void onShutterCoordinate(TouchCoordinate coord) {
-        // TODO Auto-generated method stub
+        mLastShutterTouchCoordinate = coord;
     }
 
     @Override
@@ -466,7 +502,36 @@ public class CaptureModule extends CameraModule implements
         }
     }
 
+
+    private void decorateSessionAtCaptureTime(CaptureSession session) {
+        String flashSetting =
+                mSettingsManager.getString(mAppController.getCameraScope(),
+                        Keys.KEY_FLASH_MODE);
+        boolean gridLinesOn = Keys.areGridLinesOn(mSettingsManager);
+        float timerDuration = mSettingsManager
+                .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);
+
+        session.getCollector().decorateAtTimeCaptureRequest(
+                eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
+                session.getTitle() + ".jpg",
+                (mCameraFacing == Facing.FRONT),
+                mHdrSceneEnabled,
+                mZoomValue,
+                flashSetting,
+                gridLinesOn,
+                timerDuration,
+                mLastShutterTouchCoordinate,
+                null /* TODO: Implement Volume Button Shutter Click Instrumentation */,
+                mCameraCharacteristics.getSensorInfoActiveArraySize()
+        );
+    }
+
     private void takePictureNow() {
+        if (mCamera == null) {
+            Log.i(TAG, "Not taking picture since Camera is closed.");
+            return;
+        }
+
         CaptureSession session = createAndStartCaptureSession();
         int orientation = mAppController.getOrientationManager().getDeviceOrientation()
                 .getDegrees();
@@ -477,6 +542,7 @@ public class CaptureModule extends CameraModule implements
                 session.getTitle(), orientation, session.getLocation(),
                 mContext.getExternalCacheDir(), this, mPictureSaverCallback,
                 mHeadingSensor.getCurrentHeading(), mZoomValue, 0);
+        decorateSessionAtCaptureTime(session);
         mCamera.takePicture(params, session);
     }
 
@@ -490,7 +556,21 @@ public class CaptureModule extends CameraModule implements
         String title = CameraUtil.instance().createJpegName(sessionTime);
         CaptureSession session = getServices().getCaptureSessionManager()
                 .createNewSession(title, sessionTime, location);
-        session.startEmpty(new Size((int) mPreviewArea.width(), (int) mPreviewArea.height()));
+
+        session.startEmpty(new CaptureStats(mHdrPlusEnabled),
+              new Size((int) mPreviewArea.width(), (int) mPreviewArea.height()));
+        return session;
+    }
+
+    private CaptureSession createAndStartUntrackedCaptureSession() {
+        long sessionTime = getSessionTime();
+        Location location = mLocationManager.getCurrentLocation();
+        String title = CameraUtil.instance().createJpegName(sessionTime);
+        CaptureSession session = getServices().getCaptureSessionManager()
+              .createNewSession(title, sessionTime, location);
+
+        session.startEmpty(null,
+              new Size((int) mPreviewArea.width(), (int) mPreviewArea.height()));
         return session;
     }
 
@@ -523,8 +603,11 @@ public class CaptureModule extends CameraModule implements
             // Cancel on-going countdown.
             mUI.cancelCountDown();
         }
-        mAppController.getCameraAppUI().showModeOptions();
-        mAppController.getCameraAppUI().transitionToCapture();
+
+        if (!mPaused) {
+            mAppController.getCameraAppUI().showModeOptions();
+            mAppController.getCameraAppUI().transitionToCapture();
+        }
     }
 
     @Override
@@ -581,12 +664,10 @@ public class CaptureModule extends CameraModule implements
     }
 
     @Override
-    public String getModuleStringIdentifier() {
-        return PHOTO_MODULE_STRING_ID;
-    }
-
-    @Override
     public void resume() {
+        if (mShowErrorAndFinish) {
+            return;
+        }
         Profile guard = mProfiler.create("CaptureModule.resume").start();
 
         // We'll transition into 'ready' once the preview is started.
@@ -595,8 +676,6 @@ public class CaptureModule extends CameraModule implements
         mAppController.addPreviewAreaSizeChangedListener(mPreviewAreaChangedListener);
         mAppController.addPreviewAreaSizeChangedListener(mUI);
 
-        mAppController.getCameraAppUI().onChangeCamera();
-
         guard.mark();
         getServices().getRemoteShutterListener().onModuleReady(this);
         guard.mark("getRemoteShutterListener.onModuleReady");
@@ -606,6 +685,8 @@ public class CaptureModule extends CameraModule implements
         // state, ... ).
         mAppController.getCameraAppUI().enableModeOptions();
         mAppController.setShutterEnabled(true);
+        mAppController.getCameraAppUI().showAccessibilityZoomUI(
+                mCameraCharacteristics.getAvailableMaxDigitalZoom());
 
         mHdrPlusEnabled = mStickyGcamCamera || mAppController.getSettingsManager().getInteger(
                 SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS) == 1;
@@ -641,6 +722,10 @@ public class CaptureModule extends CameraModule implements
 
     @Override
     public void pause() {
+        if (mShowErrorAndFinish) {
+            return;
+        }
+        cancelCountDown();
         mPaused = true;
         mHeadingSensor.deactivate();
 
@@ -648,7 +733,6 @@ public class CaptureModule extends CameraModule implements
         mAppController.removePreviewAreaSizeChangedListener(mPreviewAreaChangedListener);
         getServices().getRemoteShutterListener().onModuleExit();
         mBurstController.release();
-        cancelCountDown();
         closeCamera();
         resetTextureBufferSize();
         mSoundPlayer.unloadSound(R.raw.timer_final_second);
@@ -679,7 +763,7 @@ public class CaptureModule extends CameraModule implements
             // facing.
             settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, true);
             settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,
-                    getBackFacingCameraId());
+                  mOneCameraManager.findFirstCameraFacing(Facing.BACK).getValue());
         }
     }
 
@@ -693,12 +777,19 @@ public class CaptureModule extends CameraModule implements
 
             @Override
             public boolean isHdrSupported() {
-                return mCameraCharacteristics.isHdrSceneSupported();
+                if (ApiHelper.IS_NEXUS_4 && is16by9AspectRatio(mPictureSize)) {
+                    Log.v(TAG, "16:9 N4, no HDR support");
+                    return false;
+                } else {
+                    return mCameraCharacteristics.isHdrSceneSupported();
+                }
             }
 
             @Override
             public boolean isHdrPlusSupported() {
-                return GcamHelper.hasGcamCapture(mAppController.getCameraFeatureConfig());
+                OneCameraFeatureConfig featureConfig = mAppController.getCameraFeatureConfig();
+                return featureConfig.getHdrPlusSupportLevel(mCameraFacing) !=
+                        OneCameraFeatureConfig.HdrPlusSupportLevel.NONE;
             }
 
             @Override
@@ -715,10 +806,14 @@ public class CaptureModule extends CameraModule implements
         bottomBarSpec.enableGridLines = true;
         bottomBarSpec.enableCamera = true;
         bottomBarSpec.cameraCallback = getCameraCallback();
-        bottomBarSpec.enableHdr = hardwareSpec.isHdrSupported() || hardwareSpec.isHdrPlusSupported();
+        bottomBarSpec.enableHdr =
+                hardwareSpec.isHdrSupported() || hardwareSpec.isHdrPlusSupported();
         bottomBarSpec.hdrCallback = getHdrButtonCallback();
         bottomBarSpec.enableSelfTimer = true;
         bottomBarSpec.showSelfTimer = true;
+        bottomBarSpec.isExposureCompensationSupported = mCameraCharacteristics
+                .isExposureCompensationSupported();
+        bottomBarSpec.enableExposureCompensation = bottomBarSpec.isExposureCompensationSupported;
 
         // We must read the key from the settings because the button callback
         // is not executed until after this method is called.
@@ -731,14 +826,15 @@ public class CaptureModule extends CameraModule implements
             // Disable flash if this is a sticky gcam camera, or if
             // HDR is enabled.
             bottomBarSpec.enableFlash = false;
+            // Disable manual exposure if HDR is enabled.
+            bottomBarSpec.enableExposureCompensation = false;
         } else {
             // If we are not in HDR / GCAM mode, fallback on the
-            // flash supported property for this camera.
+            // flash supported property and manual exposure supported property
+            // for this camera.
             bottomBarSpec.enableFlash = mCameraCharacteristics.isFlashSupported();
         }
 
-        bottomBarSpec.enableExposureCompensation =
-                mCameraCharacteristics.isExposureCompensationSupported();
         bottomBarSpec.minExposureCompensation =
                 mCameraCharacteristics.getMinExposureCompensation();
         bottomBarSpec.maxExposureCompensation =
@@ -818,6 +914,12 @@ public class CaptureModule extends CameraModule implements
         Matrix rotationMatrix = new Matrix();
         rotationMatrix.setRotate(mDisplayRotation, 0.5f, 0.5f);
         rotationMatrix.mapPoints(points);
+
+        // Invert X coordinate on front camera since the display is mirrored.
+        if (mCameraCharacteristics.getCameraDirection() == Facing.FRONT) {
+            points[0] = 1 - points[0];
+        }
+
         mCamera.triggerFocusAndMeterAtPoint(points[0], points[1]);
 
         // Log touch (screen coordinates).
@@ -858,14 +960,15 @@ public class CaptureModule extends CameraModule implements
                 startPassiveFocus();
                 break;
             case ACTIVE_SCAN:
+                // Unused, manual scans are triggered via the UI
                 break;
             case PASSIVE_FOCUSED:
             case PASSIVE_UNFOCUSED:
-                mFocusController.clearFocusIndicator();
+                // Unused
                 break;
             case ACTIVE_FOCUSED:
             case ACTIVE_UNFOCUSED:
-                mFocusController.clearFocusIndicator();
+                // Unused
                 break;
         }
 
@@ -906,10 +1009,6 @@ public class CaptureModule extends CameraModule implements
 
     @Override
     public void onReadyStateChanged(boolean readyForCapture) {
-        if (!mBurstController.isReady()) {
-            return;
-        }
-
         if (readyForCapture) {
             mAppController.getCameraAppUI().enableModeOptions();
         }
@@ -944,6 +1043,7 @@ public class CaptureModule extends CameraModule implements
 
     @Override
     public void onPictureTakingFailed() {
+        mAppController.getFatalErrorHandler().onMediaStorageFailure();
     }
 
     /**
@@ -961,17 +1061,6 @@ public class CaptureModule extends CameraModule implements
     }
 
     /**
-     * TODO: Remove this method once we are in pure CaptureModule land.
-     */
-    private String getBackFacingCameraId() {
-        if (!(mCameraManager instanceof OneCameraManagerImpl)) {
-            throw new IllegalStateException("This should never be called with Camera API V1");
-        }
-        OneCameraManagerImpl manager = (OneCameraManagerImpl) mCameraManager;
-        return manager.getFirstBackCameraId();
-    }
-
-    /**
      * @return Depending on whether we're in sticky-HDR mode or not, return the
      *         proper callback to be used for when the HDR/HDR+ button is
      *         pressed.
@@ -1052,6 +1141,9 @@ public class CaptureModule extends CameraModule implements
                         return;
                     }
 
+                    ButtonManager buttonManager = mAppController.getButtonManager();
+                    buttonManager.disableCameraButtonAndBlock();
+
                     // At the time this callback is fired, the camera id
                     // has be set to the desired camera.
                     mSettingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,
@@ -1059,7 +1151,7 @@ public class CaptureModule extends CameraModule implements
 
                     Log.d(TAG, "Start to switch camera. cameraId=" + cameraId);
                     mCameraFacing = getFacingFromCameraId(cameraId);
-                    updateCameraCharacteristics();
+                    mShowErrorAndFinish = !updateCameraCharacteristics();
                     switchCamera();
                 }
             };
@@ -1105,6 +1197,17 @@ public class CaptureModule extends CameraModule implements
         updatePreviewTransform(incomingWidth, incomingHeight, false);
     }
 
+    /**
+     * Returns whether it is necessary to apply device-specific fix for b/19271661
+     * on the AutoTransform Path, i.e. USE_AUTOTRANSFORM_UI_LAYOUT == true
+     *
+     * @return whether to apply workaround fix for b/19271661
+     */
+    private boolean requiresNexus4SpecificFixFor16By9Previews() {
+        return USE_AUTOTRANSFORM_UI_LAYOUT && ApiHelper.IS_NEXUS_4
+                && is16by9AspectRatio(mPictureSize);
+    }
+
     /***
      * Update the preview transform based on the new dimensions. TODO: Make work
      * with all: aspect ratios/resolutions x screens/cameras.
@@ -1137,11 +1240,46 @@ public class CaptureModule extends CameraModule implements
 
             // Get natural orientation and buffer dimensions
 
-            Matrix transformMatrix = mPreviewTransformCalculator.toTransformMatrix(
-                    new Size(mScreenWidth, mScreenHeight),
-                    new Size(mPreviewBufferWidth, mPreviewBufferHeight));
-            mAppController.updatePreviewTransform(transformMatrix);
+            if(USE_AUTOTRANSFORM_UI_LAYOUT) {
+                // Use PhotoUI-based AutoTransformation Interface
+                if (mPreviewBufferWidth != 0 && mPreviewBufferHeight != 0) {
+                    if (requiresNexus4SpecificFixFor16By9Previews()) {
+                        // Force preview size to be 16:9, even though surface is 4:3
+                        // Surface content is assumed to be 16:9.
+                        mAppController.updatePreviewAspectRatio(16.f / 9.f);
+                    } else {
+                        mAppController.updatePreviewAspectRatio(
+                                mPreviewBufferWidth / (float) mPreviewBufferHeight);
+                    }
+                }
+            } else {
+                Matrix transformMatrix = mPreviewTransformCalculator.toTransformMatrix(
+                        new Size(mScreenWidth, mScreenHeight),
+                        new Size(mPreviewBufferWidth, mPreviewBufferHeight));
+                mAppController.updatePreviewTransform(transformMatrix);
+            }
+        }
+    }
+
+
+    /**
+     * Calculates whether a picture size is 16:9 ratio, regardless of its
+     * orientation.
+     *
+     * @param size the size of the picture to be considered
+     * @return true, if the picture is 16:9; false if it's invalid or size is null
+     */
+    private boolean is16by9AspectRatio(Size size) {
+        if (size == null || size.getWidth() == 0 || size.getHeight() == 0) {
+            return false;
         }
+
+        // Normalize aspect ratio to be greater than 1.
+        final float aspectRatio = (size.getHeight() > size.getWidth())
+                ? (size.getHeight() / (float) size.getWidth())
+                : (size.getWidth() / (float) size.getHeight());
+
+        return Math.abs(aspectRatio - (16.f / 9.f)) < 0.001f;
     }
 
     /**
@@ -1157,6 +1295,16 @@ public class CaptureModule extends CameraModule implements
         Size previewBufferSize = mCamera.pickPreviewSize(mPictureSize, mContext);
         mPreviewBufferWidth = previewBufferSize.getWidth();
         mPreviewBufferHeight = previewBufferSize.getHeight();
+
+        // Workaround for N4 TextureView/HAL issues b/19271661 for 16:9 preview
+        // streams.
+        if (requiresNexus4SpecificFixFor16By9Previews()) {
+            // Override the preview selection logic to the largest N4 4:3
+            // preview size but pass in 16:9 aspect ratio in
+            // UpdatePreviewAspectRatio later.
+            mPreviewBufferWidth = 1280;
+            mPreviewBufferHeight = 960;
+        }
         updatePreviewBufferSize();
     }
 
@@ -1182,10 +1330,10 @@ public class CaptureModule extends CameraModule implements
 
         guard.mark("Acquired mCameraOpenCloseLock");
 
-        if (mCameraManager == null) {
+        if (mOneCameraOpener == null) {
             Log.e(TAG, "no available OneCameraManager, showing error dialog");
             mCameraOpenCloseLock.release();
-            mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
+            mAppController.getFatalErrorHandler().onGenericCameraAccessFailure();
             guard.stop("No OneCameraManager");
             return;
         }
@@ -1205,108 +1353,149 @@ public class CaptureModule extends CameraModule implements
         // Only enable GCam on the back camera
         boolean useHdr = mHdrPlusEnabled && mCameraFacing == Facing.BACK;
 
+        CameraId cameraId = mOneCameraManager.findFirstCameraFacing(mCameraFacing);
+        final String settingScope = SettingsManager.getCameraSettingScope(cameraId.getValue());
+
+        OneCameraCaptureSetting captureSetting;
         // Read the preferred picture size from the setting.
         try {
-            mPictureSize = mAppController.getResolutionSetting().getPictureSize(mCameraFacing);
+            mPictureSize = mAppController.getResolutionSetting().getPictureSize(
+                    cameraId, mCameraFacing);
+            captureSetting = OneCameraCaptureSetting.create(mPictureSize, mSettingsManager,
+                    getHardwareSpec(), settingScope, useHdr);
         } catch (OneCameraAccessException ex) {
-            mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
+            mAppController.getFatalErrorHandler().onGenericCameraAccessFailure();
             return;
         }
 
-        mCameraManager.open(mCameraFacing, useHdr, mPictureSize,
-                new OpenCallback() {
-                    @Override
-                    public void onFailure() {
-                        Log.e(TAG, "Could not open camera.");
-                        mCamera = null;
-                        mCameraOpenCloseLock.release();
-                        mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
-                    }
-
-                    @Override
-                    public void onCameraClosed() {
-                        mCamera = null;
-                        mCameraOpenCloseLock.release();
-                    }
-
-                    @Override
-                    public void onCameraOpened(final OneCamera camera) {
-                        Log.d(TAG, "onCameraOpened: " + camera);
-                        mCamera = camera;
-                        updatePreviewBufferDimension();
-
-                        // If the surface texture is not destroyed, it may have
-                        // the last frame lingering. We need to hold off setting
-                        // transform until preview is started.
-                        updatePreviewBufferSize();
-                        mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED;
-                        Log.d(TAG, "starting preview ...");
-
-                        // TODO: make mFocusController final and remove null
-                        // check.
-                        if (mFocusController != null) {
-                            camera.setFocusDistanceListener(mFocusController);
-                        }
-
-                        // TODO: Consider rolling these two calls into one.
-                        camera.startPreview(new Surface(getPreviewSurfaceTexture()),
-                                new CaptureReadyCallback() {
-                                    @Override
-                                    public void onSetupFailed() {
-                                        // We must release this lock here,
-                                        // before posting to the main handler
-                                        // since we may be blocked in pause(),
-                                        // getting ready to close the camera.
-                                        mCameraOpenCloseLock.release();
-                                        Log.e(TAG, "Could not set up preview.");
-                                        mMainThread.execute(new Runnable() {
-                                            @Override
-                                            public void run() {
-                                                if (mCamera == null) {
-                                                    Log.d(TAG, "Camera closed, aborting.");
-                                                    return;
-                                                }
-                                                mCamera.close();
-                                                mCamera = null;
-                                                // TODO: Show an error message
-                                                // and exit.
+        mOneCameraOpener.open(cameraId, captureSetting, mCameraHandler, mainThread,
+              imageRotationCalculator, mBurstController, mSoundPlayer,
+              new OpenCallback() {
+                  @Override
+                  public void onFailure() {
+                      Log.e(TAG, "Could not open camera.");
+                      // Sometimes the failure happens due to the controller
+                      // being in paused state but mCamera is already
+                      // initialized.  In these cases we just need to close the
+                      // camera device without showing the error dialog.
+                      // Application will properly reopen the camera on the next
+                      // resume operation (b/21025113).
+                      boolean isControllerPaused = mAppController.isPaused();
+                      if (mCamera != null) {
+                          mCamera.close();
+                      }
+                      mCamera = null;
+                      mCameraOpenCloseLock.release();
+                      if (!isControllerPaused) {
+                          mAppController.getFatalErrorHandler().onCameraOpenFailure();
+                      }
+                  }
+
+                  @Override
+                  public void onCameraClosed() {
+                      mCamera = null;
+                      mCameraOpenCloseLock.release();
+                  }
+
+                  @Override
+                  public void onCameraOpened(@Nonnull final OneCamera camera) {
+                      Log.d(TAG, "onCameraOpened: " + camera);
+                      mCamera = camera;
+
+                      // A race condition exists where the camera may be in the process
+                      // of opening (blocked), but the activity gets destroyed. If the
+                      // preview is initialized or callbacks are invoked on a destroyed
+                      // activity, bad things can happen.
+                      if (mAppController.isPaused()) {
+                          onFailure();
+                          return;
+                      }
+
+                      // When camera is opened, the zoom is implicitly reset to 1.0f
+                      mZoomValue = 1.0f;
+
+                      updatePreviewBufferDimension();
+
+                      // If the surface texture is not destroyed, it may have
+                      // the last frame lingering. We need to hold off setting
+                      // transform until preview is started.
+                      updatePreviewBufferSize();
+                      mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED;
+                      Log.d(TAG, "starting preview ...");
+
+                      // TODO: make mFocusController final and remove null
+                      // check.
+                      if (mFocusController != null) {
+                          camera.setFocusDistanceListener(mFocusController);
+                      }
+
+                      mMainThread.execute(new Runnable() {
+                          @Override
+                          public void run() {
+                              mAppController.getCameraAppUI().onChangeCamera();
+                              mAppController.getButtonManager().enableCameraButton();
+                          }
+                      });
+
+                      // TODO: Consider rolling these two calls into one.
+                      camera.startPreview(new Surface(getPreviewSurfaceTexture()),
+                            new CaptureReadyCallback() {
+                                @Override
+                                public void onSetupFailed() {
+                                    // We must release this lock here,
+                                    // before posting to the main handler
+                                    // since we may be blocked in pause(),
+                                    // getting ready to close the camera.
+                                    mCameraOpenCloseLock.release();
+                                    Log.e(TAG, "Could not set up preview.");
+                                    mMainThread.execute(new Runnable() {
+                                        @Override
+                                        public void run() {
+                                            if (mCamera == null) {
+                                                Log.d(TAG, "Camera closed, aborting.");
+                                                return;
                                             }
-                                        });
-                                    }
-
-                                    @Override
-                                    public void onReadyForCapture() {
-                                        // We must release this lock here,
-                                        // before posting to the main handler
-                                        // since we may be blocked in pause(),
-                                        // getting ready to close the camera.
-                                        mCameraOpenCloseLock.release();
-                                        mMainThread.execute(new Runnable() {
-                                            @Override
-                                            public void run() {
-                                                Log.d(TAG, "Ready for capture.");
-                                                if (mCamera == null) {
-                                                    Log.d(TAG, "Camera closed, aborting.");
-                                                    return;
-                                                }
-                                                onPreviewStarted();
-                                                // May be overridden by
-                                                // subsequent call to
-                                                // onReadyStateChanged().
-                                                onReadyStateChanged(true);
-                                                mCamera.setReadyStateChangedListener(
-                                                        CaptureModule.this);
-                                                // Enable zooming after preview
-                                                // has started.
-                                                mUI.initializeZoom(mCamera.getMaxZoom());
-                                                mCamera.setFocusStateListener(CaptureModule.this);
+                                            mCamera.close();
+                                            mCamera = null;
+                                            // TODO: Show an error message
+                                            // and exit.
+                                        }
+                                    });
+                                }
+
+                                @Override
+                                public void onReadyForCapture() {
+                                    // We must release this lock here,
+                                    // before posting to the main handler
+                                    // since we may be blocked in pause(),
+                                    // getting ready to close the camera.
+                                    mCameraOpenCloseLock.release();
+                                    mMainThread.execute(new Runnable() {
+                                        @Override
+                                        public void run() {
+                                            Log.d(TAG, "Ready for capture.");
+                                            if (mCamera == null) {
+                                                Log.d(TAG, "Camera closed, aborting.");
+                                                return;
                                             }
-                                        });
-                                    }
-                                });
-                        }
-                }, mCameraHandler, mainThread, imageRotationCalculator, mBurstController);
-        guard.stop("mCameraManager.open()");
+                                            onPreviewStarted();
+                                            // May be overridden by
+                                            // subsequent call to
+                                            // onReadyStateChanged().
+                                            onReadyStateChanged(true);
+                                            mCamera.setReadyStateChangedListener(
+                                                  CaptureModule.this);
+                                            // Enable zooming after preview
+                                            // has started.
+                                            mUI.initializeZoom(mCamera.getMaxZoom());
+                                            mCamera.setFocusStateListener(CaptureModule.this);
+                                        }
+                                    });
+                                }
+                            });
+                  }
+              }, mAppController.getFatalErrorHandler());
+        guard.stop("mOneCameraOpener.open()");
     }
 
     private void closeCamera() {
@@ -1334,6 +1523,9 @@ public class CaptureModule extends CameraModule implements
      * Re-initialize the camera if e.g. the HDR mode or facing property changed.
      */
     private void switchCamera() {
+        if (mShowErrorAndFinish) {
+            return;
+        }
         if (mPaused) {
             return;
         }
@@ -1342,13 +1534,6 @@ public class CaptureModule extends CameraModule implements
         initSurfaceTextureConsumer();
     }
 
-    private int getPreviewOrientation(int deviceOrientationDegrees) {
-        // Important: Camera2 buffers are already rotated to the natural
-        // orientation of the device (at least for the back-camera).
-
-        return (360 - deviceOrientationDegrees) % 360;
-    }
-
     /**
      * Returns which way around the camera is facing, based on it's ID.
      * <p>