diff options
author | Erin Dahlgren <edahlgren@google.com> | 2013-11-06 11:57:51 -0800 |
---|---|---|
committer | Erin Dahlgren <edahlgren@google.com> | 2013-11-07 10:02:55 -0800 |
commit | b09b53efcd179d55836014dcd054cff6f7d688d8 (patch) | |
tree | 4344716326c40cce060215dba89a5f741de33cf0 | |
parent | eb605b4a27be212915db5bc23e9a82b857036a1c (diff) | |
download | android_packages_apps_Snap-b09b53efcd179d55836014dcd054cff6f7d688d8.tar.gz android_packages_apps_Snap-b09b53efcd179d55836014dcd054cff6f7d688d8.tar.bz2 android_packages_apps_Snap-b09b53efcd179d55836014dcd054cff6f7d688d8.zip |
Parallelize opening the camera with view handling in photo mode.
Bug: 11255097
Change-Id: I8da16a97ee46555267ae8cfee0e7940d3f53f98f
-rw-r--r-- | src/com/android/camera/PhotoModule.java | 239 | ||||
-rw-r--r-- | src/com/android/camera/PhotoUI.java | 33 |
2 files changed, 192 insertions, 80 deletions
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java index 9523c9429..ce0deed54 100644 --- a/src/com/android/camera/PhotoModule.java +++ b/src/com/android/camera/PhotoModule.java @@ -40,6 +40,7 @@ import android.net.Uri; 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 @@ public class PhotoModule 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 @@ public class PhotoModule 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 @@ public class PhotoModule } /** + * 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 @@ public class PhotoModule 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 @@ public class PhotoModule @Override public void onPreviewUIReady() { - startPreview(); + // Requires that OPEN_CAMERA_ASYNC has been already sent. + mOpenCameraHandler.sendEmptyMessage(START_PREVIEW_ASYNC); } @Override @@ -453,7 +546,11 @@ public class PhotoModule 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 @@ public class PhotoModule // 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 @@ public class PhotoModule 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 @@ public class PhotoModule 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 @@ public class PhotoModule // 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 @@ public class PhotoModule } private void closeCamera() { + Log.v(TAG, "closeCamera"); if (mCameraDevice != null) { mCameraDevice.setZoomChangeListener(null); mCameraDevice.setFaceDetectionCallback(null, null); @@ -1473,48 +1566,58 @@ public class PhotoModule startPreview(); } - // This can only be called by UI Thread. private void startPreview() { - if (mPaused || mCameraDevice == null) { + if (mCameraState != PREVIEW_STOPPED) { + Log.v(TAG, "Already previewing"); 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."); + + if (mPaused || mCameraDevice == null) { 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(); + // 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; + } - 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(); + if (!mCameraPreviewParamsReady) { + Log.w(TAG, "startPreview: parameters for preview 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); + mCameraDevice.setErrorCallback(mErrorCallback); - Log.v(TAG, "startPreview"); - mCameraDevice.startPreview(); - mFocusManager.onPreviewStarted(); - onPreviewStarted(); + setDisplayOrientation(); - if (mSnapshotOnIdle) { - mHandler.post(mDoSnapRunnable); + 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 ffb19d4e1..524cd4654 100644 --- a/src/com/android/camera/PhotoUI.java +++ b/src/com/android/camera/PhotoUI.java @@ -117,6 +117,7 @@ public class PhotoUI implements PieListener, 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 @@ public class PhotoUI implements PieListener, 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 @@ public class PhotoUI implements PieListener, @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 |