diff options
39 files changed, 929 insertions, 472 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 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()) { 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 17cbc02..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 = 2500; + 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; 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; diff --git a/framesequence/Android.mk b/framesequence/Android.mk index cc2c16c..1b3cf2d 100644 --- a/framesequence/Android.mk +++ b/framesequence/Android.mk @@ -21,6 +21,8 @@ LOCAL_MODULE := android-common-framesequence LOCAL_SDK_VERSION := 8 LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_PROGUARD_FLAG_FILES := proguard.flags + include $(BUILD_STATIC_JAVA_LIBRARY) include $(call all-makefiles-under, $(LOCAL_PATH)) diff --git a/framesequence/jni/Android.mk b/framesequence/jni/Android.mk index e9d0ec5..7353436 100644 --- a/framesequence/jni/Android.mk +++ b/framesequence/jni/Android.mk @@ -19,13 +19,12 @@ include $(CLEAR_VARS) ## Main library -LOCAL_STATIC_LIBRARIES += libgif libwebp-decode +LOCAL_STATIC_LIBRARIES = libgif LOCAL_LDFLAGS := -llog -ljnigraphics LOCAL_C_INCLUDES := \ - external/giflib \ - external/webp/include + external/giflib LOCAL_MODULE := libframesequence LOCAL_SRC_FILES := \ @@ -33,11 +32,16 @@ LOCAL_SRC_FILES := \ FrameSequence.cpp \ FrameSequenceJNI.cpp \ FrameSequence_gif.cpp \ - FrameSequence_webp.cpp \ JNIHelpers.cpp \ Registry.cpp \ Stream.cpp +ifeq ($(FRAMESEQUENCE_INCLUDE_WEBP),true) + LOCAL_C_INCLUDES += external/webp/include + LOCAL_SRC_FILES += FrameSequence_webp.cpp + LOCAL_STATIC_LIBRARIES += libwebp-decode +endif + LOCAL_CFLAGS += -Wall -Wno-unused-parameter -Wno-unused-variable -Wno-overloaded-virtual LOCAL_CFLAGS += -fvisibility=hidden diff --git a/framesequence/jni/FrameSequence.h b/framesequence/jni/FrameSequence.h index 6667cdd..134c81a 100644 --- a/framesequence/jni/FrameSequence.h +++ b/framesequence/jni/FrameSequence.h @@ -49,6 +49,7 @@ public: virtual bool isOpaque() const = 0; virtual int getFrameCount() const = 0; virtual int getDefaultLoopCount() const = 0; + virtual jobject getRawByteBuffer() const = 0; virtual FrameSequenceState* createState() const = 0; }; diff --git a/framesequence/jni/FrameSequenceJNI.cpp b/framesequence/jni/FrameSequenceJNI.cpp index 08a73bc..dfc51ec 100644 --- a/framesequence/jni/FrameSequenceJNI.cpp +++ b/framesequence/jni/FrameSequenceJNI.cpp @@ -53,12 +53,27 @@ static jobject nativeDecodeByteArray(JNIEnv* env, jobject clazz, "couldn't read array bytes"); return NULL; } - MemoryStream stream(bytes + offset, length); + MemoryStream stream(bytes + offset, length, NULL); FrameSequence* frameSequence = FrameSequence::create(&stream); env->ReleasePrimitiveArrayCritical(byteArray, bytes, 0); return createJavaFrameSequence(env, frameSequence); } +static jobject nativeDecodeByteBuffer(JNIEnv* env, jobject clazz, + jobject buf, jint offset, jint limit) { + jobject globalBuf = env->NewGlobalRef(buf); + JavaVM* vm; + env->GetJavaVM(&vm); + MemoryStream stream( + (reinterpret_cast<uint8_t*>( + env->GetDirectBufferAddress(globalBuf))) + offset, + limit, + globalBuf); + FrameSequence* frameSequence = FrameSequence::create(&stream); + jobject finalSequence = createJavaFrameSequence(env, frameSequence); + return finalSequence; +} + static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject istream, jbyteArray byteArray) { JavaInputStream stream(env, istream, byteArray); @@ -69,6 +84,10 @@ static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, static void nativeDestroyFrameSequence(JNIEnv* env, jobject clazz, jlong frameSequenceLong) { FrameSequence* frameSequence = reinterpret_cast<FrameSequence*>(frameSequenceLong); + jobject buf = frameSequence->getRawByteBuffer(); + if (buf != NULL) { + env->DeleteGlobalRef(buf); + } delete frameSequence; } @@ -123,6 +142,10 @@ static JNINativeMethod gMethods[] = { "([BII)L" JNI_PACKAGE "/FrameSequence;", (void*) nativeDecodeByteArray }, + { "nativeDecodeByteBuffer", + "(Ljava/nio/ByteBuffer;II)L" JNI_PACKAGE "/FrameSequence;", + (void*) nativeDecodeByteBuffer + }, { "nativeDecodeStream", "(Ljava/io/InputStream;[B)L" JNI_PACKAGE "/FrameSequence;", (void*) nativeDecodeStream diff --git a/framesequence/jni/FrameSequence_gif.cpp b/framesequence/jni/FrameSequence_gif.cpp index f3e94df..2188c53 100644 --- a/framesequence/jni/FrameSequence_gif.cpp +++ b/framesequence/jni/FrameSequence_gif.cpp @@ -352,6 +352,10 @@ static bool isGif(void* header, int header_size) { || !memcmp(GIF89_STAMP, header, GIF_STAMP_LEN); } +static bool acceptsBuffers() { + return false; +} + static FrameSequence* createFramesequence(Stream* stream) { return new FrameSequence_gif(stream); } @@ -361,6 +365,7 @@ static RegistryEntry gEntry = { isGif, createFramesequence, NULL, + acceptsBuffers, }; static Registry gRegister(gEntry); diff --git a/framesequence/jni/FrameSequence_gif.h b/framesequence/jni/FrameSequence_gif.h index 8bf57b6..563f5b8 100644 --- a/framesequence/jni/FrameSequence_gif.h +++ b/framesequence/jni/FrameSequence_gif.h @@ -49,6 +49,10 @@ public: return mLoopCount; } + virtual jobject getRawByteBuffer() const { + return NULL; + } + virtual FrameSequenceState* createState() const; GifFileType* getGif() const { return mGif; } diff --git a/framesequence/jni/FrameSequence_webp.cpp b/framesequence/jni/FrameSequence_webp.cpp index 602feb7..c33a7e2 100644 --- a/framesequence/jni/FrameSequence_webp.cpp +++ b/framesequence/jni/FrameSequence_webp.cpp @@ -85,22 +85,28 @@ void FrameSequence_webp::constructDependencyChain() { } FrameSequence_webp::FrameSequence_webp(Stream* stream) { - // Read RIFF header to get file size. - uint8_t riff_header[RIFF_HEADER_SIZE]; - if (stream->read(riff_header, RIFF_HEADER_SIZE) != RIFF_HEADER_SIZE) { - ALOGE("WebP header load failed"); - return; - } - mData.size = CHUNK_HEADER_SIZE + GetLE32(riff_header + TAG_SIZE); - mData.bytes = new uint8_t[mData.size]; - memcpy((void*)mData.bytes, riff_header, RIFF_HEADER_SIZE); - - // Read rest of the bytes. - void* remaining_bytes = (void*)(mData.bytes + RIFF_HEADER_SIZE); - size_t remaining_size = mData.size - RIFF_HEADER_SIZE; - if (stream->read(remaining_bytes, remaining_size) != remaining_size) { - ALOGE("WebP full load failed"); - return; + if (stream->getRawBuffer() != NULL) { + mData.size = stream->getRawBufferSize(); + mData.bytes = stream->getRawBufferAddr(); + mRawByteBuffer = stream->getRawBuffer(); + } else { + // Read RIFF header to get file size. + uint8_t riff_header[RIFF_HEADER_SIZE]; + if (stream->read(riff_header, RIFF_HEADER_SIZE) != RIFF_HEADER_SIZE) { + ALOGE("WebP header load failed"); + return; + } + mData.size = CHUNK_HEADER_SIZE + GetLE32(riff_header + TAG_SIZE); + mData.bytes = new uint8_t[mData.size]; + memcpy((void*)mData.bytes, riff_header, RIFF_HEADER_SIZE); + + // Read rest of the bytes. + void* remaining_bytes = (void*)(mData.bytes + RIFF_HEADER_SIZE); + size_t remaining_size = mData.size - RIFF_HEADER_SIZE; + if (stream->read(remaining_bytes, remaining_size) != remaining_size) { + ALOGE("WebP full load failed"); + return; + } } // Construct demux. @@ -120,8 +126,10 @@ FrameSequence_webp::FrameSequence_webp(Stream* stream) { FrameSequence_webp::~FrameSequence_webp() { WebPDemuxDelete(mDemux); - delete[] mData.bytes; delete[] mIsKeyFrame; + if (mRawByteBuffer == NULL) { + delete[] mData.bytes; + } } FrameSequenceState* FrameSequence_webp::createState() const { @@ -366,6 +374,10 @@ static bool isWebP(void* header, int header_size) { !memcmp("WEBP", header_str + 8, 4); } +static bool acceptsWebPBuffer() { + return true; +} + static FrameSequence* createFramesequence(Stream* stream) { return new FrameSequence_webp(stream); } @@ -375,6 +387,7 @@ static RegistryEntry gEntry = { isWebP, createFramesequence, NULL, + acceptsWebPBuffer, }; static Registry gRegister(gEntry); diff --git a/framesequence/jni/FrameSequence_webp.h b/framesequence/jni/FrameSequence_webp.h index f4fbec0..94dcc3b 100644 --- a/framesequence/jni/FrameSequence_webp.h +++ b/framesequence/jni/FrameSequence_webp.h @@ -51,6 +51,10 @@ public: return mLoopCount; } + virtual jobject getRawByteBuffer() const { + return mRawByteBuffer; + } + virtual FrameSequenceState* createState() const; WebPDemuxer* getDemuxer() const { return mDemux; } @@ -66,6 +70,7 @@ private: uint32_t mFormatFlags; // mIsKeyFrame[i] is true if ith canvas can be constructed without decoding any prior frames. bool* mIsKeyFrame; + jobject mRawByteBuffer; }; // Produces frames of a possibly-animated WebP file for display. diff --git a/framesequence/jni/Registry.cpp b/framesequence/jni/Registry.cpp index 125ac66..e632bb2 100644 --- a/framesequence/jni/Registry.cpp +++ b/framesequence/jni/Registry.cpp @@ -34,15 +34,26 @@ Registry::Registry(const RegistryEntry& entry) { const RegistryEntry* Registry::Find(Stream* stream) { Registry* registry = gHead; - int headerSize = gHeaderBytesRequired; - char header[headerSize]; - headerSize = stream->peek(header, headerSize); - while (registry) { - if (headerSize >= registry->mImpl.requiredHeaderBytes - && registry->mImpl.checkHeader(header, headerSize)) { - return &(registry->mImpl); + + if (stream->getRawBuffer() != NULL) { + while (registry) { + if (registry->mImpl.acceptsBuffer()) { + return &(registry->mImpl); + } + registry = registry->mNext; + } + } else { + int headerSize = gHeaderBytesRequired; + char header[headerSize]; + headerSize = stream->peek(header, headerSize); + while (registry) { + if (headerSize >= registry->mImpl.requiredHeaderBytes + && registry->mImpl.checkHeader(header, headerSize)) { + return &(registry->mImpl); + } + registry = registry->mNext; } - registry = registry->mNext; } return 0; } + diff --git a/framesequence/jni/Registry.h b/framesequence/jni/Registry.h index 571c611..8db43cf 100644 --- a/framesequence/jni/Registry.h +++ b/framesequence/jni/Registry.h @@ -17,6 +17,9 @@ #ifndef RASTERMILL_REGISTRY_H #define RASTERMILL_REGISTRY_H +#include "jni.h" +#include <stdint.h> + class FrameSequence; class Decoder; class Stream; @@ -26,6 +29,7 @@ struct RegistryEntry { bool (*checkHeader)(void* header, int header_size); FrameSequence* (*createFrameSequence)(Stream* stream); Decoder* (*createDecoder)(Stream* stream); + bool (*acceptsBuffer)(); }; /** diff --git a/framesequence/jni/Stream.cpp b/framesequence/jni/Stream.cpp index b2e0c39..a576e66 100644 --- a/framesequence/jni/Stream.cpp +++ b/framesequence/jni/Stream.cpp @@ -79,6 +79,34 @@ size_t Stream::read(void* buffer, size_t size) { return bytes_read; } +uint8_t* Stream::getRawBufferAddr() { + return NULL; +} + +jobject Stream::getRawBuffer() { + return NULL; +} + +int Stream::getRawBufferSize() { + return 0; +} + +uint8_t* MemoryStream::getRawBufferAddr() { + return mBuffer; +} + +jobject MemoryStream::getRawBuffer() { + return mRawBuffer; +} + +int MemoryStream::getRawBufferSize() { + if (mRawBuffer != NULL) { + return mRemaining; + } else { + return 0; + } +} + size_t MemoryStream::doRead(void* buffer, size_t size) { size = min(size, mRemaining); memcpy(buffer, mBuffer, size); diff --git a/framesequence/jni/Stream.h b/framesequence/jni/Stream.h index f8f2427..f0f3895 100644 --- a/framesequence/jni/Stream.h +++ b/framesequence/jni/Stream.h @@ -28,6 +28,9 @@ public: size_t peek(void* buffer, size_t size); size_t read(void* buffer, size_t size); + virtual uint8_t* getRawBufferAddr(); + virtual jobject getRawBuffer(); + virtual int getRawBufferSize(); protected: virtual size_t doRead(void* buffer, size_t size) = 0; @@ -40,16 +43,21 @@ private: class MemoryStream : public Stream { public: - MemoryStream(void* buffer, size_t size) : - mBuffer((char*)buffer), - mRemaining(size) {} + MemoryStream(void* buffer, size_t size, jobject buf) : + mBuffer((uint8_t*)buffer), + mRemaining(size), + mRawBuffer(buf) {} + virtual uint8_t* getRawBufferAddr(); + virtual jobject getRawBuffer(); + virtual int getRawBufferSize(); protected: virtual size_t doRead(void* buffer, size_t size); private: - char* mBuffer; + uint8_t* mBuffer; size_t mRemaining; + jobject mRawBuffer; }; class FileStream : public Stream { diff --git a/framesequence/samples/RastermillSamples/Android.mk b/framesequence/samples/FrameSequenceSamples/Android.mk index bb8920f..fff4a15 100644 --- a/framesequence/samples/RastermillSamples/Android.mk +++ b/framesequence/samples/FrameSequenceSamples/Android.mk @@ -35,6 +35,6 @@ LOCAL_SDK_VERSION := 19 LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, res) LOCAL_AAPT_FLAGS := --auto-add-overlay -LOCAL_AAPT_FLAGS += --extra-packages com.android.rastermill.samples +LOCAL_AAPT_FLAGS += --extra-packages com.android.framesequence.samples include $(BUILD_PACKAGE) diff --git a/framesequence/samples/RastermillSamples/AndroidManifest.xml b/framesequence/samples/FrameSequenceSamples/AndroidManifest.xml index b554021..d614631 100644 --- a/framesequence/samples/RastermillSamples/AndroidManifest.xml +++ b/framesequence/samples/FrameSequenceSamples/AndroidManifest.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.rastermill.samples" + package="com.android.framesequence.samples" android:versionCode="1" android:versionName="1.0" > @@ -20,7 +20,7 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> - <activity android:name=".AnimatedGifTest" /> + <activity android:name=".FrameSequenceTest" /> </application> </manifest> diff --git a/framesequence/samples/RastermillSamples/build.xml b/framesequence/samples/FrameSequenceSamples/build.xml index 5e55b4e..5e55b4e 100644 --- a/framesequence/samples/RastermillSamples/build.xml +++ b/framesequence/samples/FrameSequenceSamples/build.xml diff --git a/framesequence/samples/RastermillSamples/proguard.flags b/framesequence/samples/FrameSequenceSamples/proguard.flags index 4acde2d..4acde2d 100644 --- a/framesequence/samples/RastermillSamples/proguard.flags +++ b/framesequence/samples/FrameSequenceSamples/proguard.flags diff --git a/framesequence/samples/RastermillSamples/project.properties b/framesequence/samples/FrameSequenceSamples/project.properties index ce39f2d..ce39f2d 100644 --- a/framesequence/samples/RastermillSamples/project.properties +++ b/framesequence/samples/FrameSequenceSamples/project.properties diff --git a/framesequence/samples/RastermillSamples/res/drawable-hdpi/ic_launcher.png b/framesequence/samples/FrameSequenceSamples/res/drawable-hdpi/ic_launcher.png Binary files differindex 96a442e..96a442e 100644 --- a/framesequence/samples/RastermillSamples/res/drawable-hdpi/ic_launcher.png +++ b/framesequence/samples/FrameSequenceSamples/res/drawable-hdpi/ic_launcher.png diff --git a/framesequence/samples/RastermillSamples/res/drawable-mdpi/ic_launcher.png b/framesequence/samples/FrameSequenceSamples/res/drawable-mdpi/ic_launcher.png Binary files differindex 359047d..359047d 100644 --- a/framesequence/samples/RastermillSamples/res/drawable-mdpi/ic_launcher.png +++ b/framesequence/samples/FrameSequenceSamples/res/drawable-mdpi/ic_launcher.png diff --git a/framesequence/samples/RastermillSamples/res/drawable-xhdpi/ic_launcher.png b/framesequence/samples/FrameSequenceSamples/res/drawable-xhdpi/ic_launcher.png Binary files differindex 71c6d76..71c6d76 100644 --- a/framesequence/samples/RastermillSamples/res/drawable-xhdpi/ic_launcher.png +++ b/framesequence/samples/FrameSequenceSamples/res/drawable-xhdpi/ic_launcher.png diff --git a/framesequence/samples/RastermillSamples/res/layout/basic_test_activity.xml b/framesequence/samples/FrameSequenceSamples/res/layout/basic_test_activity.xml index 0b9a2df..0b9a2df 100644 --- a/framesequence/samples/RastermillSamples/res/layout/basic_test_activity.xml +++ b/framesequence/samples/FrameSequenceSamples/res/layout/basic_test_activity.xml diff --git a/framesequence/samples/RastermillSamples/res/raw/animated.gif b/framesequence/samples/FrameSequenceSamples/res/raw/animated_gif.gif Binary files differindex 51baf15..51baf15 100644 --- a/framesequence/samples/RastermillSamples/res/raw/animated.gif +++ b/framesequence/samples/FrameSequenceSamples/res/raw/animated_gif.gif diff --git a/framesequence/samples/FrameSequenceSamples/res/raw/animated_webp.webp b/framesequence/samples/FrameSequenceSamples/res/raw/animated_webp.webp Binary files differnew file mode 100644 index 0000000..25c6a4d --- /dev/null +++ b/framesequence/samples/FrameSequenceSamples/res/raw/animated_webp.webp diff --git a/framesequence/samples/RastermillSamples/res/values/strings.xml b/framesequence/samples/FrameSequenceSamples/res/values/strings.xml index 811c979..dc0962e 100644 --- a/framesequence/samples/RastermillSamples/res/values/strings.xml +++ b/framesequence/samples/FrameSequenceSamples/res/values/strings.xml @@ -3,7 +3,7 @@ <!-- NOTE: all strings should be marked as translatable=false, since this sample app is for testing, and won't be shipped --> - <string name="app_name" translatable="false">Rastermill Samples</string> + <string name="app_name" translatable="false">FrameSequence Samples</string> <string name="action_settings" translatable="false">Settings</string> <string name="start" translatable="false">start</string> diff --git a/framesequence/samples/RastermillSamples/res/values/styles.xml b/framesequence/samples/FrameSequenceSamples/res/values/styles.xml index 737bdc3..737bdc3 100644 --- a/framesequence/samples/RastermillSamples/res/values/styles.xml +++ b/framesequence/samples/FrameSequenceSamples/res/values/styles.xml diff --git a/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java b/framesequence/samples/FrameSequenceSamples/src/com/android/framesequence/samples/FrameSequenceTest.java index 2328975..5587dc6 100644 --- a/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java +++ b/framesequence/samples/FrameSequenceSamples/src/com/android/framesequence/samples/FrameSequenceTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.rastermill.samples; +package com.android.framesequence.samples; import android.app.Activity; import android.graphics.Bitmap; @@ -27,8 +27,9 @@ import android.widget.Toast; import java.io.InputStream; import java.util.HashSet; -public class AnimatedGifTest extends Activity { +public class FrameSequenceTest extends Activity { FrameSequenceDrawable mDrawable; + int mResourceId; // This provider is entirely unnecessary, just here to validate the acquire/release process private class CheckingProvider implements FrameSequenceDrawable.BitmapProvider { @@ -59,6 +60,8 @@ public class AnimatedGifTest extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mResourceId = getIntent().getIntExtra("resourceId", R.raw.animated_gif); + setContentView(R.layout.basic_test_activity); findViewById(R.id.start).setOnClickListener(new View.OnClickListener() { @Override @@ -91,7 +94,7 @@ public class AnimatedGifTest extends Activity { super.onResume(); ImageView imageView = (ImageView) findViewById(R.id.imageview); - InputStream is = getResources().openRawResource(R.raw.animated); + InputStream is = getResources().openRawResource(mResourceId); FrameSequence fs = FrameSequence.decodeStream(is); mDrawable = new FrameSequenceDrawable(fs, mProvider); @@ -99,7 +102,7 @@ public class AnimatedGifTest extends Activity { @Override public void onFinished(FrameSequenceDrawable drawable) { Toast.makeText(getApplicationContext(), - "THE ANIMATION HAS FINISHED", Toast.LENGTH_SHORT).show(); + "The animation has finished", Toast.LENGTH_SHORT).show(); } }); imageView.setImageDrawable(mDrawable); diff --git a/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/SamplesList.java b/framesequence/samples/FrameSequenceSamples/src/com/android/framesequence/samples/SamplesList.java index 0447537..c67b83c 100644 --- a/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/SamplesList.java +++ b/framesequence/samples/FrameSequenceSamples/src/com/android/framesequence/samples/SamplesList.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.rastermill.samples; +package com.android.framesequence.samples; import android.app.ListActivity; import android.content.Intent; @@ -31,17 +31,20 @@ public class SamplesList extends ListActivity { static final String KEY_NAME = "name"; static final String KEY_CLASS = "clazz"; + static final String KEY_RESOURCE = "res"; - static Map<String,?> makeSample(String name, Class<?> activity) { + static Map<String,?> makeSample(String name, Class<?> activity, int resourceId) { Map<String,Object> ret = new HashMap<String,Object>(); ret.put(KEY_NAME, name); ret.put(KEY_CLASS, activity); + ret.put(KEY_RESOURCE, resourceId); return ret; } @SuppressWarnings("serial") static final ArrayList<Map<String,?>> SAMPLES = new ArrayList<Map<String,?>>() {{ - add(makeSample("Animation Test", AnimatedGifTest.class)); + add(makeSample("GIF animation", FrameSequenceTest.class, R.raw.animated_gif)); + add(makeSample("WEBP animation", FrameSequenceTest.class, R.raw.animated_webp)); }}; @Override @@ -55,7 +58,11 @@ public class SamplesList extends ListActivity { @Override protected void onListItemClick(ListView l, View v, int position, long id) { Class<?> clazz = (Class<?>) SAMPLES.get(position).get(KEY_CLASS); - startActivity(new Intent(this, clazz)); + int resourceId = ((Integer) SAMPLES.get(position).get(KEY_RESOURCE)).intValue(); + + Intent intent = new Intent(this, clazz); + intent.putExtra("resourceId", resourceId); + startActivity(intent); } } diff --git a/framesequence/src/android/support/rastermill/FrameSequence.java b/framesequence/src/android/support/rastermill/FrameSequence.java index d2bd128..8ff241f 100644 --- a/framesequence/src/android/support/rastermill/FrameSequence.java +++ b/framesequence/src/android/support/rastermill/FrameSequence.java @@ -17,6 +17,7 @@ package android.support.rastermill; import android.graphics.Bitmap; +import java.nio.ByteBuffer; import java.io.InputStream; @@ -40,6 +41,7 @@ public class FrameSequence { private static native FrameSequence nativeDecodeByteArray(byte[] data, int offset, int length); private static native FrameSequence nativeDecodeStream(InputStream is, byte[] tempStorage); + private static native FrameSequence nativeDecodeByteBuffer(ByteBuffer buffer, int offset, int capacity); private static native void nativeDestroyFrameSequence(long nativeFrameSequence); private static native long nativeCreateState(long nativeFrameSequence); private static native void nativeDestroyState(long nativeState); @@ -69,6 +71,19 @@ public class FrameSequence { return nativeDecodeByteArray(data, offset, length); } + public static FrameSequence decodeByteBuffer(ByteBuffer buffer) { + if (buffer == null) throw new IllegalArgumentException(); + if (!buffer.isDirect()) { + if (buffer.hasArray()) { + byte[] byteArray = buffer.array(); + return decodeByteArray(byteArray, buffer.position(), buffer.remaining()); + } else { + throw new IllegalArgumentException("Cannot have non-direct ByteBuffer with no byte array"); + } + } + return nativeDecodeByteBuffer(buffer, buffer.position(), buffer.remaining()); + } + public static FrameSequence decodeStream(InputStream stream) { if (stream == null) throw new IllegalArgumentException(); byte[] tempStorage = new byte[16 * 1024]; // TODO: use buffer pool |