From 1c1d1afb5701b78fcdf09969bba1c20eb591bccf Mon Sep 17 00:00:00 2001 From: Alan Newberger Date: Mon, 29 Sep 2014 16:58:57 -0700 Subject: Remove default thumbnail size setting in portability lib Settings thumbnail size to (0,0) suppresses thumbnail generation. The Camera app did not set to (0,0) explicitly, but when porting Camera.Parameters to CameraSettings this crept in. Causes issue on Nakasi and no reason to suppress thumbnails in general. Bug: 17700333 Change-Id: I486e560bc9aaf05daf019a3aad8de78ee306f21b --- .../portability/AndroidCameraAgentImpl.java | 4 +++- .../ex/camera2/portability/CameraSettings.java | 22 ++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java index 992fcec..6a764f3 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java @@ -670,7 +670,9 @@ class AndroidCameraAgentImpl extends CameraAgent { } parameters.setRecordingHint(settings.isRecordingHintEnabled()); Size jpegThumbSize = settings.getExifThumbnailSize(); - parameters.setJpegThumbnailSize(jpegThumbSize.width(), jpegThumbSize.height()); + if (jpegThumbSize != null) { + parameters.setJpegThumbnailSize(jpegThumbSize.width(), jpegThumbSize.height()); + } parameters.setPictureFormat(settings.getCurrentPhotoFormat()); CameraSettings.GpsData gpsData = settings.getGpsData(); diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java index 87e9adf..ccd980a 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java @@ -58,7 +58,7 @@ public abstract class CameraSettings { protected boolean mAutoWhiteBalanceLocked; protected boolean mRecordingHintEnabled; protected GpsData mGpsData; - protected Size mExifThumbnailSize = new Size(0,0); + protected Size mExifThumbnailSize; /** * An immutable class storing GPS related information. @@ -491,20 +491,22 @@ public abstract class CameraSettings { } /** - * Sets the size of the thumbnail in EXIF header. + * Sets the size of the thumbnail in EXIF header. To suppress thumbnail + * generation, set a size of (0,0). * - * @param s The size for the thumbnail. {@code null} will clear the size to - * (0,0). + * @param s The size for the thumbnail. If {@code null}, agent will not + * set a thumbnail size. */ public void setExifThumbnailSize(Size s) { - if (s != null) { - mExifThumbnailSize = s; - } else { - mExifThumbnailSize = new Size(0,0); - } + mExifThumbnailSize = s; } + /** + * Gets the size of the thumbnail in EXIF header. + * + * @return desired thumbnail size, or null if no size was set + */ public Size getExifThumbnailSize() { - return new Size(mExifThumbnailSize); + return (mExifThumbnailSize == null) ? null : new Size(mExifThumbnailSize); } } -- cgit v1.2.3 From 698997b9ef47f215350f03c0289f291846cd3604 Mon Sep 17 00:00:00 2001 From: Puneet Lall Date: Fri, 3 Oct 2014 11:40:30 -0700 Subject: Add null check for exif thumbnail size Bug:17790747 Change-Id: I758bb217d0a76093d8e4bb1832cc7ab6907235ee --- .../ex/camera2/portability/AndroidCamera2Settings.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java index 540d8df..0062097 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java @@ -228,6 +228,11 @@ public class AndroidCamera2Settings extends CameraSettings { } else if (setting == CONTROL_AWB_LOCK) { return Objects.equals(mAutoWhiteBalanceLocked, mTemplateSettings.get(CONTROL_AWB_LOCK)); } else if (setting == JPEG_THUMBNAIL_SIZE) { + if (mExifThumbnailSize == null) { + // It doesn't matter if this is true or false since setting this + // to null in the request settings will use the default anyway. + return false; + } android.util.Size defaultThumbnailSize = mTemplateSettings.get(JPEG_THUMBNAIL_SIZE); return (mExifThumbnailSize.width() == 0 && mExifThumbnailSize.height() == 0) || (defaultThumbnailSize != null && @@ -273,9 +278,13 @@ public class AndroidCamera2Settings extends CameraSettings { updateRequestSettingOrForceToDefault(CONTROL_AWB_LOCK, mAutoWhiteBalanceLocked); // TODO: mRecordingHintEnabled updateRequestGpsData(); - updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE, - new android.util.Size( - mExifThumbnailSize.width(), mExifThumbnailSize.height())); + if (mExifThumbnailSize != null) { + updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE, + new android.util.Size( + mExifThumbnailSize.width(), mExifThumbnailSize.height())); + } else { + updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE, null); + } return mRequestSettings; } -- cgit v1.2.3 From 90e75d1f90b85e11f6905966ea28d59916935c68 Mon Sep 17 00:00:00 2001 From: Alan Newberger Date: Sun, 5 Oct 2014 12:41:11 -0700 Subject: Check isZoomSupported before any zoom ratio calculations. Per API doc, isZoomeSupported should be checked before calling getZoomRatios or getMaxZoom. Moving zoom calculation into isZoomSupported check fixed crash in front camera with no zoom support, where a HAL/fwk returns a null list of zoom ratios. Bug: 17863266 Change-Id: I80b5f7a431e7bbe4d50e5d062628e38eda3fd0a6 --- .../com/android/ex/camera2/portability/AndroidCameraCapabilities.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraCapabilities.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraCapabilities.java index 84b44e6..9892d4a 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraCapabilities.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraCapabilities.java @@ -47,7 +47,6 @@ class AndroidCameraCapabilities extends CameraCapabilities { mPreferredPreviewSizeForVideo = new Size(p.getPreferredPreviewSizeForVideo()); mSupportedPreviewFormats.addAll(p.getSupportedPreviewFormats()); mSupportedPhotoFormats.addAll(p.getSupportedPictureFormats()); - mMaxZoomRatio = p.getZoomRatios().get(p.getMaxZoom()) / ZOOM_MULTIPLIER; mHorizontalViewAngle = p.getHorizontalViewAngle(); mVerticalViewAngle = p.getVerticalViewAngle(); buildPreviewFpsRange(p); @@ -60,6 +59,7 @@ class AndroidCameraCapabilities extends CameraCapabilities { buildWhiteBalances(p); if (p.isZoomSupported()) { + mMaxZoomRatio = p.getZoomRatios().get(p.getMaxZoom()) / ZOOM_MULTIPLIER; mSupportedFeatures.add(Feature.ZOOM); } if (p.isVideoSnapshotSupported()) { -- cgit v1.2.3 From 2d01b8e2b5ad30ce3c442799cad4c3d26607fc6f Mon Sep 17 00:00:00 2001 From: Senpo Hu Date: Mon, 6 Oct 2014 10:36:55 -0700 Subject: Overhaul error handling in AndroidCameraAgentImpl. DispatchThread could throw RTE when timeout (2500 ms). This CL extends it to 3500 ms and also re-route the exception to the proper exception callback instead of surfacing that up to the app. Since a lot of try-catch block is added, CameraExceptionHandler class is added to keep CameraAgent.java clean. The portability layer should ensure the proper use of Camera API 1. With this assumption, any run time exception thrown by the framework should be considered fatal. Once it happened, CameraAgent/CameraProxy should be invalidated. Any subsequent camera commands should be gated as no-ops to prevent HAL hanging symptom. Users should be guided to exit the app and recover from the camera error. Design Doc: https://docs.google.com/a/google.com/document/d/1t0sEPwGgc387XcoCVC7kT9Y_RSZPxcayla03M4bdn0U/edit Corresponding CL for the app layer: ag/562383 Bug: 16189216 Change-Id: I33e69aeff0c8e4211e82e0e83133915700c4eb8f --- .../portability/AndroidCamera2AgentImpl.java | 170 ++++++---- .../portability/AndroidCameraAgentImpl.java | 318 ++++++++++-------- .../ex/camera2/portability/CameraAgent.java | 367 +++++++++++++-------- .../portability/CameraExceptionHandler.java | 101 ++++++ .../ex/camera2/portability/CameraStateHolder.java | 33 ++ 5 files changed, 653 insertions(+), 336 deletions(-) create mode 100644 camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java index a746634..ae4c208 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java @@ -65,6 +65,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { private final DispatchThread mDispatchThread; private final CameraManager mCameraManager; private final MediaActionSound mNoisemaker; + private CameraExceptionHandler mExceptionHandler; /** * Number of camera devices. The length of {@code mCameraDevices} does not reveal this @@ -86,6 +87,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { mCameraHandlerThread = new HandlerThread("Camera2 Handler Thread"); mCameraHandlerThread.start(); mCameraHandler = new Camera2Handler(mCameraHandlerThread.getLooper()); + mExceptionHandler = new CameraExceptionHandler(mCameraHandler); mCameraState = new AndroidCamera2StateHolder(); mDispatchThread = new DispatchThread(mCameraHandler, mCameraHandlerThread); mDispatchThread.start(); @@ -132,11 +134,6 @@ class AndroidCamera2AgentImpl extends CameraAgent { } } - // TODO: Implement - @Override - public void setCameraDefaultExceptionCallback(CameraExceptionCallback callback, - Handler handler) {} - // TODO: Implement @Override public void recycle() {} @@ -159,6 +156,21 @@ class AndroidCamera2AgentImpl extends CameraAgent { return mDispatchThread; } + @Override + protected CameraStateHolder getCameraState() { + return mCameraState; + } + + @Override + protected CameraExceptionHandler getCameraExceptionHandler() { + return mExceptionHandler; + } + + @Override + public void setCameraExceptionHandler(CameraExceptionHandler exceptionHandler) { + mExceptionHandler = exceptionHandler; + } + private static abstract class CaptureAvailableListener extends CameraCaptureSession.CaptureCallback implements ImageReader.OnImageAvailableListener {}; @@ -645,11 +657,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { } if (ex instanceof RuntimeException) { - post(new Runnable() { - @Override - public void run() { - sCameraExceptionCallback.onCameraException((RuntimeException) ex); - }}); + mExceptionHandler.onCameraException((RuntimeException) ex); } } finally { WaitDoneBundle.unblockSyncWaiters(msg); @@ -771,8 +779,10 @@ class AndroidCamera2AgentImpl extends CameraAgent { try { CameraCharacteristics props = mCameraManager.getCameraCharacteristics(mCameraId); - mCameraProxy = new AndroidCamera2ProxyImpl(mCameraIndex, mCamera, - getCameraDeviceInfo().getCharacteristics(mCameraIndex), props); + CameraDeviceInfo.Characteristics characteristics = + getCameraDeviceInfo().getCharacteristics(mCameraIndex); + mCameraProxy = new AndroidCamera2ProxyImpl(AndroidCamera2AgentImpl.this, + mCameraIndex, mCamera, characteristics, props); mPersistentSettings = new Camera2RequestSettingsSet(); mActiveArray = props.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); @@ -960,6 +970,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { } private class AndroidCamera2ProxyImpl extends CameraAgent.CameraProxy { + private final AndroidCamera2AgentImpl mCameraAgent; private final int mCameraIndex; private final CameraDevice mCamera; private final CameraDeviceInfo.Characteristics mCharacteristics; @@ -967,9 +978,13 @@ class AndroidCamera2AgentImpl extends CameraAgent { private CameraSettings mLastSettings; private boolean mShutterSoundEnabled; - public AndroidCamera2ProxyImpl(int cameraIndex, CameraDevice camera, + public AndroidCamera2ProxyImpl( + AndroidCamera2AgentImpl agent, + int cameraIndex, + CameraDevice camera, CameraDeviceInfo.Characteristics characteristics, CameraCharacteristics properties) { + mCameraAgent = agent; mCameraIndex = cameraIndex; mCamera = camera; mCharacteristics = characteristics; @@ -997,6 +1012,10 @@ class AndroidCamera2AgentImpl extends CameraAgent { return mCapabilities; } + public CameraAgent getAgent() { + return mCameraAgent; + } + private AndroidCamera2Capabilities getSpecializedCapabilities() { return mCapabilities; } @@ -1039,53 +1058,67 @@ class AndroidCamera2AgentImpl extends CameraAgent { @Override public void autoFocus(final Handler handler, final CameraAFCallback cb) { - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - CameraAFCallback cbForward = null; - if (cb != null) { - cbForward = new CameraAFCallback() { - @Override - public void onAutoFocus(final boolean focused, - final CameraProxy camera) { - handler.post(new Runnable() { - @Override - public void run() { - cb.onAutoFocus(focused, camera); - }}); - }}; - } + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + CameraAFCallback cbForward = null; + if (cb != null) { + cbForward = new CameraAFCallback() { + @Override + public void onAutoFocus(final boolean focused, + final CameraProxy camera) { + handler.post(new Runnable() { + @Override + public void run() { + cb.onAutoFocus(focused, camera); + } + }); + } + }; + } - mCameraState.waitForStates(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE | - AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); - mCameraHandler.obtainMessage(CameraActions.AUTO_FOCUS, cbForward) - .sendToTarget(); - }}); + mCameraState.waitForStates(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE | + AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); + mCameraHandler.obtainMessage(CameraActions.AUTO_FOCUS, cbForward) + .sendToTarget(); + } + }); + } catch (RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public void setAutoFocusMoveCallback(final Handler handler, final CameraAFMoveCallback cb) { - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - CameraAFMoveCallback cbForward = null; - if (cb != null) { - cbForward = new CameraAFMoveCallback() { - @Override - public void onAutoFocusMoving(final boolean moving, - final CameraProxy camera) { - handler.post(new Runnable() { - @Override - public void run() { - cb.onAutoFocusMoving(moving, camera); - }}); - }}; - } + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + CameraAFMoveCallback cbForward = null; + if (cb != null) { + cbForward = new CameraAFMoveCallback() { + @Override + public void onAutoFocusMoving(final boolean moving, + final CameraProxy camera) { + handler.post(new Runnable() { + @Override + public void run() { + cb.onAutoFocusMoving(moving, camera); + } + }); + } + }; + } - mCameraHandler.obtainMessage(CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK, - cbForward).sendToTarget(); - }}); + mCameraHandler.obtainMessage(CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK, + cbForward).sendToTarget(); + } + }); + } catch (RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @Override @@ -1127,15 +1160,20 @@ class AndroidCamera2AgentImpl extends CameraAgent { } } }}; - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - // Wait until PREVIEW_ACTIVE or better - mCameraState.waitForStates( - ~(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE - 1)); - mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO, picListener) - .sendToTarget(); - }}); + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + // Wait until PREVIEW_ACTIVE or better + mCameraState.waitForStates( + ~(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE - 1)); + mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO, picListener) + .sendToTarget(); + } + }); + } catch (RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } // TODO: Implement @@ -1380,12 +1418,4 @@ class AndroidCamera2AgentImpl extends CameraAgent { } } } - - private static final CameraExceptionCallback sCameraExceptionCallback = - new CameraExceptionCallback() { - @Override - public synchronized void onCameraException(RuntimeException e) { - throw e; - } - }; } diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java index 6a764f3..8e17276 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java @@ -55,39 +55,23 @@ class AndroidCameraAgentImpl extends CameraAgent { private final HandlerThread mCameraHandlerThread; private final CameraStateHolder mCameraState; private final DispatchThread mDispatchThread; - - private Handler mCameraExceptionCallbackHandler; - private CameraExceptionCallback mCameraExceptionCallback = - new CameraExceptionCallback() { - @Override - public void onCameraException(RuntimeException e) { - throw e; - } - }; + private CameraExceptionHandler mExceptionHandler; AndroidCameraAgentImpl() { mCameraHandlerThread = new HandlerThread("Camera Handler Thread"); mCameraHandlerThread.start(); - mCameraHandler = new CameraHandler(mCameraHandlerThread.getLooper()); - mCameraExceptionCallbackHandler = mCameraHandler; + mCameraHandler = new CameraHandler(this, mCameraHandlerThread.getLooper()); + mExceptionHandler = new CameraExceptionHandler(mCameraHandler); mCameraState = new AndroidCameraStateHolder(); mDispatchThread = new DispatchThread(mCameraHandler, mCameraHandlerThread); mDispatchThread.start(); } - @Override - public void setCameraDefaultExceptionCallback(CameraExceptionCallback callback, - Handler handler) { - synchronized (mCameraExceptionCallback) { - mCameraExceptionCallback = callback; - mCameraExceptionCallbackHandler = handler; - } - } - @Override public void recycle() { closeCamera(null, true); mDispatchThread.end(); + mCameraState.invalidate(); } @Override @@ -105,6 +89,21 @@ class AndroidCameraAgentImpl extends CameraAgent { return mDispatchThread; } + @Override + protected CameraStateHolder getCameraState() { + return mCameraState; + } + + @Override + protected CameraExceptionHandler getCameraExceptionHandler() { + return mExceptionHandler; + } + + @Override + public void setCameraExceptionHandler(CameraExceptionHandler exceptionHandler) { + mExceptionHandler = exceptionHandler; + } + private static class AndroidCameraDeviceInfo implements CameraDeviceInfo { private final Camera.CameraInfo[] mCameraInfos; private final int mNumberOfCameras; @@ -238,7 +237,7 @@ class AndroidCameraAgentImpl extends CameraAgent { * The handler on which the actual camera operations happen. */ private class CameraHandler extends HistoryHandler { - + private CameraAgent mAgent; private Camera mCamera; private int mCameraId; private ParametersCache mParameterCache; @@ -259,8 +258,9 @@ class AndroidCameraAgentImpl extends CameraAgent { } } - CameraHandler(Looper looper) { + CameraHandler(CameraAgent agent, Looper looper) { super(looper); + mAgent = agent; } private void startFaceDetection() { @@ -298,16 +298,6 @@ class AndroidCameraAgentImpl extends CameraAgent { } } - private void capture(final CaptureCallbacks cb) { - try { - mCamera.takePicture(cb.mShutter, cb.mRaw, cb.mPostView, cb.mJpeg); - } catch (RuntimeException e) { - // TODO: output camera state and focus state for debugging. - Log.e(TAG, "take picture failed."); - throw e; - } - } - public void requestTakePicture( final ShutterCallback shutter, final PictureCallback raw, @@ -324,7 +314,13 @@ class AndroidCameraAgentImpl extends CameraAgent { @Override public void handleMessage(final Message msg) { super.handleMessage(msg); + + if (getCameraState().isInvalid()) { + Log.v(TAG, "Skip handleMessage - action = '" + CameraActions.stringify(msg.what) + "'"); + return; + } Log.v(TAG, "handleMessage - action = '" + CameraActions.stringify(msg.what) + "'"); + try { switch (msg.what) { case CameraActions.OPEN_CAMERA: { @@ -348,9 +344,9 @@ class AndroidCameraAgentImpl extends CameraAgent { mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE); if (openCallback != null) { - openCallback.onCameraOpened( - new AndroidCameraProxyImpl(cameraId, mCamera, - mCharacteristics, mCapabilities)); + CameraProxy cameraProxy = new AndroidCameraProxyImpl( + mAgent, cameraId, mCamera, mCharacteristics, mCapabilities); + openCallback.onCameraOpened(cameraProxy); } } else { if (openCallback != null) { @@ -379,8 +375,7 @@ class AndroidCameraAgentImpl extends CameraAgent { mCamera.reconnect(); } catch (IOException ex) { if (cbForward != null) { - cbForward.onReconnectionFailure(AndroidCameraAgentImpl.this, - generateHistoryString(mCameraId)); + cbForward.onReconnectionFailure(mAgent, generateHistoryString(mCameraId)); } break; } @@ -388,8 +383,8 @@ class AndroidCameraAgentImpl extends CameraAgent { mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE); if (cbForward != null) { cbForward.onCameraOpened( - new AndroidCameraProxyImpl(cameraId, mCamera, mCharacteristics, - mCapabilities)); + new AndroidCameraProxyImpl(AndroidCameraAgentImpl.this, + cameraId, mCamera, mCharacteristics, mCapabilities)); } break; } @@ -573,45 +568,49 @@ class AndroidCameraAgentImpl extends CameraAgent { case CameraActions.CAPTURE_PHOTO: { mCameraState.setState(AndroidCameraStateHolder.CAMERA_CAPTURING); - capture((CaptureCallbacks) msg.obj); + CaptureCallbacks captureCallbacks = (CaptureCallbacks) msg.obj; + mCamera.takePicture( + captureCallbacks.mShutter, + captureCallbacks.mRaw, + captureCallbacks.mPostView, + captureCallbacks.mJpeg); break; } default: { - throw new RuntimeException("Invalid CameraProxy message=" + msg.what); + Log.e(TAG, "Invalid CameraProxy message=" + msg.what); } } - } catch (final RuntimeException e) { - Log.e(TAG, "Exception during camera operation " + msg.what, e); - if (msg.what != CameraActions.RELEASE && mCamera != null) { + } catch (final RuntimeException ex) { + String cameraAction = CameraActions.stringify(msg.what); + int cameraState = mCameraState.getState(); + String errorContext = + "CameraAction[" + cameraAction + "] at CameraState[" + cameraState + "]"; + Log.e(TAG, "RuntimeException during " + errorContext, ex); + + // Be conservative by invalidating both CameraAgent and CameraProxy objects. + mCameraState.invalidate(); + + if (mCamera != null) { + Log.i(TAG, "Release camera since mCamera is not null."); try { mCamera.release(); - mCameraState.setState(AndroidCameraStateHolder.CAMERA_UNOPENED); - } catch (Exception ex) { - Log.e(TAG, "Fail to release the camera."); - } - mCamera = null; - } else { - if (mCamera == null) { - if (msg.what == CameraActions.OPEN_CAMERA) { - final int cameraId = msg.arg1; - if (msg.obj != null) { - ((CameraOpenCallback) msg.obj).onDeviceOpenFailure( - msg.arg1, generateHistoryString(cameraId)); - } - } else { - Log.w(TAG, "Cannot handle message " + msg.what + ", mCamera is null."); - } - return; + } catch (Exception e) { + Log.e(TAG, "Fail when calling Camera.release().", e); + } finally { + mCamera = null; } } - synchronized (mCameraExceptionCallback) { - mCameraExceptionCallbackHandler.post(new Runnable() { - @Override - public void run() { - mCameraExceptionCallback.onCameraException(e); - } - }); + + // Invoke error callback. + if (msg.what == CameraActions.OPEN_CAMERA && mCamera == null) { + final int cameraId = msg.arg1; + if (msg.obj != null) { + ((CameraOpenCallback) msg.obj).onDeviceOpenFailure( + msg.arg1, generateHistoryString(cameraId)); + } + } else { + mAgent.getCameraExceptionHandler().onCameraException(ex); } } finally { WaitDoneBundle.unblockSyncWaiters(msg); @@ -722,15 +721,20 @@ class AndroidCameraAgentImpl extends CameraAgent { * camera handler thread. */ private class AndroidCameraProxyImpl extends CameraAgent.CameraProxy { + private final CameraAgent mCameraAgent; private final int mCameraId; /* TODO: remove this Camera instance. */ private final Camera mCamera; private final CameraDeviceInfo.Characteristics mCharacteristics; private final AndroidCameraCapabilities mCapabilities; - private AndroidCameraProxyImpl(int cameraId, Camera camera, + private AndroidCameraProxyImpl( + CameraAgent cameraAgent, + int cameraId, + Camera camera, CameraDeviceInfo.Characteristics characteristics, AndroidCameraCapabilities capabilities) { + mCameraAgent = cameraAgent; mCamera = camera; mCameraId = cameraId; mCharacteristics = characteristics; @@ -740,6 +744,9 @@ class AndroidCameraAgentImpl extends CameraAgent { @Deprecated @Override public android.hardware.Camera getCamera() { + if (getCameraState().isInvalid()) { + return null; + } return mCamera; } @@ -758,6 +765,11 @@ class AndroidCameraAgentImpl extends CameraAgent { return new AndroidCameraCapabilities(mCapabilities); } + @Override + public CameraAgent getAgent() { + return mCameraAgent; + } + @Override public void setPreviewDataCallback( final Handler handler, final CameraPreviewDataCallback cb) { @@ -821,6 +833,10 @@ class AndroidCameraAgentImpl extends CameraAgent { mDispatchThread.runJob(new Runnable() { @Override public void run() { + // Don't bother to wait since camera is in bad state. + if (getCameraState().isInvalid()) { + return; + } mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE); mCameraHandler.obtainMessage(CameraActions.AUTO_FOCUS, afCallback) .sendToTarget(); @@ -832,15 +848,19 @@ class AndroidCameraAgentImpl extends CameraAgent { @Override public void setAutoFocusMoveCallback( final Handler handler, final CameraAFMoveCallback cb) { - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - mCameraHandler.obtainMessage(CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK, - AFMoveCallbackForward.getNewInstance( - handler, AndroidCameraProxyImpl.this, cb)) - .sendToTarget(); - } - }); + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + mCameraHandler.obtainMessage(CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK, + AFMoveCallbackForward.getNewInstance( + handler, AndroidCameraProxyImpl.this, cb)) + .sendToTarget(); + } + }); + } catch (final RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @Override @@ -865,59 +885,79 @@ class AndroidCameraAgentImpl extends CameraAgent { } }; - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE | - AndroidCameraStateHolder.CAMERA_UNLOCKED); - mCameraHandler.requestTakePicture(ShutterCallbackForward - .getNewInstance(handler, AndroidCameraProxyImpl.this, shutter), - PictureCallbackForward - .getNewInstance(handler, AndroidCameraProxyImpl.this, raw), - PictureCallbackForward - .getNewInstance(handler, AndroidCameraProxyImpl.this, post), - jpegCallback - ); - } - }); + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + // Don't bother to wait since camera is in bad state. + if (getCameraState().isInvalid()) { + return; + } + mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE | + AndroidCameraStateHolder.CAMERA_UNLOCKED); + mCameraHandler.requestTakePicture(ShutterCallbackForward + .getNewInstance(handler, AndroidCameraProxyImpl.this, shutter), + PictureCallbackForward + .getNewInstance(handler, AndroidCameraProxyImpl.this, raw), + PictureCallbackForward + .getNewInstance(handler, AndroidCameraProxyImpl.this, post), + jpegCallback + ); + } + }); + } catch (final RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @Override public void setZoomChangeListener(final OnZoomChangeListener listener) { - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - mCameraHandler.obtainMessage(CameraActions.SET_ZOOM_CHANGE_LISTENER, listener) - .sendToTarget(); - } - }); + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + mCameraHandler.obtainMessage(CameraActions.SET_ZOOM_CHANGE_LISTENER, listener) + .sendToTarget(); + } + }); + } catch (final RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @Override public void setFaceDetectionCallback(final Handler handler, final CameraFaceDetectionCallback cb) { - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - mCameraHandler.obtainMessage(CameraActions.SET_FACE_DETECTION_LISTENER, - FaceDetectionCallbackForward - .getNewInstance(handler, AndroidCameraProxyImpl.this, cb)) - .sendToTarget(); - } - }); + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + mCameraHandler.obtainMessage(CameraActions.SET_FACE_DETECTION_LISTENER, + FaceDetectionCallbackForward + .getNewInstance(handler, AndroidCameraProxyImpl.this, cb)) + .sendToTarget(); + } + }); + } catch (final RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @Override public void setErrorCallback(final Handler handler, final CameraErrorCallback cb) { - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - mCameraHandler.obtainMessage(CameraActions.SET_ERROR_CALLBACK, - ErrorCallbackForward.getNewInstance( - handler, AndroidCameraProxyImpl.this, cb)) - .sendToTarget(); - } - }); + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + mCameraHandler.obtainMessage(CameraActions.SET_ERROR_CALLBACK, + ErrorCallbackForward.getNewInstance( + handler, AndroidCameraProxyImpl.this, cb)) + .sendToTarget(); + } + }); + } catch (final RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @Deprecated @@ -928,15 +968,19 @@ class AndroidCameraAgentImpl extends CameraAgent { return; } final String flattenedParameters = params.flatten(); - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE | - AndroidCameraStateHolder.CAMERA_UNLOCKED); - mCameraHandler.obtainMessage(CameraActions.SET_PARAMETERS, flattenedParameters) - .sendToTarget(); - } - }); + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE | + AndroidCameraStateHolder.CAMERA_UNLOCKED); + mCameraHandler.obtainMessage(CameraActions.SET_PARAMETERS, flattenedParameters) + .sendToTarget(); + } + }); + } catch (final RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @Deprecated @@ -944,14 +988,18 @@ class AndroidCameraAgentImpl extends CameraAgent { public Parameters getParameters() { final WaitDoneBundle bundle = new WaitDoneBundle(); final Parameters[] parametersHolder = new Parameters[1]; - mDispatchThread.runJobSync(new Runnable() { - @Override - public void run() { - mCameraHandler.obtainMessage( - CameraActions.GET_PARAMETERS, parametersHolder).sendToTarget(); - mCameraHandler.post(bundle.mUnlockRunnable); - } - }, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "get parameters"); + try { + mDispatchThread.runJobSync(new Runnable() { + @Override + public void run() { + mCameraHandler.obtainMessage( + CameraActions.GET_PARAMETERS, parametersHolder).sendToTarget(); + mCameraHandler.post(bundle.mUnlockRunnable); + } + }, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "get parameters"); + } catch (final RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } return parametersHolder[0]; } diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java index 17cbc02..d44cacd 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java @@ -43,7 +43,7 @@ import com.android.ex.camera2.portability.debug.Log; * {@code android.hardware.Camera.OnZoomChangeListener}, and */ public abstract class CameraAgent { - public static final long CAMERA_OPERATION_TIMEOUT_MS = 2500; + public static final long CAMERA_OPERATION_TIMEOUT_MS = 3500; private static final Log.Tag TAG = new Log.Tag("CamAgnt"); @@ -154,14 +154,6 @@ public abstract class CameraAgent { } } - /** - * A handler for all camera api runtime exceptions. - * The default behavior is to throw the runtime exception. - */ - public static interface CameraExceptionCallback { - public void onCameraException(RuntimeException e); - } - /** * An interface which wraps * {@link android.hardware.Camera.ErrorCallback} @@ -293,12 +285,17 @@ public abstract class CameraAgent { */ public void openCamera(final Handler handler, final int cameraId, final CameraOpenCallback callback) { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler().obtainMessage(CameraActions.OPEN_CAMERA, cameraId, 0, - CameraOpenCallbackForward.getNewInstance(handler, callback)).sendToTarget(); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler().obtainMessage(CameraActions.OPEN_CAMERA, cameraId, 0, + CameraOpenCallbackForward.getNewInstance(handler, callback)).sendToTarget(); + } + }); + } catch (final RuntimeException ex) { + getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -308,22 +305,30 @@ public abstract class CameraAgent { * @param synced Whether this call should be synchronous. */ public void closeCamera(CameraProxy camera, boolean synced) { - if (synced) { - final WaitDoneBundle bundle = new WaitDoneBundle(); - - getDispatchThread().runJobSync(new Runnable() { - @Override - public void run() { - getCameraHandler().obtainMessage(CameraActions.RELEASE).sendToTarget(); - getCameraHandler().post(bundle.mUnlockRunnable); - }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "camera release"); - } else { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler().removeCallbacksAndMessages(null); - getCameraHandler().obtainMessage(CameraActions.RELEASE).sendToTarget(); - }}); + try { + if (synced) { + // Don't bother to wait since camera is in bad state. + if (getCameraState().isInvalid()) { + return; + } + final WaitDoneBundle bundle = new WaitDoneBundle(); + + getDispatchThread().runJobSync(new Runnable() { + @Override + public void run() { + getCameraHandler().obtainMessage(CameraActions.RELEASE).sendToTarget(); + getCameraHandler().post(bundle.mUnlockRunnable); + }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "camera release"); + } else { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler().removeCallbacksAndMessages(null); + getCameraHandler().obtainMessage(CameraActions.RELEASE).sendToTarget(); + }}); + } + } catch (final RuntimeException ex) { + getCameraExceptionHandler().onDispatchThreadException(ex); } } @@ -331,8 +336,7 @@ public abstract class CameraAgent { * Sets a callback for handling camera api runtime exceptions on * a handler. */ - public abstract void setCameraDefaultExceptionCallback(CameraExceptionCallback callback, - Handler handler); + public abstract void setCameraExceptionHandler(CameraExceptionHandler exceptionHandler); /** * Recycles the resources used by this instance. CameraAgent will be in @@ -355,12 +359,22 @@ public abstract class CameraAgent { */ protected abstract DispatchThread getDispatchThread(); + /** + * @return The state machine tracking the camera API's current status. + */ + protected abstract CameraStateHolder getCameraState(); + + /** + * @return The exception handler. + */ + protected abstract CameraExceptionHandler getCameraExceptionHandler(); + /** * An interface that takes camera operation requests and post messages to the * camera handler thread. All camera operations made through this interface is * asynchronous by default except those mentioned specifically. */ - public static abstract class CameraProxy { + public abstract static class CameraProxy { /** * Returns the underlying {@link android.hardware.Camera} object used @@ -387,6 +401,11 @@ public abstract class CameraAgent { */ public abstract CameraCapabilities getCapabilities(); + /** + * @return The camera agent which creates this proxy. + */ + public abstract CameraAgent getAgent(); + /** * Reconnects to the camera device. On success, the camera device will * be returned through {@link CameraAgent @@ -399,12 +418,16 @@ public abstract class CameraAgent { * @param cb The callback when any error happens. */ public void reconnect(final Handler handler, final CameraOpenCallback cb) { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler().obtainMessage(CameraActions.RECONNECT, getCameraId(), 0, - CameraOpenCallbackForward.getNewInstance(handler, cb)).sendToTarget(); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler().obtainMessage(CameraActions.RECONNECT, getCameraId(), 0, + CameraOpenCallbackForward.getNewInstance(handler, cb)).sendToTarget(); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -413,13 +436,22 @@ public abstract class CameraAgent { * @see android.hardware.Camera#unlock() */ public void unlock() { + // Don't bother to wait since camera is in bad state. + if (getCameraState().isInvalid()) { + return; + } final WaitDoneBundle bundle = new WaitDoneBundle(); - getDispatchThread().runJobSync(new Runnable() { - @Override - public void run() { - getCameraHandler().sendEmptyMessage(CameraActions.UNLOCK); - getCameraHandler().post(bundle.mUnlockRunnable); - }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "camera unlock"); + try { + getDispatchThread().runJobSync(new Runnable() { + @Override + public void run() { + getCameraHandler().sendEmptyMessage(CameraActions.UNLOCK); + getCameraHandler().post(bundle.mUnlockRunnable); + } + }, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "camera unlock"); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -427,11 +459,15 @@ public abstract class CameraAgent { * @see android.hardware.Camera#lock() */ public void lock() { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler().sendEmptyMessage(CameraActions.LOCK); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler().sendEmptyMessage(CameraActions.LOCK); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -456,13 +492,17 @@ public abstract class CameraAgent { // the STOP_PREVIEW case in its handler; in the meantime, changing API 2 // sizes would require closing and reopening the camera. public void setPreviewTexture(final SurfaceTexture surfaceTexture) { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler() - .obtainMessage(CameraActions.SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture) - .sendToTarget(); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler() + .obtainMessage(CameraActions.SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture) + .sendToTarget(); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -480,15 +520,23 @@ public abstract class CameraAgent { * @see CameraSettings#setPreviewSize */ public void setPreviewTextureSync(final SurfaceTexture surfaceTexture) { + // Don't bother to wait since camera is in bad state. + if (getCameraState().isInvalid()) { + return; + } final WaitDoneBundle bundle = new WaitDoneBundle(); - getDispatchThread().runJobSync(new Runnable() { - @Override - public void run() { - getCameraHandler() - .obtainMessage(CameraActions.SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture) - .sendToTarget(); - getCameraHandler().post(bundle.mUnlockRunnable); - }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "set preview texture"); + try { + getDispatchThread().runJobSync(new Runnable() { + @Override + public void run() { + getCameraHandler() + .obtainMessage(CameraActions.SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture) + .sendToTarget(); + getCameraHandler().post(bundle.mUnlockRunnable); + }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "set preview texture"); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -497,25 +545,33 @@ public abstract class CameraAgent { * @param surfaceHolder The {@link SurfaceHolder} for preview. */ public void setPreviewDisplay(final SurfaceHolder surfaceHolder) { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler() - .obtainMessage(CameraActions.SET_PREVIEW_DISPLAY_ASYNC, surfaceHolder) - .sendToTarget(); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler() + .obtainMessage(CameraActions.SET_PREVIEW_DISPLAY_ASYNC, surfaceHolder) + .sendToTarget(); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** * Starts the camera preview. */ public void startPreview() { + try { getDispatchThread().runJob(new Runnable() { @Override public void run() { getCameraHandler() .obtainMessage(CameraActions.START_PREVIEW_ASYNC, null).sendToTarget(); }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -523,6 +579,7 @@ public abstract class CameraAgent { * the preview starts. */ public void startPreviewWithCallback(final Handler h, final CameraStartPreviewCallback cb) { + try { getDispatchThread().runJob(new Runnable() { @Override public void run() { @@ -530,6 +587,9 @@ public abstract class CameraAgent { CameraStartPreviewCallbackForward.getNewInstance(h, cb)) .sendToTarget(); }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -538,13 +598,21 @@ public abstract class CameraAgent { * continues to release resources related to camera preview. */ public void stopPreview() { + // Don't bother to wait since camera is in bad state. + if (getCameraState().isInvalid()) { + return; + } final WaitDoneBundle bundle = new WaitDoneBundle(); - getDispatchThread().runJobSync(new Runnable() { - @Override - public void run() { - getCameraHandler().obtainMessage(CameraActions.STOP_PREVIEW, bundle) - .sendToTarget(); - }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "stop preview"); + try { + getDispatchThread().runJobSync(new Runnable() { + @Override + public void run() { + getCameraHandler().obtainMessage(CameraActions.STOP_PREVIEW, bundle) + .sendToTarget(); + }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "stop preview"); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -583,14 +651,18 @@ public abstract class CameraAgent { * @param callbackBuffer The buffer allocated for the preview data. */ public void addCallbackBuffer(final byte[] callbackBuffer) { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler() - .obtainMessage(CameraActions.ADD_CALLBACK_BUFFER, callbackBuffer) - .sendToTarget(); - } - }); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler() + .obtainMessage(CameraActions.ADD_CALLBACK_BUFFER, callbackBuffer) + .sendToTarget(); + } + }); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -667,24 +739,32 @@ public abstract class CameraAgent { * @param capture Whether to adjust the JPEG capture orientation as well as the preview one. */ public void setDisplayOrientation(final int degrees, final boolean capture) { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler() - .obtainMessage(CameraActions.SET_DISPLAY_ORIENTATION, degrees, - capture ? 1 : 0) - .sendToTarget(); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler() + .obtainMessage(CameraActions.SET_DISPLAY_ORIENTATION, degrees, + capture ? 1 : 0) + .sendToTarget(); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } public void setJpegOrientation(final int degrees) { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler() - .obtainMessage(CameraActions.SET_JPEG_ORIENTATION, degrees, 0) - .sendToTarget(); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler() + .obtainMessage(CameraActions.SET_JPEG_ORIENTATION, degrees, 0) + .sendToTarget(); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -707,22 +787,30 @@ public abstract class CameraAgent { * Starts the face detection. */ public void startFaceDetection() { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler().sendEmptyMessage(CameraActions.START_FACE_DETECTION); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler().sendEmptyMessage(CameraActions.START_FACE_DETECTION); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** * Stops the face detection. */ public void stopFaceDetection() { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler().sendEmptyMessage(CameraActions.STOP_FACE_DETECTION); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler().sendEmptyMessage(CameraActions.STOP_FACE_DETECTION); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -780,13 +868,22 @@ public abstract class CameraAgent { } final CameraSettings copyOfSettings = settings.copy(); - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraState().waitForStates(statesToAwait); - getCameraHandler().obtainMessage(CameraActions.APPLY_SETTINGS, copyOfSettings) - .sendToTarget(); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + CameraStateHolder cameraState = getCameraState(); + // Don't bother to wait since camera is in bad state. + if (cameraState.isInvalid()) { + return; + } + cameraState.waitForStates(statesToAwait); + getCameraHandler().obtainMessage(CameraActions.APPLY_SETTINGS, copyOfSettings) + .sendToTarget(); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } return true; } @@ -806,11 +903,15 @@ public abstract class CameraAgent { * settings regardless of the dirty bit. */ public void refreshSettings() { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler().sendEmptyMessage(CameraActions.REFRESH_PARAMETERS); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler().sendEmptyMessage(CameraActions.REFRESH_PARAMETERS); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -820,13 +921,17 @@ public abstract class CameraAgent { * {@code false} to disable it. */ public void enableShutterSound(final boolean enable) { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler() - .obtainMessage(CameraActions.ENABLE_SHUTTER_SOUND, (enable ? 1 : 0), 0) - .sendToTarget(); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler() + .obtainMessage(CameraActions.ENABLE_SHUTTER_SOUND, (enable ? 1 : 0), 0) + .sendToTarget(); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java new file mode 100644 index 0000000..7270b22 --- /dev/null +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ex.camera2.portability; + +import android.os.Handler; + +/** + * A handler for all camera api runtime exceptions. + * The default behavior is to throw the runtime exception. + */ +public class CameraExceptionHandler { + private Handler mHandler; + + private CameraExceptionCallback mCallback = + new CameraExceptionCallback() { + @Override + public void onCameraException(RuntimeException e) { + throw e; + } + + @Override + public void onDispatchThreadException(RuntimeException e) { + throw e; + } + }; + + /** + * A callback helps to handle RuntimeException thrown by camera framework. + */ + public static interface CameraExceptionCallback { + public void onCameraException(RuntimeException e); + public void onDispatchThreadException(RuntimeException e); + } + + /** + * Construct a new instance of {@link CameraExceptionHandler} with a custom callback which will + * be executed on a specific {@link Handler}. + * + * @param callback The callback which will be invoked. + * @param handler The handler in which the callback will be invoked in. + */ + public CameraExceptionHandler(CameraExceptionCallback callback, Handler handler) { + mHandler = handler; + mCallback = callback; + } + + /** + * Construct a new instance of {@link CameraExceptionHandler} with a default callback which will + * be executed on a specific {@link Handler}. + * + * @param handler The handler in which the default callback will be invoked in. + */ + public CameraExceptionHandler(Handler handler) { + mHandler = handler; + } + + /** + * Invoke @{link CameraExceptionCallback} when a runtime exception is thrown by Android camera + * framework. + * + * @param ex The runtime exception object. + */ + public void onCameraException(final RuntimeException ex) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onCameraException(ex); + } + }); + } + + /** + * Invoke @{link CameraExceptionCallback} when a runtime exception is thrown by + * @{link DispatchThread}. + * + * @param ex The runtime exception object. + */ + public void onDispatchThreadException(final RuntimeException ex) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onDispatchThreadException(ex); + } + }); + } +} + diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraStateHolder.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraStateHolder.java index c8d82b6..b758af2 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraStateHolder.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraStateHolder.java @@ -24,11 +24,23 @@ public abstract class CameraStateHolder { private static final Log.Tag TAG = new Log.Tag("CamStateHolder"); private int mState; + private boolean mInvalid; + /** + * Construct a new instance of @{link CameraStateHolder} with an initial state. + * + * @param state The initial state. + */ public CameraStateHolder(int state) { setState(state); + mInvalid = false; } + /** + * Change to a new state. + * + * @param state The new state. + */ public synchronized void setState(int state) { if (mState != state) { Log.v(TAG, "setState - state = " + Integer.toBinaryString(state)); @@ -37,10 +49,31 @@ public abstract class CameraStateHolder { this.notifyAll(); } + /** + * Obtain the current state. + * + * @return The current state. + */ public synchronized int getState() { return mState; } + /** + * Change the state to be invalid. Once invalidated, the state will be invalid forever. + */ + public synchronized void invalidate() { + mInvalid = true; + } + + /** + * Whether the state is invalid. + * + * @return True if the state is invalid. + */ + public synchronized boolean isInvalid() { + return mInvalid; + } + private static interface ConditionChecker { /** * @return Whether the condition holds. -- cgit v1.2.3 From 318eeb225f4567a8c2a6fe14baa06e8923ebe622 Mon Sep 17 00:00:00 2001 From: Senpo Hu Date: Fri, 10 Oct 2014 15:09:56 -0700 Subject: Cleanup: handle API 1 error callback in CameraExceptionHandler. Portability API used to expose setErrorCallback call which is API 1 only. The new CameraExceptionHandler should be the centralized place to handle different camera errors. Bug: 17931297 Change-Id: I0212f96a98e0caca64aca0f947b991b895d8d56b --- .../portability/AndroidCamera2AgentImpl.java | 4 -- .../portability/AndroidCameraAgentImpl.java | 77 ++++------------------ .../ex/camera2/portability/CameraActions.java | 3 - .../ex/camera2/portability/CameraAgent.java | 9 --- .../portability/CameraExceptionHandler.java | 20 +++++- 5 files changed, 30 insertions(+), 83 deletions(-) diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java index ae4c208..c57716d 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java @@ -1193,10 +1193,6 @@ class AndroidCamera2AgentImpl extends CameraAgent { @Override public void stopFaceDetection() {} - // TODO: Implement - @Override - public void setErrorCallback(Handler handler, CameraErrorCallback cb) {} - // TODO: Implement @Override public void setParameters(android.hardware.Camera.Parameters params) {} diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java index 8e17276..515add5 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java @@ -236,7 +236,7 @@ class AndroidCameraAgentImpl extends CameraAgent { /** * The handler on which the actual camera operations happen. */ - private class CameraHandler extends HistoryHandler { + private class CameraHandler extends HistoryHandler implements Camera.ErrorCallback { private CameraAgent mAgent; private Camera mCamera; private int mCameraId; @@ -307,6 +307,14 @@ class AndroidCameraAgentImpl extends CameraAgent { obtainMessage(CameraActions.CAPTURE_PHOTO, callbacks).sendToTarget(); } + @Override + public void onError(final int errorCode, Camera camera) { + mExceptionHandler.onCameraError(errorCode); + if (errorCode == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) { + mExceptionHandler.onCameraException(new RuntimeException("Media server died.")); + } + } + /** * This method does not deal with the API level check. Everyone should * check first for supported operations before sending message to this handler. @@ -342,6 +350,8 @@ class AndroidCameraAgentImpl extends CameraAgent { mCapabilities = new AndroidCameraCapabilities( mParameterCache.getBlocking()); + mCamera.setErrorCallback(this); + mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE); if (openCallback != null) { CameraProxy cameraProxy = new AndroidCameraProxyImpl( @@ -522,11 +532,6 @@ class AndroidCameraAgentImpl extends CameraAgent { break; } - case CameraActions.SET_ERROR_CALLBACK: { - mCamera.setErrorCallback((ErrorCallback) msg.obj); - break; - } - case CameraActions.APPLY_SETTINGS: { Parameters parameters = mParameterCache.getBlocking(); CameraSettings settings = (CameraSettings) msg.obj; @@ -943,23 +948,6 @@ class AndroidCameraAgentImpl extends CameraAgent { } } - @Override - public void setErrorCallback(final Handler handler, final CameraErrorCallback cb) { - try { - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - mCameraHandler.obtainMessage(CameraActions.SET_ERROR_CALLBACK, - ErrorCallbackForward.getNewInstance( - handler, AndroidCameraProxyImpl.this, cb)) - .sendToTarget(); - } - }); - } catch (final RuntimeException ex) { - mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); - } - } - @Deprecated @Override public void setParameters(final Parameters params) { @@ -1109,49 +1097,6 @@ class AndroidCameraAgentImpl extends CameraAgent { } } - /** - * A helper class to forward ErrorCallback to another thread. - */ - private static class ErrorCallbackForward implements Camera.ErrorCallback { - private final Handler mHandler; - private final CameraProxy mCamera; - private final CameraErrorCallback mCallback; - - /** - * Returns a new instance of {@link AFCallbackForward}. - * - * @param handler The handler in which the callback will be invoked in. - * @param camera The {@link CameraProxy} which the callback is from. - * @param cb The callback to be invoked. - * @return The instance of the {@link AFCallbackForward}, - * or null if any parameter is null. - */ - public static ErrorCallbackForward getNewInstance( - Handler handler, CameraProxy camera, CameraErrorCallback cb) { - if (handler == null || camera == null || cb == null) { - return null; - } - return new ErrorCallbackForward(handler, camera, cb); - } - - private ErrorCallbackForward( - Handler h, CameraProxy camera, CameraErrorCallback cb) { - mHandler = h; - mCamera = camera; - mCallback = cb; - } - - @Override - public void onError(final int error, Camera camera) { - mHandler.post(new Runnable() { - @Override - public void run() { - mCallback.onError(error, mCamera); - } - }); - } - } - /** A helper class to forward AutoFocusMoveCallback to another thread. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private static class AFMoveCallbackForward implements AutoFocusMoveCallback { diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraActions.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraActions.java index 407fbb1..63b1fec 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraActions.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraActions.java @@ -47,7 +47,6 @@ class CameraActions { public static final int SET_FACE_DETECTION_LISTENER = 461; public static final int START_FACE_DETECTION = 462; public static final int STOP_FACE_DETECTION = 463; - public static final int SET_ERROR_CALLBACK = 464; // Presentation public static final int ENABLE_SHUTTER_SOUND = 501; public static final int SET_DISPLAY_ORIENTATION = 502; @@ -107,8 +106,6 @@ class CameraActions { return "START_FACE_DETECTION"; case STOP_FACE_DETECTION: return "STOP_FACE_DETECTION"; - case SET_ERROR_CALLBACK: - return "SET_ERROR_CALLBACK"; case ENABLE_SHUTTER_SOUND: return "ENABLE_SHUTTER_SOUND"; case SET_DISPLAY_ORIENTATION: diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java index d44cacd..66762fd 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java @@ -813,15 +813,6 @@ public abstract class CameraAgent { } } - /** - * Registers an error callback. - * - * @param handler The handler on which the callback will be invoked. - * @param cb The error callback. - * @see android.hardware.Camera#setErrorCallback(android.hardware.Camera.ErrorCallback) - */ - public abstract void setErrorCallback(Handler handler, CameraErrorCallback cb); - /** * Sets the camera parameters. * diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java index 7270b22..1fcf3b5 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java @@ -27,11 +27,13 @@ public class CameraExceptionHandler { private CameraExceptionCallback mCallback = new CameraExceptionCallback() { + @Override + public void onCameraError(int errorCode) { + } @Override public void onCameraException(RuntimeException e) { throw e; } - @Override public void onDispatchThreadException(RuntimeException e) { throw e; @@ -42,6 +44,7 @@ public class CameraExceptionHandler { * A callback helps to handle RuntimeException thrown by camera framework. */ public static interface CameraExceptionCallback { + public void onCameraError(int errorCode); public void onCameraException(RuntimeException e); public void onDispatchThreadException(RuntimeException e); } @@ -68,6 +71,21 @@ public class CameraExceptionHandler { mHandler = handler; } + /** + * Invoke @{link CameraExceptionCallback} when an error is reported by Android camera framework. + * + * @param errorCode An integer to represent the error code. + * @see android.hardware.Camera#setErrorCallback(android.hardware.Camera.ErrorCallback) + */ + public void onCameraError(final int errorCode) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onCameraError(errorCode); + } + }); + } + /** * Invoke @{link CameraExceptionCallback} when a runtime exception is thrown by Android camera * framework. -- cgit v1.2.3 From 733ca8dfa76ac34d1f9caff8798d01a4a8f44002 Mon Sep 17 00:00:00 2001 From: Senpo Hu Date: Tue, 14 Oct 2014 14:19:08 -0700 Subject: Return more detailed debugging info for logging purposes. Change-Id: If71afbf8029e9e4d5560eb6a655ece990cf19a19 --- .../portability/AndroidCamera2AgentImpl.java | 11 +++++++---- .../portability/AndroidCameraAgentImpl.java | 22 +++++++++++++++------- .../portability/CameraExceptionHandler.java | 12 ++++++++---- .../ex/camera2/portability/HistoryHandler.java | 4 ++++ 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java index c57716d..2fc4ad3 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java @@ -222,8 +222,9 @@ class AndroidCamera2AgentImpl extends CameraAgent { public void handleMessage(final Message msg) { super.handleMessage(msg); Log.v(TAG, "handleMessage - action = '" + CameraActions.stringify(msg.what) + "'"); + int cameraAction = msg.what; try { - switch(msg.what) { + switch (cameraAction) { case CameraActions.OPEN_CAMERA: case CameraActions.RECONNECT: { CameraOpenCallback openCallback = (CameraOpenCallback) msg.obj; @@ -640,12 +641,12 @@ class AndroidCamera2AgentImpl extends CameraAgent { } } } catch (final Exception ex) { - if (msg.what != CameraActions.RELEASE && mCamera != null) { + if (cameraAction != CameraActions.RELEASE && mCamera != null) { // TODO: Handle this better mCamera.close(); mCamera = null; } else if (mCamera == null) { - if (msg.what == CameraActions.OPEN_CAMERA) { + if (cameraAction == CameraActions.OPEN_CAMERA) { if (mOpenCallback != null) { mOpenCallback.onDeviceOpenFailure(mCameraIndex, generateHistoryString(mCameraIndex)); @@ -657,7 +658,9 @@ class AndroidCamera2AgentImpl extends CameraAgent { } if (ex instanceof RuntimeException) { - mExceptionHandler.onCameraException((RuntimeException) ex); + String commandHistory = generateHistoryString(Integer.parseInt(mCameraId)); + mExceptionHandler.onCameraException((RuntimeException) ex, commandHistory, + cameraAction, mCameraState.getState()); } } finally { WaitDoneBundle.unblockSyncWaiters(msg); diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java index 515add5..9910dd9 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java @@ -239,7 +239,7 @@ class AndroidCameraAgentImpl extends CameraAgent { private class CameraHandler extends HistoryHandler implements Camera.ErrorCallback { private CameraAgent mAgent; private Camera mCamera; - private int mCameraId; + private int mCameraId = -1; private ParametersCache mParameterCache; private int mCancelAfPending = 0; @@ -311,7 +311,12 @@ class AndroidCameraAgentImpl extends CameraAgent { public void onError(final int errorCode, Camera camera) { mExceptionHandler.onCameraError(errorCode); if (errorCode == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) { - mExceptionHandler.onCameraException(new RuntimeException("Media server died.")); + int lastCameraAction = getCurrentMessage(); + mExceptionHandler.onCameraException( + new RuntimeException("Media server died."), + generateHistoryString(mCameraId), + lastCameraAction, + mCameraState.getState()); } } @@ -329,8 +334,9 @@ class AndroidCameraAgentImpl extends CameraAgent { } Log.v(TAG, "handleMessage - action = '" + CameraActions.stringify(msg.what) + "'"); + int cameraAction = msg.what; try { - switch (msg.what) { + switch (cameraAction) { case CameraActions.OPEN_CAMERA: { final CameraOpenCallback openCallback = (CameraOpenCallback) msg.obj; final int cameraId = msg.arg1; @@ -371,6 +377,7 @@ class AndroidCameraAgentImpl extends CameraAgent { mCamera.release(); mCameraState.setState(AndroidCameraStateHolder.CAMERA_UNOPENED); mCamera = null; + mCameraId = -1; } else { Log.w(TAG, "Releasing camera without any camera opened."); } @@ -587,10 +594,9 @@ class AndroidCameraAgentImpl extends CameraAgent { } } } catch (final RuntimeException ex) { - String cameraAction = CameraActions.stringify(msg.what); int cameraState = mCameraState.getState(); - String errorContext = - "CameraAction[" + cameraAction + "] at CameraState[" + cameraState + "]"; + String errorContext = "CameraAction[" + CameraActions.stringify(cameraAction) + + "] at CameraState[" + cameraState + "]"; Log.e(TAG, "RuntimeException during " + errorContext, ex); // Be conservative by invalidating both CameraAgent and CameraProxy objects. @@ -615,7 +621,9 @@ class AndroidCameraAgentImpl extends CameraAgent { msg.arg1, generateHistoryString(cameraId)); } } else { - mAgent.getCameraExceptionHandler().onCameraException(ex); + CameraExceptionHandler exceptionHandler = mAgent.getCameraExceptionHandler(); + exceptionHandler.onCameraException( + ex, generateHistoryString(mCameraId), cameraAction, cameraState); } } finally { WaitDoneBundle.unblockSyncWaiters(msg); diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java index 1fcf3b5..dc71b4b 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java @@ -31,7 +31,8 @@ public class CameraExceptionHandler { public void onCameraError(int errorCode) { } @Override - public void onCameraException(RuntimeException e) { + public void onCameraException( + RuntimeException e, String commandHistory, int action, int state) { throw e; } @Override @@ -45,7 +46,8 @@ public class CameraExceptionHandler { */ public static interface CameraExceptionCallback { public void onCameraError(int errorCode); - public void onCameraException(RuntimeException e); + public void onCameraException( + RuntimeException e, String commandHistory, int action, int state); public void onDispatchThreadException(RuntimeException e); } @@ -92,11 +94,13 @@ public class CameraExceptionHandler { * * @param ex The runtime exception object. */ - public void onCameraException(final RuntimeException ex) { + public void onCameraException( + final RuntimeException ex, final String commandHistory, + final int action, final int state) { mHandler.post(new Runnable() { @Override public void run() { - mCallback.onCameraException(ex); + mCallback.onCameraException(ex, commandHistory, action, state); } }); } diff --git a/camera2/portability/src/com/android/ex/camera2/portability/HistoryHandler.java b/camera2/portability/src/com/android/ex/camera2/portability/HistoryHandler.java index ec2a555..6b1c5ab 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/HistoryHandler.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/HistoryHandler.java @@ -35,6 +35,10 @@ class HistoryHandler extends Handler { mMsgHistory.offerLast(-1); } + Integer getCurrentMessage() { + return mMsgHistory.peekLast(); + } + String generateHistoryString(int cameraId) { String info = new String("HIST"); info += "_ID" + cameraId; -- cgit v1.2.3 From 2f5251072284508374cb36d1e83f7e24cc590049 Mon Sep 17 00:00:00 2001 From: Senpo Hu Date: Mon, 6 Oct 2014 10:36:55 -0700 Subject: Overhaul error handling in AndroidCameraAgentImpl. DispatchThread could throw RTE when timeout (2500 ms). This CL extends it to 3500 ms and also re-route the exception to the proper exception callback instead of surfacing that up to the app. Since a lot of try-catch block is added, CameraExceptionHandler class is added to keep CameraAgent.java clean. The portability layer should ensure the proper use of Camera API 1. With this assumption, any run time exception thrown by the framework should be considered fatal. Once it happened, CameraAgent/CameraProxy should be invalidated. Any subsequent camera commands should be gated as no-ops to prevent HAL hanging symptom. Users should be guided to exit the app and recover from the camera error. Design Doc: https://docs.google.com/a/google.com/document/d/1t0sEPwGgc387XcoCVC7kT9Y_RSZPxcayla03M4bdn0U/edit Corresponding CL for the app layer: ag/562383 Bug: 16189216 Change-Id: I33e69aeff0c8e4211e82e0e83133915700c4eb8f --- .../portability/AndroidCamera2AgentImpl.java | 170 ++++++---- .../portability/AndroidCameraAgentImpl.java | 318 ++++++++++-------- .../ex/camera2/portability/CameraAgent.java | 367 +++++++++++++-------- .../portability/CameraExceptionHandler.java | 101 ++++++ .../ex/camera2/portability/CameraStateHolder.java | 33 ++ 5 files changed, 653 insertions(+), 336 deletions(-) create mode 100644 camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java index a746634..ae4c208 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java @@ -65,6 +65,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { private final DispatchThread mDispatchThread; private final CameraManager mCameraManager; private final MediaActionSound mNoisemaker; + private CameraExceptionHandler mExceptionHandler; /** * Number of camera devices. The length of {@code mCameraDevices} does not reveal this @@ -86,6 +87,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { mCameraHandlerThread = new HandlerThread("Camera2 Handler Thread"); mCameraHandlerThread.start(); mCameraHandler = new Camera2Handler(mCameraHandlerThread.getLooper()); + mExceptionHandler = new CameraExceptionHandler(mCameraHandler); mCameraState = new AndroidCamera2StateHolder(); mDispatchThread = new DispatchThread(mCameraHandler, mCameraHandlerThread); mDispatchThread.start(); @@ -132,11 +134,6 @@ class AndroidCamera2AgentImpl extends CameraAgent { } } - // TODO: Implement - @Override - public void setCameraDefaultExceptionCallback(CameraExceptionCallback callback, - Handler handler) {} - // TODO: Implement @Override public void recycle() {} @@ -159,6 +156,21 @@ class AndroidCamera2AgentImpl extends CameraAgent { return mDispatchThread; } + @Override + protected CameraStateHolder getCameraState() { + return mCameraState; + } + + @Override + protected CameraExceptionHandler getCameraExceptionHandler() { + return mExceptionHandler; + } + + @Override + public void setCameraExceptionHandler(CameraExceptionHandler exceptionHandler) { + mExceptionHandler = exceptionHandler; + } + private static abstract class CaptureAvailableListener extends CameraCaptureSession.CaptureCallback implements ImageReader.OnImageAvailableListener {}; @@ -645,11 +657,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { } if (ex instanceof RuntimeException) { - post(new Runnable() { - @Override - public void run() { - sCameraExceptionCallback.onCameraException((RuntimeException) ex); - }}); + mExceptionHandler.onCameraException((RuntimeException) ex); } } finally { WaitDoneBundle.unblockSyncWaiters(msg); @@ -771,8 +779,10 @@ class AndroidCamera2AgentImpl extends CameraAgent { try { CameraCharacteristics props = mCameraManager.getCameraCharacteristics(mCameraId); - mCameraProxy = new AndroidCamera2ProxyImpl(mCameraIndex, mCamera, - getCameraDeviceInfo().getCharacteristics(mCameraIndex), props); + CameraDeviceInfo.Characteristics characteristics = + getCameraDeviceInfo().getCharacteristics(mCameraIndex); + mCameraProxy = new AndroidCamera2ProxyImpl(AndroidCamera2AgentImpl.this, + mCameraIndex, mCamera, characteristics, props); mPersistentSettings = new Camera2RequestSettingsSet(); mActiveArray = props.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); @@ -960,6 +970,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { } private class AndroidCamera2ProxyImpl extends CameraAgent.CameraProxy { + private final AndroidCamera2AgentImpl mCameraAgent; private final int mCameraIndex; private final CameraDevice mCamera; private final CameraDeviceInfo.Characteristics mCharacteristics; @@ -967,9 +978,13 @@ class AndroidCamera2AgentImpl extends CameraAgent { private CameraSettings mLastSettings; private boolean mShutterSoundEnabled; - public AndroidCamera2ProxyImpl(int cameraIndex, CameraDevice camera, + public AndroidCamera2ProxyImpl( + AndroidCamera2AgentImpl agent, + int cameraIndex, + CameraDevice camera, CameraDeviceInfo.Characteristics characteristics, CameraCharacteristics properties) { + mCameraAgent = agent; mCameraIndex = cameraIndex; mCamera = camera; mCharacteristics = characteristics; @@ -997,6 +1012,10 @@ class AndroidCamera2AgentImpl extends CameraAgent { return mCapabilities; } + public CameraAgent getAgent() { + return mCameraAgent; + } + private AndroidCamera2Capabilities getSpecializedCapabilities() { return mCapabilities; } @@ -1039,53 +1058,67 @@ class AndroidCamera2AgentImpl extends CameraAgent { @Override public void autoFocus(final Handler handler, final CameraAFCallback cb) { - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - CameraAFCallback cbForward = null; - if (cb != null) { - cbForward = new CameraAFCallback() { - @Override - public void onAutoFocus(final boolean focused, - final CameraProxy camera) { - handler.post(new Runnable() { - @Override - public void run() { - cb.onAutoFocus(focused, camera); - }}); - }}; - } + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + CameraAFCallback cbForward = null; + if (cb != null) { + cbForward = new CameraAFCallback() { + @Override + public void onAutoFocus(final boolean focused, + final CameraProxy camera) { + handler.post(new Runnable() { + @Override + public void run() { + cb.onAutoFocus(focused, camera); + } + }); + } + }; + } - mCameraState.waitForStates(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE | - AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); - mCameraHandler.obtainMessage(CameraActions.AUTO_FOCUS, cbForward) - .sendToTarget(); - }}); + mCameraState.waitForStates(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE | + AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); + mCameraHandler.obtainMessage(CameraActions.AUTO_FOCUS, cbForward) + .sendToTarget(); + } + }); + } catch (RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public void setAutoFocusMoveCallback(final Handler handler, final CameraAFMoveCallback cb) { - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - CameraAFMoveCallback cbForward = null; - if (cb != null) { - cbForward = new CameraAFMoveCallback() { - @Override - public void onAutoFocusMoving(final boolean moving, - final CameraProxy camera) { - handler.post(new Runnable() { - @Override - public void run() { - cb.onAutoFocusMoving(moving, camera); - }}); - }}; - } + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + CameraAFMoveCallback cbForward = null; + if (cb != null) { + cbForward = new CameraAFMoveCallback() { + @Override + public void onAutoFocusMoving(final boolean moving, + final CameraProxy camera) { + handler.post(new Runnable() { + @Override + public void run() { + cb.onAutoFocusMoving(moving, camera); + } + }); + } + }; + } - mCameraHandler.obtainMessage(CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK, - cbForward).sendToTarget(); - }}); + mCameraHandler.obtainMessage(CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK, + cbForward).sendToTarget(); + } + }); + } catch (RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @Override @@ -1127,15 +1160,20 @@ class AndroidCamera2AgentImpl extends CameraAgent { } } }}; - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - // Wait until PREVIEW_ACTIVE or better - mCameraState.waitForStates( - ~(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE - 1)); - mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO, picListener) - .sendToTarget(); - }}); + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + // Wait until PREVIEW_ACTIVE or better + mCameraState.waitForStates( + ~(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE - 1)); + mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO, picListener) + .sendToTarget(); + } + }); + } catch (RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } // TODO: Implement @@ -1380,12 +1418,4 @@ class AndroidCamera2AgentImpl extends CameraAgent { } } } - - private static final CameraExceptionCallback sCameraExceptionCallback = - new CameraExceptionCallback() { - @Override - public synchronized void onCameraException(RuntimeException e) { - throw e; - } - }; } diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java index 992fcec..61a1cc7 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java @@ -55,39 +55,23 @@ class AndroidCameraAgentImpl extends CameraAgent { private final HandlerThread mCameraHandlerThread; private final CameraStateHolder mCameraState; private final DispatchThread mDispatchThread; - - private Handler mCameraExceptionCallbackHandler; - private CameraExceptionCallback mCameraExceptionCallback = - new CameraExceptionCallback() { - @Override - public void onCameraException(RuntimeException e) { - throw e; - } - }; + private CameraExceptionHandler mExceptionHandler; AndroidCameraAgentImpl() { mCameraHandlerThread = new HandlerThread("Camera Handler Thread"); mCameraHandlerThread.start(); - mCameraHandler = new CameraHandler(mCameraHandlerThread.getLooper()); - mCameraExceptionCallbackHandler = mCameraHandler; + mCameraHandler = new CameraHandler(this, mCameraHandlerThread.getLooper()); + mExceptionHandler = new CameraExceptionHandler(mCameraHandler); mCameraState = new AndroidCameraStateHolder(); mDispatchThread = new DispatchThread(mCameraHandler, mCameraHandlerThread); mDispatchThread.start(); } - @Override - public void setCameraDefaultExceptionCallback(CameraExceptionCallback callback, - Handler handler) { - synchronized (mCameraExceptionCallback) { - mCameraExceptionCallback = callback; - mCameraExceptionCallbackHandler = handler; - } - } - @Override public void recycle() { closeCamera(null, true); mDispatchThread.end(); + mCameraState.invalidate(); } @Override @@ -105,6 +89,21 @@ class AndroidCameraAgentImpl extends CameraAgent { return mDispatchThread; } + @Override + protected CameraStateHolder getCameraState() { + return mCameraState; + } + + @Override + protected CameraExceptionHandler getCameraExceptionHandler() { + return mExceptionHandler; + } + + @Override + public void setCameraExceptionHandler(CameraExceptionHandler exceptionHandler) { + mExceptionHandler = exceptionHandler; + } + private static class AndroidCameraDeviceInfo implements CameraDeviceInfo { private final Camera.CameraInfo[] mCameraInfos; private final int mNumberOfCameras; @@ -238,7 +237,7 @@ class AndroidCameraAgentImpl extends CameraAgent { * The handler on which the actual camera operations happen. */ private class CameraHandler extends HistoryHandler { - + private CameraAgent mAgent; private Camera mCamera; private int mCameraId; private ParametersCache mParameterCache; @@ -259,8 +258,9 @@ class AndroidCameraAgentImpl extends CameraAgent { } } - CameraHandler(Looper looper) { + CameraHandler(CameraAgent agent, Looper looper) { super(looper); + mAgent = agent; } private void startFaceDetection() { @@ -298,16 +298,6 @@ class AndroidCameraAgentImpl extends CameraAgent { } } - private void capture(final CaptureCallbacks cb) { - try { - mCamera.takePicture(cb.mShutter, cb.mRaw, cb.mPostView, cb.mJpeg); - } catch (RuntimeException e) { - // TODO: output camera state and focus state for debugging. - Log.e(TAG, "take picture failed."); - throw e; - } - } - public void requestTakePicture( final ShutterCallback shutter, final PictureCallback raw, @@ -324,7 +314,13 @@ class AndroidCameraAgentImpl extends CameraAgent { @Override public void handleMessage(final Message msg) { super.handleMessage(msg); + + if (getCameraState().isInvalid()) { + Log.v(TAG, "Skip handleMessage - action = '" + CameraActions.stringify(msg.what) + "'"); + return; + } Log.v(TAG, "handleMessage - action = '" + CameraActions.stringify(msg.what) + "'"); + try { switch (msg.what) { case CameraActions.OPEN_CAMERA: { @@ -348,9 +344,9 @@ class AndroidCameraAgentImpl extends CameraAgent { mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE); if (openCallback != null) { - openCallback.onCameraOpened( - new AndroidCameraProxyImpl(cameraId, mCamera, - mCharacteristics, mCapabilities)); + CameraProxy cameraProxy = new AndroidCameraProxyImpl( + mAgent, cameraId, mCamera, mCharacteristics, mCapabilities); + openCallback.onCameraOpened(cameraProxy); } } else { if (openCallback != null) { @@ -379,8 +375,7 @@ class AndroidCameraAgentImpl extends CameraAgent { mCamera.reconnect(); } catch (IOException ex) { if (cbForward != null) { - cbForward.onReconnectionFailure(AndroidCameraAgentImpl.this, - generateHistoryString(mCameraId)); + cbForward.onReconnectionFailure(mAgent, generateHistoryString(mCameraId)); } break; } @@ -388,8 +383,8 @@ class AndroidCameraAgentImpl extends CameraAgent { mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE); if (cbForward != null) { cbForward.onCameraOpened( - new AndroidCameraProxyImpl(cameraId, mCamera, mCharacteristics, - mCapabilities)); + new AndroidCameraProxyImpl(AndroidCameraAgentImpl.this, + cameraId, mCamera, mCharacteristics, mCapabilities)); } break; } @@ -573,45 +568,49 @@ class AndroidCameraAgentImpl extends CameraAgent { case CameraActions.CAPTURE_PHOTO: { mCameraState.setState(AndroidCameraStateHolder.CAMERA_CAPTURING); - capture((CaptureCallbacks) msg.obj); + CaptureCallbacks captureCallbacks = (CaptureCallbacks) msg.obj; + mCamera.takePicture( + captureCallbacks.mShutter, + captureCallbacks.mRaw, + captureCallbacks.mPostView, + captureCallbacks.mJpeg); break; } default: { - throw new RuntimeException("Invalid CameraProxy message=" + msg.what); + Log.e(TAG, "Invalid CameraProxy message=" + msg.what); } } - } catch (final RuntimeException e) { - Log.e(TAG, "Exception during camera operation " + msg.what, e); - if (msg.what != CameraActions.RELEASE && mCamera != null) { + } catch (final RuntimeException ex) { + String cameraAction = CameraActions.stringify(msg.what); + int cameraState = mCameraState.getState(); + String errorContext = + "CameraAction[" + cameraAction + "] at CameraState[" + cameraState + "]"; + Log.e(TAG, "RuntimeException during " + errorContext, ex); + + // Be conservative by invalidating both CameraAgent and CameraProxy objects. + mCameraState.invalidate(); + + if (mCamera != null) { + Log.i(TAG, "Release camera since mCamera is not null."); try { mCamera.release(); - mCameraState.setState(AndroidCameraStateHolder.CAMERA_UNOPENED); - } catch (Exception ex) { - Log.e(TAG, "Fail to release the camera."); - } - mCamera = null; - } else { - if (mCamera == null) { - if (msg.what == CameraActions.OPEN_CAMERA) { - final int cameraId = msg.arg1; - if (msg.obj != null) { - ((CameraOpenCallback) msg.obj).onDeviceOpenFailure( - msg.arg1, generateHistoryString(cameraId)); - } - } else { - Log.w(TAG, "Cannot handle message " + msg.what + ", mCamera is null."); - } - return; + } catch (Exception e) { + Log.e(TAG, "Fail when calling Camera.release().", e); + } finally { + mCamera = null; } } - synchronized (mCameraExceptionCallback) { - mCameraExceptionCallbackHandler.post(new Runnable() { - @Override - public void run() { - mCameraExceptionCallback.onCameraException(e); - } - }); + + // Invoke error callback. + if (msg.what == CameraActions.OPEN_CAMERA && mCamera == null) { + final int cameraId = msg.arg1; + if (msg.obj != null) { + ((CameraOpenCallback) msg.obj).onDeviceOpenFailure( + msg.arg1, generateHistoryString(cameraId)); + } + } else { + mAgent.getCameraExceptionHandler().onCameraException(ex); } } finally { WaitDoneBundle.unblockSyncWaiters(msg); @@ -720,15 +719,20 @@ class AndroidCameraAgentImpl extends CameraAgent { * camera handler thread. */ private class AndroidCameraProxyImpl extends CameraAgent.CameraProxy { + private final CameraAgent mCameraAgent; private final int mCameraId; /* TODO: remove this Camera instance. */ private final Camera mCamera; private final CameraDeviceInfo.Characteristics mCharacteristics; private final AndroidCameraCapabilities mCapabilities; - private AndroidCameraProxyImpl(int cameraId, Camera camera, + private AndroidCameraProxyImpl( + CameraAgent cameraAgent, + int cameraId, + Camera camera, CameraDeviceInfo.Characteristics characteristics, AndroidCameraCapabilities capabilities) { + mCameraAgent = cameraAgent; mCamera = camera; mCameraId = cameraId; mCharacteristics = characteristics; @@ -738,6 +742,9 @@ class AndroidCameraAgentImpl extends CameraAgent { @Deprecated @Override public android.hardware.Camera getCamera() { + if (getCameraState().isInvalid()) { + return null; + } return mCamera; } @@ -756,6 +763,11 @@ class AndroidCameraAgentImpl extends CameraAgent { return new AndroidCameraCapabilities(mCapabilities); } + @Override + public CameraAgent getAgent() { + return mCameraAgent; + } + @Override public void setPreviewDataCallback( final Handler handler, final CameraPreviewDataCallback cb) { @@ -819,6 +831,10 @@ class AndroidCameraAgentImpl extends CameraAgent { mDispatchThread.runJob(new Runnable() { @Override public void run() { + // Don't bother to wait since camera is in bad state. + if (getCameraState().isInvalid()) { + return; + } mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE); mCameraHandler.obtainMessage(CameraActions.AUTO_FOCUS, afCallback) .sendToTarget(); @@ -830,15 +846,19 @@ class AndroidCameraAgentImpl extends CameraAgent { @Override public void setAutoFocusMoveCallback( final Handler handler, final CameraAFMoveCallback cb) { - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - mCameraHandler.obtainMessage(CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK, - AFMoveCallbackForward.getNewInstance( - handler, AndroidCameraProxyImpl.this, cb)) - .sendToTarget(); - } - }); + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + mCameraHandler.obtainMessage(CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK, + AFMoveCallbackForward.getNewInstance( + handler, AndroidCameraProxyImpl.this, cb)) + .sendToTarget(); + } + }); + } catch (final RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @Override @@ -863,59 +883,79 @@ class AndroidCameraAgentImpl extends CameraAgent { } }; - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE | - AndroidCameraStateHolder.CAMERA_UNLOCKED); - mCameraHandler.requestTakePicture(ShutterCallbackForward - .getNewInstance(handler, AndroidCameraProxyImpl.this, shutter), - PictureCallbackForward - .getNewInstance(handler, AndroidCameraProxyImpl.this, raw), - PictureCallbackForward - .getNewInstance(handler, AndroidCameraProxyImpl.this, post), - jpegCallback - ); - } - }); + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + // Don't bother to wait since camera is in bad state. + if (getCameraState().isInvalid()) { + return; + } + mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE | + AndroidCameraStateHolder.CAMERA_UNLOCKED); + mCameraHandler.requestTakePicture(ShutterCallbackForward + .getNewInstance(handler, AndroidCameraProxyImpl.this, shutter), + PictureCallbackForward + .getNewInstance(handler, AndroidCameraProxyImpl.this, raw), + PictureCallbackForward + .getNewInstance(handler, AndroidCameraProxyImpl.this, post), + jpegCallback + ); + } + }); + } catch (final RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @Override public void setZoomChangeListener(final OnZoomChangeListener listener) { - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - mCameraHandler.obtainMessage(CameraActions.SET_ZOOM_CHANGE_LISTENER, listener) - .sendToTarget(); - } - }); + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + mCameraHandler.obtainMessage(CameraActions.SET_ZOOM_CHANGE_LISTENER, listener) + .sendToTarget(); + } + }); + } catch (final RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @Override public void setFaceDetectionCallback(final Handler handler, final CameraFaceDetectionCallback cb) { - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - mCameraHandler.obtainMessage(CameraActions.SET_FACE_DETECTION_LISTENER, - FaceDetectionCallbackForward - .getNewInstance(handler, AndroidCameraProxyImpl.this, cb)) - .sendToTarget(); - } - }); + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + mCameraHandler.obtainMessage(CameraActions.SET_FACE_DETECTION_LISTENER, + FaceDetectionCallbackForward + .getNewInstance(handler, AndroidCameraProxyImpl.this, cb)) + .sendToTarget(); + } + }); + } catch (final RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @Override public void setErrorCallback(final Handler handler, final CameraErrorCallback cb) { - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - mCameraHandler.obtainMessage(CameraActions.SET_ERROR_CALLBACK, - ErrorCallbackForward.getNewInstance( - handler, AndroidCameraProxyImpl.this, cb)) - .sendToTarget(); - } - }); + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + mCameraHandler.obtainMessage(CameraActions.SET_ERROR_CALLBACK, + ErrorCallbackForward.getNewInstance( + handler, AndroidCameraProxyImpl.this, cb)) + .sendToTarget(); + } + }); + } catch (final RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @Deprecated @@ -926,15 +966,19 @@ class AndroidCameraAgentImpl extends CameraAgent { return; } final String flattenedParameters = params.flatten(); - mDispatchThread.runJob(new Runnable() { - @Override - public void run() { - mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE | - AndroidCameraStateHolder.CAMERA_UNLOCKED); - mCameraHandler.obtainMessage(CameraActions.SET_PARAMETERS, flattenedParameters) - .sendToTarget(); - } - }); + try { + mDispatchThread.runJob(new Runnable() { + @Override + public void run() { + mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE | + AndroidCameraStateHolder.CAMERA_UNLOCKED); + mCameraHandler.obtainMessage(CameraActions.SET_PARAMETERS, flattenedParameters) + .sendToTarget(); + } + }); + } catch (final RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @Deprecated @@ -942,14 +986,18 @@ class AndroidCameraAgentImpl extends CameraAgent { public Parameters getParameters() { final WaitDoneBundle bundle = new WaitDoneBundle(); final Parameters[] parametersHolder = new Parameters[1]; - mDispatchThread.runJobSync(new Runnable() { - @Override - public void run() { - mCameraHandler.obtainMessage( - CameraActions.GET_PARAMETERS, parametersHolder).sendToTarget(); - mCameraHandler.post(bundle.mUnlockRunnable); - } - }, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "get parameters"); + try { + mDispatchThread.runJobSync(new Runnable() { + @Override + public void run() { + mCameraHandler.obtainMessage( + CameraActions.GET_PARAMETERS, parametersHolder).sendToTarget(); + mCameraHandler.post(bundle.mUnlockRunnable); + } + }, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "get parameters"); + } catch (final RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } return parametersHolder[0]; } diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java index 17cbc02..d44cacd 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java @@ -43,7 +43,7 @@ import com.android.ex.camera2.portability.debug.Log; * {@code android.hardware.Camera.OnZoomChangeListener}, and */ public abstract class CameraAgent { - public static final long CAMERA_OPERATION_TIMEOUT_MS = 2500; + public static final long CAMERA_OPERATION_TIMEOUT_MS = 3500; private static final Log.Tag TAG = new Log.Tag("CamAgnt"); @@ -154,14 +154,6 @@ public abstract class CameraAgent { } } - /** - * A handler for all camera api runtime exceptions. - * The default behavior is to throw the runtime exception. - */ - public static interface CameraExceptionCallback { - public void onCameraException(RuntimeException e); - } - /** * An interface which wraps * {@link android.hardware.Camera.ErrorCallback} @@ -293,12 +285,17 @@ public abstract class CameraAgent { */ public void openCamera(final Handler handler, final int cameraId, final CameraOpenCallback callback) { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler().obtainMessage(CameraActions.OPEN_CAMERA, cameraId, 0, - CameraOpenCallbackForward.getNewInstance(handler, callback)).sendToTarget(); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler().obtainMessage(CameraActions.OPEN_CAMERA, cameraId, 0, + CameraOpenCallbackForward.getNewInstance(handler, callback)).sendToTarget(); + } + }); + } catch (final RuntimeException ex) { + getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -308,22 +305,30 @@ public abstract class CameraAgent { * @param synced Whether this call should be synchronous. */ public void closeCamera(CameraProxy camera, boolean synced) { - if (synced) { - final WaitDoneBundle bundle = new WaitDoneBundle(); - - getDispatchThread().runJobSync(new Runnable() { - @Override - public void run() { - getCameraHandler().obtainMessage(CameraActions.RELEASE).sendToTarget(); - getCameraHandler().post(bundle.mUnlockRunnable); - }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "camera release"); - } else { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler().removeCallbacksAndMessages(null); - getCameraHandler().obtainMessage(CameraActions.RELEASE).sendToTarget(); - }}); + try { + if (synced) { + // Don't bother to wait since camera is in bad state. + if (getCameraState().isInvalid()) { + return; + } + final WaitDoneBundle bundle = new WaitDoneBundle(); + + getDispatchThread().runJobSync(new Runnable() { + @Override + public void run() { + getCameraHandler().obtainMessage(CameraActions.RELEASE).sendToTarget(); + getCameraHandler().post(bundle.mUnlockRunnable); + }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "camera release"); + } else { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler().removeCallbacksAndMessages(null); + getCameraHandler().obtainMessage(CameraActions.RELEASE).sendToTarget(); + }}); + } + } catch (final RuntimeException ex) { + getCameraExceptionHandler().onDispatchThreadException(ex); } } @@ -331,8 +336,7 @@ public abstract class CameraAgent { * Sets a callback for handling camera api runtime exceptions on * a handler. */ - public abstract void setCameraDefaultExceptionCallback(CameraExceptionCallback callback, - Handler handler); + public abstract void setCameraExceptionHandler(CameraExceptionHandler exceptionHandler); /** * Recycles the resources used by this instance. CameraAgent will be in @@ -355,12 +359,22 @@ public abstract class CameraAgent { */ protected abstract DispatchThread getDispatchThread(); + /** + * @return The state machine tracking the camera API's current status. + */ + protected abstract CameraStateHolder getCameraState(); + + /** + * @return The exception handler. + */ + protected abstract CameraExceptionHandler getCameraExceptionHandler(); + /** * An interface that takes camera operation requests and post messages to the * camera handler thread. All camera operations made through this interface is * asynchronous by default except those mentioned specifically. */ - public static abstract class CameraProxy { + public abstract static class CameraProxy { /** * Returns the underlying {@link android.hardware.Camera} object used @@ -387,6 +401,11 @@ public abstract class CameraAgent { */ public abstract CameraCapabilities getCapabilities(); + /** + * @return The camera agent which creates this proxy. + */ + public abstract CameraAgent getAgent(); + /** * Reconnects to the camera device. On success, the camera device will * be returned through {@link CameraAgent @@ -399,12 +418,16 @@ public abstract class CameraAgent { * @param cb The callback when any error happens. */ public void reconnect(final Handler handler, final CameraOpenCallback cb) { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler().obtainMessage(CameraActions.RECONNECT, getCameraId(), 0, - CameraOpenCallbackForward.getNewInstance(handler, cb)).sendToTarget(); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler().obtainMessage(CameraActions.RECONNECT, getCameraId(), 0, + CameraOpenCallbackForward.getNewInstance(handler, cb)).sendToTarget(); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -413,13 +436,22 @@ public abstract class CameraAgent { * @see android.hardware.Camera#unlock() */ public void unlock() { + // Don't bother to wait since camera is in bad state. + if (getCameraState().isInvalid()) { + return; + } final WaitDoneBundle bundle = new WaitDoneBundle(); - getDispatchThread().runJobSync(new Runnable() { - @Override - public void run() { - getCameraHandler().sendEmptyMessage(CameraActions.UNLOCK); - getCameraHandler().post(bundle.mUnlockRunnable); - }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "camera unlock"); + try { + getDispatchThread().runJobSync(new Runnable() { + @Override + public void run() { + getCameraHandler().sendEmptyMessage(CameraActions.UNLOCK); + getCameraHandler().post(bundle.mUnlockRunnable); + } + }, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "camera unlock"); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -427,11 +459,15 @@ public abstract class CameraAgent { * @see android.hardware.Camera#lock() */ public void lock() { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler().sendEmptyMessage(CameraActions.LOCK); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler().sendEmptyMessage(CameraActions.LOCK); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -456,13 +492,17 @@ public abstract class CameraAgent { // the STOP_PREVIEW case in its handler; in the meantime, changing API 2 // sizes would require closing and reopening the camera. public void setPreviewTexture(final SurfaceTexture surfaceTexture) { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler() - .obtainMessage(CameraActions.SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture) - .sendToTarget(); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler() + .obtainMessage(CameraActions.SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture) + .sendToTarget(); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -480,15 +520,23 @@ public abstract class CameraAgent { * @see CameraSettings#setPreviewSize */ public void setPreviewTextureSync(final SurfaceTexture surfaceTexture) { + // Don't bother to wait since camera is in bad state. + if (getCameraState().isInvalid()) { + return; + } final WaitDoneBundle bundle = new WaitDoneBundle(); - getDispatchThread().runJobSync(new Runnable() { - @Override - public void run() { - getCameraHandler() - .obtainMessage(CameraActions.SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture) - .sendToTarget(); - getCameraHandler().post(bundle.mUnlockRunnable); - }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "set preview texture"); + try { + getDispatchThread().runJobSync(new Runnable() { + @Override + public void run() { + getCameraHandler() + .obtainMessage(CameraActions.SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture) + .sendToTarget(); + getCameraHandler().post(bundle.mUnlockRunnable); + }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "set preview texture"); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -497,25 +545,33 @@ public abstract class CameraAgent { * @param surfaceHolder The {@link SurfaceHolder} for preview. */ public void setPreviewDisplay(final SurfaceHolder surfaceHolder) { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler() - .obtainMessage(CameraActions.SET_PREVIEW_DISPLAY_ASYNC, surfaceHolder) - .sendToTarget(); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler() + .obtainMessage(CameraActions.SET_PREVIEW_DISPLAY_ASYNC, surfaceHolder) + .sendToTarget(); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** * Starts the camera preview. */ public void startPreview() { + try { getDispatchThread().runJob(new Runnable() { @Override public void run() { getCameraHandler() .obtainMessage(CameraActions.START_PREVIEW_ASYNC, null).sendToTarget(); }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -523,6 +579,7 @@ public abstract class CameraAgent { * the preview starts. */ public void startPreviewWithCallback(final Handler h, final CameraStartPreviewCallback cb) { + try { getDispatchThread().runJob(new Runnable() { @Override public void run() { @@ -530,6 +587,9 @@ public abstract class CameraAgent { CameraStartPreviewCallbackForward.getNewInstance(h, cb)) .sendToTarget(); }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -538,13 +598,21 @@ public abstract class CameraAgent { * continues to release resources related to camera preview. */ public void stopPreview() { + // Don't bother to wait since camera is in bad state. + if (getCameraState().isInvalid()) { + return; + } final WaitDoneBundle bundle = new WaitDoneBundle(); - getDispatchThread().runJobSync(new Runnable() { - @Override - public void run() { - getCameraHandler().obtainMessage(CameraActions.STOP_PREVIEW, bundle) - .sendToTarget(); - }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "stop preview"); + try { + getDispatchThread().runJobSync(new Runnable() { + @Override + public void run() { + getCameraHandler().obtainMessage(CameraActions.STOP_PREVIEW, bundle) + .sendToTarget(); + }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "stop preview"); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -583,14 +651,18 @@ public abstract class CameraAgent { * @param callbackBuffer The buffer allocated for the preview data. */ public void addCallbackBuffer(final byte[] callbackBuffer) { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler() - .obtainMessage(CameraActions.ADD_CALLBACK_BUFFER, callbackBuffer) - .sendToTarget(); - } - }); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler() + .obtainMessage(CameraActions.ADD_CALLBACK_BUFFER, callbackBuffer) + .sendToTarget(); + } + }); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -667,24 +739,32 @@ public abstract class CameraAgent { * @param capture Whether to adjust the JPEG capture orientation as well as the preview one. */ public void setDisplayOrientation(final int degrees, final boolean capture) { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler() - .obtainMessage(CameraActions.SET_DISPLAY_ORIENTATION, degrees, - capture ? 1 : 0) - .sendToTarget(); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler() + .obtainMessage(CameraActions.SET_DISPLAY_ORIENTATION, degrees, + capture ? 1 : 0) + .sendToTarget(); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } public void setJpegOrientation(final int degrees) { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler() - .obtainMessage(CameraActions.SET_JPEG_ORIENTATION, degrees, 0) - .sendToTarget(); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler() + .obtainMessage(CameraActions.SET_JPEG_ORIENTATION, degrees, 0) + .sendToTarget(); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -707,22 +787,30 @@ public abstract class CameraAgent { * Starts the face detection. */ public void startFaceDetection() { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler().sendEmptyMessage(CameraActions.START_FACE_DETECTION); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler().sendEmptyMessage(CameraActions.START_FACE_DETECTION); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** * Stops the face detection. */ public void stopFaceDetection() { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler().sendEmptyMessage(CameraActions.STOP_FACE_DETECTION); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler().sendEmptyMessage(CameraActions.STOP_FACE_DETECTION); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -780,13 +868,22 @@ public abstract class CameraAgent { } final CameraSettings copyOfSettings = settings.copy(); - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraState().waitForStates(statesToAwait); - getCameraHandler().obtainMessage(CameraActions.APPLY_SETTINGS, copyOfSettings) - .sendToTarget(); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + CameraStateHolder cameraState = getCameraState(); + // Don't bother to wait since camera is in bad state. + if (cameraState.isInvalid()) { + return; + } + cameraState.waitForStates(statesToAwait); + getCameraHandler().obtainMessage(CameraActions.APPLY_SETTINGS, copyOfSettings) + .sendToTarget(); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } return true; } @@ -806,11 +903,15 @@ public abstract class CameraAgent { * settings regardless of the dirty bit. */ public void refreshSettings() { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler().sendEmptyMessage(CameraActions.REFRESH_PARAMETERS); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler().sendEmptyMessage(CameraActions.REFRESH_PARAMETERS); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -820,13 +921,17 @@ public abstract class CameraAgent { * {@code false} to disable it. */ public void enableShutterSound(final boolean enable) { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler() - .obtainMessage(CameraActions.ENABLE_SHUTTER_SOUND, (enable ? 1 : 0), 0) - .sendToTarget(); - }}); + try { + getDispatchThread().runJob(new Runnable() { + @Override + public void run() { + getCameraHandler() + .obtainMessage(CameraActions.ENABLE_SHUTTER_SOUND, (enable ? 1 : 0), 0) + .sendToTarget(); + }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java new file mode 100644 index 0000000..7270b22 --- /dev/null +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ex.camera2.portability; + +import android.os.Handler; + +/** + * A handler for all camera api runtime exceptions. + * The default behavior is to throw the runtime exception. + */ +public class CameraExceptionHandler { + private Handler mHandler; + + private CameraExceptionCallback mCallback = + new CameraExceptionCallback() { + @Override + public void onCameraException(RuntimeException e) { + throw e; + } + + @Override + public void onDispatchThreadException(RuntimeException e) { + throw e; + } + }; + + /** + * A callback helps to handle RuntimeException thrown by camera framework. + */ + public static interface CameraExceptionCallback { + public void onCameraException(RuntimeException e); + public void onDispatchThreadException(RuntimeException e); + } + + /** + * Construct a new instance of {@link CameraExceptionHandler} with a custom callback which will + * be executed on a specific {@link Handler}. + * + * @param callback The callback which will be invoked. + * @param handler The handler in which the callback will be invoked in. + */ + public CameraExceptionHandler(CameraExceptionCallback callback, Handler handler) { + mHandler = handler; + mCallback = callback; + } + + /** + * Construct a new instance of {@link CameraExceptionHandler} with a default callback which will + * be executed on a specific {@link Handler}. + * + * @param handler The handler in which the default callback will be invoked in. + */ + public CameraExceptionHandler(Handler handler) { + mHandler = handler; + } + + /** + * Invoke @{link CameraExceptionCallback} when a runtime exception is thrown by Android camera + * framework. + * + * @param ex The runtime exception object. + */ + public void onCameraException(final RuntimeException ex) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onCameraException(ex); + } + }); + } + + /** + * Invoke @{link CameraExceptionCallback} when a runtime exception is thrown by + * @{link DispatchThread}. + * + * @param ex The runtime exception object. + */ + public void onDispatchThreadException(final RuntimeException ex) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onDispatchThreadException(ex); + } + }); + } +} + diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraStateHolder.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraStateHolder.java index c8d82b6..b758af2 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraStateHolder.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraStateHolder.java @@ -24,11 +24,23 @@ public abstract class CameraStateHolder { private static final Log.Tag TAG = new Log.Tag("CamStateHolder"); private int mState; + private boolean mInvalid; + /** + * Construct a new instance of @{link CameraStateHolder} with an initial state. + * + * @param state The initial state. + */ public CameraStateHolder(int state) { setState(state); + mInvalid = false; } + /** + * Change to a new state. + * + * @param state The new state. + */ public synchronized void setState(int state) { if (mState != state) { Log.v(TAG, "setState - state = " + Integer.toBinaryString(state)); @@ -37,10 +49,31 @@ public abstract class CameraStateHolder { this.notifyAll(); } + /** + * Obtain the current state. + * + * @return The current state. + */ public synchronized int getState() { return mState; } + /** + * Change the state to be invalid. Once invalidated, the state will be invalid forever. + */ + public synchronized void invalidate() { + mInvalid = true; + } + + /** + * Whether the state is invalid. + * + * @return True if the state is invalid. + */ + public synchronized boolean isInvalid() { + return mInvalid; + } + private static interface ConditionChecker { /** * @return Whether the condition holds. -- cgit v1.2.3 From c15ddae4862bb9a10b0142fac9558a5f899b1a21 Mon Sep 17 00:00:00 2001 From: Alan Newberger Date: Mon, 27 Oct 2014 14:23:23 -0700 Subject: Ensure agent error handler is always non-null Create a dummy placeholder handler which logs warnings, which can be replaced by app if desired. This makes various assumptions valid that this is non-null. Bug: 18137167 Change-Id: I0f508dff3cfe766ccc5a3c5a26c6196f4fb31e04 --- .../ex/camera2/portability/AndroidCameraAgentImpl.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java index 9910dd9..cdd6993 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java @@ -55,7 +55,23 @@ class AndroidCameraAgentImpl extends CameraAgent { private final HandlerThread mCameraHandlerThread; private final CameraStateHolder mCameraState; private final DispatchThread mDispatchThread; - private CameraExceptionHandler mExceptionHandler; + private CameraExceptionHandler mExceptionHandler = new CameraExceptionHandler(null) { + @Override + public void onCameraError(int errorCode) { + Log.w(TAG, "onCameraError called before handler set: " + errorCode); + } + + @Override + public void onCameraException(RuntimeException ex, String commandHistory, int action, + int state) { + Log.w(TAG, "onCameraException called before handler set", ex); + } + + @Override + public void onDispatchThreadException(RuntimeException ex) { + Log.w(TAG, "onDispatchThreadException called before handler set", ex); + } + }; AndroidCameraAgentImpl() { mCameraHandlerThread = new HandlerThread("Camera Handler Thread"); -- cgit v1.2.3 From 25b78c8a18327bd73c3d4d34887dd63953aeaa8c Mon Sep 17 00:00:00 2001 From: Alan Newberger Date: Tue, 4 Nov 2014 10:18:41 -0800 Subject: Ensure default handler always is set if no other error handler registered To avoid memory leaks we set the error handler to null in activity onDestroy. But, the agent may receive callbacks after this, so ensure at minimum that we set the default logging handler to default handle errors post activity destroy. Bug: 18200874 Bug: 18202261 Change-Id: Ie7251a149ab02c474d8affd15af6b9ea48c2cdc0 --- .../ex/camera2/portability/AndroidCameraAgentImpl.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java index cdd6993..201a905 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java @@ -55,24 +55,28 @@ class AndroidCameraAgentImpl extends CameraAgent { private final HandlerThread mCameraHandlerThread; private final CameraStateHolder mCameraState; private final DispatchThread mDispatchThread; - private CameraExceptionHandler mExceptionHandler = new CameraExceptionHandler(null) { + + private static final CameraExceptionHandler sDefaultExceptionHandler = + new CameraExceptionHandler(null) { @Override public void onCameraError(int errorCode) { - Log.w(TAG, "onCameraError called before handler set: " + errorCode); + Log.w(TAG, "onCameraError called with no handler set: " + errorCode); } @Override public void onCameraException(RuntimeException ex, String commandHistory, int action, int state) { - Log.w(TAG, "onCameraException called before handler set", ex); + Log.w(TAG, "onCameraException called with no handler set", ex); } @Override public void onDispatchThreadException(RuntimeException ex) { - Log.w(TAG, "onDispatchThreadException called before handler set", ex); + Log.w(TAG, "onDispatchThreadException called with no handler set", ex); } }; + private CameraExceptionHandler mExceptionHandler = sDefaultExceptionHandler; + AndroidCameraAgentImpl() { mCameraHandlerThread = new HandlerThread("Camera Handler Thread"); mCameraHandlerThread.start(); @@ -117,7 +121,8 @@ class AndroidCameraAgentImpl extends CameraAgent { @Override public void setCameraExceptionHandler(CameraExceptionHandler exceptionHandler) { - mExceptionHandler = exceptionHandler; + // In case of null set the default handler to route exceptions to logs + mExceptionHandler = exceptionHandler != null ? exceptionHandler : sDefaultExceptionHandler; } private static class AndroidCameraDeviceInfo implements CameraDeviceInfo { -- cgit v1.2.3 From 7158b6705d330ac1e61d5aa51c51b54507c4ab88 Mon Sep 17 00:00:00 2001 From: Jay Shrauner Date: Tue, 16 Dec 2014 16:06:03 -0800 Subject: Fix StaleDataExceptions In ensureCacheValid, check whether the cursor is closed before querying its count. In getItem, check whether the cursor is closed before moving it to position, as was already being done in getItemId. Bug:18815354 Change-Id: I4cf2509923695afe9499d0507383c4e0d51bd23e --- .../com/android/common/widget/CompositeCursorAdapter.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/common/java/com/android/common/widget/CompositeCursorAdapter.java b/common/java/com/android/common/widget/CompositeCursorAdapter.java index dddbcf6..8a3fa9b 100644 --- a/common/java/com/android/common/widget/CompositeCursorAdapter.java +++ b/common/java/com/android/common/widget/CompositeCursorAdapter.java @@ -170,7 +170,12 @@ public abstract class CompositeCursorAdapter extends BaseAdapter { mCount = 0; for (Partition partition : mPartitions) { Cursor cursor = partition.cursor; - int count = cursor != null ? cursor.getCount() : 0; + int count; + if (cursor == null || cursor.isClosed()) { + count = 0; + } else { + count = cursor.getCount(); + } if (partition.hasHeader) { if (count != 0 || partition.showIfEmpty) { count++; @@ -428,7 +433,9 @@ public abstract class CompositeCursorAdapter extends BaseAdapter { return null; } Cursor cursor = mPartition.cursor; - cursor.moveToPosition(offset); + if (cursor == null || cursor.isClosed() || !cursor.moveToPosition(offset)) { + return null; + } return cursor; } start = end; -- cgit v1.2.3