diff options
Diffstat (limited to 'camera2/portability/src')
10 files changed, 741 insertions, 425 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 a746634..2fc4ad3 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(); @@ -134,11 +136,6 @@ class AndroidCamera2AgentImpl extends CameraAgent { // TODO: Implement @Override - public void setCameraDefaultExceptionCallback(CameraExceptionCallback callback, - Handler handler) {} - - // TODO: Implement - @Override public void recycle() {} // TODO: Some indices may now be invalid; ensure everyone can handle that and update the docs @@ -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 {}; @@ -210,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; @@ -628,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)); @@ -645,11 +658,9 @@ class AndroidCamera2AgentImpl extends CameraAgent { } if (ex instanceof RuntimeException) { - post(new Runnable() { - @Override - public void run() { - sCameraExceptionCallback.onCameraException((RuntimeException) ex); - }}); + String commandHistory = generateHistoryString(Integer.parseInt(mCameraId)); + mExceptionHandler.onCameraException((RuntimeException) ex, commandHistory, + cameraAction, mCameraState.getState()); } } finally { WaitDoneBundle.unblockSyncWaiters(msg); @@ -771,8 +782,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 +973,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 +981,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 +1015,10 @@ class AndroidCamera2AgentImpl extends CameraAgent { return mCapabilities; } + public CameraAgent getAgent() { + return mCameraAgent; + } + private AndroidCamera2Capabilities getSpecializedCapabilities() { return mCapabilities; } @@ -1039,53 +1061,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 +1163,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 @@ -1157,10 +1198,6 @@ class AndroidCamera2AgentImpl extends CameraAgent { // TODO: Implement @Override - public void setErrorCallback(Handler handler, CameraErrorCallback cb) {} - - // TODO: Implement - @Override public void setParameters(android.hardware.Camera.Parameters params) {} // TODO: Implement @@ -1380,12 +1417,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/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; } 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..201a905 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java @@ -56,38 +56,42 @@ class AndroidCameraAgentImpl extends CameraAgent { 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 static final CameraExceptionHandler sDefaultExceptionHandler = + new CameraExceptionHandler(null) { + @Override + public void onCameraError(int 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 with no handler set", ex); + } + + @Override + public void onDispatchThreadException(RuntimeException ex) { + Log.w(TAG, "onDispatchThreadException called with no handler set", ex); + } + }; + + private CameraExceptionHandler mExceptionHandler = sDefaultExceptionHandler; 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 +109,22 @@ class AndroidCameraAgentImpl extends CameraAgent { return mDispatchThread; } + @Override + protected CameraStateHolder getCameraState() { + return mCameraState; + } + + @Override + protected CameraExceptionHandler getCameraExceptionHandler() { + return mExceptionHandler; + } + + @Override + public void setCameraExceptionHandler(CameraExceptionHandler 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 { private final Camera.CameraInfo[] mCameraInfos; private final int mNumberOfCameras; @@ -237,10 +257,10 @@ 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; + private int mCameraId = -1; private ParametersCache mParameterCache; private int mCancelAfPending = 0; @@ -259,8 +279,9 @@ class AndroidCameraAgentImpl extends CameraAgent { } } - CameraHandler(Looper looper) { + CameraHandler(CameraAgent agent, Looper looper) { super(looper); + mAgent = agent; } private void startFaceDetection() { @@ -298,16 +319,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, @@ -317,6 +328,19 @@ 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) { + int lastCameraAction = getCurrentMessage(); + mExceptionHandler.onCameraException( + new RuntimeException("Media server died."), + generateHistoryString(mCameraId), + lastCameraAction, + mCameraState.getState()); + } + } + /** * This method does not deal with the API level check. Everyone should * check first for supported operations before sending message to this handler. @@ -324,9 +348,16 @@ 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) + "'"); + + 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; @@ -346,11 +377,13 @@ class AndroidCameraAgentImpl extends CameraAgent { mCapabilities = new AndroidCameraCapabilities( mParameterCache.getBlocking()); + mCamera.setErrorCallback(this); + 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) { @@ -365,6 +398,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."); } @@ -379,8 +413,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 +421,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; } @@ -527,11 +560,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; @@ -573,45 +601,50 @@ 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) { + int cameraState = mCameraState.getState(); + 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. + 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 { + CameraExceptionHandler exceptionHandler = mAgent.getCameraExceptionHandler(); + exceptionHandler.onCameraException( + ex, generateHistoryString(mCameraId), cameraAction, cameraState); } } finally { WaitDoneBundle.unblockSyncWaiters(msg); @@ -670,7 +703,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(); @@ -720,15 +755,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 +778,9 @@ class AndroidCameraAgentImpl extends CameraAgent { @Deprecated @Override public android.hardware.Camera getCamera() { + if (getCameraState().isInvalid()) { + return null; + } return mCamera; } @@ -757,6 +800,11 @@ class AndroidCameraAgentImpl extends CameraAgent { } @Override + public CameraAgent getAgent() { + return mCameraAgent; + } + + @Override public void setPreviewDataCallback( final Handler handler, final CameraPreviewDataCallback cb) { mDispatchThread.runJob(new Runnable() { @@ -819,6 +867,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 +882,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 +919,62 @@ 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(); - } - }); - } - - @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_FACE_DETECTION_LISTENER, + FaceDetectionCallbackForward + .getNewInstance(handler, AndroidCameraProxyImpl.this, cb)) + .sendToTarget(); + } + }); + } catch (final RuntimeException ex) { + mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); + } } @Deprecated @@ -926,15 +985,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 +1005,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]; } @@ -1059,49 +1126,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/AndroidCameraCapabilities.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraCapabilities.java index 85b20dd..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,8 +47,6 @@ class AndroidCameraCapabilities extends CameraCapabilities { mPreferredPreviewSizeForVideo = new Size(p.getPreferredPreviewSizeForVideo()); mSupportedPreviewFormats.addAll(p.getSupportedPreviewFormats()); mSupportedPhotoFormats.addAll(p.getSupportedPictureFormats()); - mMaxZoomRatio = p.isZoomSupported() ? - p.getZoomRatios().get(p.getMaxZoom()) / ZOOM_MULTIPLIER : ZOOM_RATIO_UNZOOMED; mHorizontalViewAngle = p.getHorizontalViewAngle(); mVerticalViewAngle = p.getVerticalViewAngle(); buildPreviewFpsRange(p); @@ -61,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()) { 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 2d3f90e..66762fd 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 = 5000; + public static final long CAMERA_OPERATION_TIMEOUT_MS = 3500; private static final Log.Tag TAG = new Log.Tag("CamAgnt"); @@ -155,14 +155,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 @@ -356,11 +360,21 @@ 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 @@ -388,6 +402,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 * .CameraOpenCallback#onCameraOpened(com.android.camera.cameradevice.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,34 +787,33 @@ 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); + } } /** - * 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. * * @param params The camera parameters to use. @@ -780,13 +859,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 +894,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 +912,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..dc71b4b --- /dev/null +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java @@ -0,0 +1,123 @@ +/* + * 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 onCameraError(int errorCode) { + } + @Override + public void onCameraException( + RuntimeException e, String commandHistory, int action, int state) { + 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 onCameraError(int errorCode); + public void onCameraException( + RuntimeException e, String commandHistory, int action, int state); + 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 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. + * + * @param ex The runtime exception object. + */ + 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, commandHistory, action, state); + } + }); + } + + /** + * 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/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); } } 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. 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; |