Parallelize opening the camera with view handling in photo mode.

Bug: 11255097
Change-Id: I8da16a97ee46555267ae8cfee0e7940d3f53f98f
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index 9523c94..ce0deed 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -40,6 +40,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
@@ -103,6 +104,10 @@
     private static final int OPEN_CAMERA_FAIL = 9;
     private static final int CAMERA_DISABLED = 10;
     private static final int SWITCH_TO_GCAM_MODULE = 11;
+    private static final int CAMERA_PREVIEW_DONE = 12;
+
+    private static final int OPEN_CAMERA_ASYNC = 1;
+    private static final int START_PREVIEW_ASYNC = 2;
 
     // The subset of parameters we need to update in setCameraParameters().
     private static final int UPDATE_PARAM_INITIALIZE = 1;
@@ -238,6 +243,14 @@
     private String mSceneMode;
 
     private final Handler mHandler = new MainHandler();
+
+    /** A thread separate from the UI thread for camera startup. */
+    private HandlerThread mOpenCameraThread;
+    /** A handler to run on the camera startup thread. */
+    private Handler mOpenCameraHandler;
+    /** This lock should always protect openCamera and closeCamera. */
+    private final Object mCameraOpenLock = new Object();
+
     private PreferenceGroup mPreferenceGroup;
 
     private boolean mQuickCapture;
@@ -280,10 +293,83 @@
     }
 
     /**
+     * This Handler is used to open the camera.
+     */
+    private class OpenCameraHandler extends Handler {
+        public OpenCameraHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case OPEN_CAMERA_ASYNC: {
+                    // Prevent closeCamera from thinking the camera
+                    // is already closed when it is still opening.
+                    //
+                    // This happens during the lockscreen sequence:
+                    // onResume -> onPause -> onResume.
+                    synchronized (mCameraOpenLock) {
+                        Log.v(TAG, "openCamera");
+                        if (mCameraDevice != null) {
+                            throw new IllegalArgumentException("Camera already open.");
+                        }
+
+                        mCameraDevice = CameraUtil.openCamera(
+                            mActivity, mCameraId, mHandler,
+                            mActivity.getCameraOpenErrorCallback());
+
+                        if (mCameraDevice == null) {
+                            Log.e(TAG, "Failed to open camera:" + mCameraId);
+                            break;
+                        }
+                        mParameters = mCameraDevice.getParameters();
+
+                        initializeCapabilities();
+                        if (mFocusManager == null) {
+                            initializeFocusManager();
+                        }
+
+                        // The views can't be updated from a non UI thread.
+                        mHandler.sendEmptyMessage(CAMERA_OPEN_DONE);
+
+                        setCameraParameters(UPDATE_PARAM_ALL);
+                        mCameraPreviewParamsReady = true;
+
+                        // This will exit early if the surface texture
+                        // isn't ready. We also need to protect the surface
+                        // texture from concurrent updates/checks.
+                        startPreview();
+                    }
+                    break;
+                }
+
+                case START_PREVIEW_ASYNC: {
+                    if (mCameraDevice == null) {
+                        throw new IllegalStateException("Camera not yet opened.");
+                    }
+
+                    startPreview();
+                    mHandler.sendEmptyMessage(CAMERA_PREVIEW_DONE);
+                    break;
+                }
+
+                default: {
+                    throw new UnsupportedOperationException("Unknown message " + msg.what);
+                }
+            }
+        };
+    }
+
+    /**
      * This Handler is used to post message back onto the main thread of the
      * application
      */
     private class MainHandler extends Handler {
+        public MainHandler() {
+            super(Looper.getMainLooper());
+        }
+
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -345,6 +431,12 @@
 
                 case SWITCH_TO_GCAM_MODULE: {
                     mActivity.onModuleSelected(ModuleSwitcher.GCAM_MODULE_INDEX);
+                    break;
+                }
+
+                case CAMERA_PREVIEW_DONE: {
+                    // Modifies views, so must be executed on the UI thread.
+                    onPreviewStarted();
                 }
             }
         }
@@ -413,7 +505,8 @@
 
     @Override
     public void onPreviewUIReady() {
-        startPreview();
+        // Requires that OPEN_CAMERA_ASYNC has been already sent.
+        mOpenCameraHandler.sendEmptyMessage(START_PREVIEW_ASYNC);
     }
 
     @Override
@@ -453,7 +546,11 @@
         setCameraId(mCameraId);
 
         // from onPause
-        closeCamera();
+        mOpenCameraHandler.removeMessages(OPEN_CAMERA_ASYNC);
+        mOpenCameraHandler.removeMessages(START_PREVIEW_ASYNC);
+        synchronized (mCameraOpenLock) {
+            closeCamera();
+        }
         mUI.collapseCameraControls();
         mUI.clearFaces();
         if (mFocusManager != null) mFocusManager.removeMessages();
@@ -461,9 +558,12 @@
         // Restart the camera and initialize the UI. From onCreate.
         mPreferences.setLocalId(mActivity, mCameraId);
         CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
-        mCameraDevice = CameraUtil.openCamera(
+        synchronized (mCameraOpenLock) {
+            Log.v(TAG, "openCamera");
+            mCameraDevice = CameraUtil.openCamera(
                 mActivity, mCameraId, mHandler,
                 mActivity.getCameraOpenErrorCallback());
+        }
         if (mCameraDevice == null) {
             Log.e(TAG, "Failed to open camera:" + mCameraId + ", aborting.");
             return;
@@ -1161,47 +1261,25 @@
         mPaused = false;
     }
 
-    /**
-     * Opens the camera device.
-     *
-     * @return Whether the camera was opened successfully.
-     */
-    private boolean prepareCamera() {
-        // We need to check whether the activity is paused before long
-        // operations to ensure that onPause() can be done ASAP.
-        mCameraDevice = CameraUtil.openCamera(
-                mActivity, mCameraId, mHandler,
-                mActivity.getCameraOpenErrorCallback());
-        if (mCameraDevice == null) {
-            Log.e(TAG, "Failed to open camera:" + mCameraId);
-            return false;
-        }
-        mParameters = mCameraDevice.getParameters();
-
-        initializeCapabilities();
-        if (mFocusManager == null) initializeFocusManager();
-        setCameraParameters(UPDATE_PARAM_ALL);
-        mHandler.sendEmptyMessage(CAMERA_OPEN_DONE);
-        mCameraPreviewParamsReady = true;
-        startPreview();
-        mOnResumeTime = SystemClock.uptimeMillis();
-        checkDisplayRotation();
-        return true;
-    }
-
-
     @Override
     public void onResumeAfterSuper() {
         Log.v(TAG, "On resume.");
         if (mOpenCameraFail || mCameraDisabled) return;
 
+        if (mOpenCameraThread == null) {
+            Log.e("DEBUG", "new OpenCameraThread");
+            mOpenCameraThread = new HandlerThread("OpenCameraThread");
+            mOpenCameraThread.start();
+            mOpenCameraHandler = new OpenCameraHandler(mOpenCameraThread.getLooper());
+        }
+
         mJpegPictureCallbackTime = 0;
         mZoomValue = 0;
         resetExposureCompensation();
-        if (!prepareCamera()) {
-            // Camera failure.
-            return;
-        }
+
+        mOpenCameraHandler.sendEmptyMessage(OPEN_CAMERA_ASYNC);
+        mOnResumeTime = SystemClock.uptimeMillis();
+        checkDisplayRotation();
 
         // If first time initialization is not finished, put it in the
         // message queue.
@@ -1259,6 +1337,9 @@
         if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
             mCameraDevice.cancelAutoFocus();
         }
+        // If the camera has not been opened asynchronously yet,
+        // and startPreview hasn't been called, then this is a no-op.
+        // (e.g. onResume -> onPause -> onResume).
         stopPreview();
 
         mNamedImages = null;
@@ -1272,7 +1353,18 @@
         // Remove the messages and runnables in the queue.
         mHandler.removeCallbacksAndMessages(null);
 
-        closeCamera();
+        // Postpones actually releasing for KEEP_CAMERA_TIMEOUT,
+        // so if onResume is directly called after this, the camera
+        // simply needs to reconnect (takes about 2-5ms).
+        mOpenCameraHandler.removeMessages(OPEN_CAMERA_ASYNC);
+        mOpenCameraHandler.removeMessages(START_PREVIEW_ASYNC);
+        synchronized (mCameraOpenLock) {
+            closeCamera();
+        }
+        // Stop the long running open camera thread.
+        mOpenCameraThread.quitSafely();
+        mOpenCameraThread = null;
+        Log.e(TAG, "Done quiting safely.");
 
         resetScreenOn();
         mUI.onPause();
@@ -1434,6 +1526,7 @@
     }
 
     private void closeCamera() {
+        Log.v(TAG, "closeCamera");
         if (mCameraDevice != null) {
             mCameraDevice.setZoomChangeListener(null);
             mCameraDevice.setFaceDetectionCallback(null, null);
@@ -1473,48 +1566,58 @@
         startPreview();
     }
 
-    // This can only be called by UI Thread.
     private void startPreview() {
+        if (mCameraState != PREVIEW_STOPPED) {
+            Log.v(TAG, "Already previewing");
+            return;
+        }
+
         if (mPaused || mCameraDevice == null) {
             return;
         }
-        SurfaceTexture st = mUI.getSurfaceTexture();
-        if (st == null) {
-            Log.w(TAG, "startPreview: surfaceTexture is not ready.");
-            return;
-        }
-        if (!mCameraPreviewParamsReady) {
-            Log.w(TAG, "startPreview: parameters for preview is not ready.");
-            return;
-        }
-        mCameraDevice.setErrorCallback(mErrorCallback);
 
-        // ICS camera frameworks has a bug. Face detection state is not cleared
-        // after taking a picture. Stop the preview to work around it. The bug
-        // was fixed in JB.
-        if (mCameraState != PREVIEW_STOPPED) stopPreview();
+        Object textureLock = mUI.getSurfaceTextureLock();
 
-        setDisplayOrientation();
-
-        if (!mSnapshotOnIdle) {
-            // If the focus mode is continuous autofocus, call cancelAutoFocus to
-            // resume it because it may have been paused by autoFocus call.
-            if (CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusManager.getFocusMode())) {
-                mCameraDevice.cancelAutoFocus();
+         // Any decisions we make based on the surface texture state
+         // need to be protected.
+        synchronized (textureLock) {
+            SurfaceTexture st = mUI.getSurfaceTexture();
+            if (st == null) {
+                Log.w(TAG, "startPreview: surfaceTexture is not ready.");
+                return;
             }
-            mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
-        }
-        setCameraParameters(UPDATE_PARAM_ALL);
-        // Let UI set its expected aspect ratio
-        mCameraDevice.setPreviewTexture(st);
 
-        Log.v(TAG, "startPreview");
-        mCameraDevice.startPreview();
-        mFocusManager.onPreviewStarted();
-        onPreviewStarted();
+            if (!mCameraPreviewParamsReady) {
+                Log.w(TAG, "startPreview: parameters for preview is not ready.");
+                return;
+            }
+            mCameraDevice.setErrorCallback(mErrorCallback);
 
-        if (mSnapshotOnIdle) {
-            mHandler.post(mDoSnapRunnable);
+            setDisplayOrientation();
+
+            if (!mSnapshotOnIdle) {
+                // If the focus mode is continuous autofocus, call cancelAutoFocus to
+                // resume it because it may have been paused by autoFocus call.
+                if (CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusManager.getFocusMode())) {
+                    mCameraDevice.cancelAutoFocus();
+                }
+                mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
+            }
+            setCameraParameters(UPDATE_PARAM_ALL);
+            // Let UI set its expected aspect ratio
+            mCameraDevice.setPreviewTexture(st);
+
+            Log.v(TAG, "startPreview");
+            mCameraDevice.startPreview();
+
+            // Since the preview actually started, remove any messages to
+            // start it again.
+            mOpenCameraHandler.removeMessages(START_PREVIEW_ASYNC);
+            mFocusManager.onPreviewStarted();
+
+            if (mSnapshotOnIdle) {
+                mHandler.post(mDoSnapRunnable);
+            }
         }
     }
 
diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java
index ffb19d4..524cd46 100644
--- a/src/com/android/camera/PhotoUI.java
+++ b/src/com/android/camera/PhotoUI.java
@@ -117,6 +117,7 @@
     private Matrix mMatrix = null;
     private float mAspectRatio = 4f / 3f;
     private View mPreviewCover;
+    private final Object mSurfaceTextureLock = new Object();
 
     public interface SurfaceTextureSizeChangedListener {
         public void onSurfaceTextureSizeChanged(int uncroppedWidth, int uncroppedHeight);
@@ -277,16 +278,22 @@
         mController.onPreviewRectChanged(CameraUtil.rectFToRect(previewRect));
     }
 
+    protected Object getSurfaceTextureLock() {
+        return mSurfaceTextureLock;
+    }
+
     @Override
     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
-        Log.v(TAG, "SurfaceTexture ready.");
-        mPreviewCover.setVisibility(View.GONE);
-        mSurfaceTexture = surface;
-        mController.onPreviewUIReady();
-        // Workaround for b/11168275, see b/10981460 for more details
-        if (mPreviewWidth != 0 && mPreviewHeight != 0) {
-            // Re-apply transform matrix for new surface texture
-            setTransformMatrix(mPreviewWidth, mPreviewHeight);
+        synchronized (mSurfaceTextureLock) {
+            Log.v(TAG, "SurfaceTexture ready.");
+            mPreviewCover.setVisibility(View.GONE);
+            mSurfaceTexture = surface;
+            mController.onPreviewUIReady();
+            // Workaround for b/11168275, see b/10981460 for more details
+            if (mPreviewWidth != 0 && mPreviewHeight != 0) {
+                // Re-apply transform matrix for new surface texture
+                setTransformMatrix(mPreviewWidth, mPreviewHeight);
+            }
         }
     }
 
@@ -297,10 +304,12 @@
 
     @Override
     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
-        mSurfaceTexture = null;
-        mController.onPreviewUIDestroyed();
-        Log.w(TAG, "SurfaceTexture destroyed");
-        return true;
+        synchronized (mSurfaceTextureLock) {
+            mSurfaceTexture = null;
+            mController.onPreviewUIDestroyed();
+            Log.w(TAG, "SurfaceTexture destroyed");
+            return true;
+        }
     }
 
     @Override