diff options
Diffstat (limited to 'camera2')
24 files changed, 1084 insertions, 528 deletions
diff --git a/camera2/Android.mk b/camera2/Android.mk index 9ac4a8a..3719578 100644 --- a/camera2/Android.mk +++ b/camera2/Android.mk @@ -12,4 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. +# Build all subprojects include $(call all-subdir-makefiles) 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 913a575..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,8 +156,23 @@ 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.CaptureListener + extends CameraCaptureSession.CaptureCallback implements ImageReader.OnImageAvailableListener {}; private class Camera2Handler extends HistoryHandler { @@ -168,6 +180,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { private CameraOpenCallback mOpenCallback; private int mCameraIndex; private String mCameraId; + private int mCancelAfPending = 0; // Available in CAMERA_UNCONFIGURED state and above: private CameraDevice mCamera; @@ -208,8 +221,10 @@ class AndroidCamera2AgentImpl extends CameraAgent { @Override 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; @@ -231,7 +246,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { mOpenCallback.onCameraDisabled(msg.arg1); break; } - mCameraManager.openCamera(mCameraId, mCameraDeviceStateListener, this); + mCameraManager.openCamera(mCameraId, mCameraDeviceStateCallback, this); break; } @@ -297,7 +312,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { mSession.setRepeatingRequest( mPersistentSettings.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW, mPreviewSurface), - /*listener*/mCameraResultStateListener, /*handler*/this); + /*listener*/mCameraResultStateCallback, /*handler*/this); } catch(CameraAccessException ex) { Log.w(TAG, "Unable to start preview", ex); changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); @@ -360,6 +375,11 @@ class AndroidCamera2AgentImpl extends CameraAgent { } case CameraActions.AUTO_FOCUS: { + if (mCancelAfPending > 0) { + Log.v(TAG, "handleMessage - Ignored AUTO_FOCUS because there was " + + mCancelAfPending + " pending CANCEL_AUTO_FOCUS messages"); + break; // ignore AF because a CANCEL_AF is queued after this + } // We only support locking the focus while a preview is being displayed. // However, it can be requested multiple times in succession; the effect of // the subsequent invocations is determined by the focus mode defined in the @@ -379,8 +399,8 @@ class AndroidCamera2AgentImpl extends CameraAgent { // However, it will probably take longer than that, so once that happens, // just start checking the repeating preview requests as they complete. final CameraAFCallback callback = (CameraAFCallback) msg.obj; - CameraCaptureSession.CaptureListener deferredCallbackSetter = - new CameraCaptureSession.CaptureListener() { + CameraCaptureSession.CaptureCallback deferredCallbackSetter = + new CameraCaptureSession.CaptureCallback() { private boolean mAlreadyDispatched = false; @Override @@ -400,13 +420,13 @@ class AndroidCamera2AgentImpl extends CameraAgent { private void checkAfState(CaptureResult result) { if (result.get(CaptureResult.CONTROL_AF_STATE) != null && !mAlreadyDispatched) { - // Now our mCameraResultStateListener will invoke the callback + // Now our mCameraResultStateCallback will invoke the callback // the first time it finds the focus motor to be locked. mAlreadyDispatched = true; mOneshotAfCallback = callback; // This is an optimization: check the AF state of this frame // instead of simply waiting for the next. - mCameraResultStateListener.monitorControlStates(result); + mCameraResultStateCallback.monitorControlStates(result); } } @@ -437,6 +457,9 @@ class AndroidCamera2AgentImpl extends CameraAgent { } case CameraActions.CANCEL_AUTO_FOCUS: { + // Ignore all AFs that were already queued until we see + // a CANCEL_AUTO_FOCUS_FINISH + mCancelAfPending++; // Why would you want to unlock the lens if it isn't already locked? if (mCameraState.getState() < AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) { @@ -462,6 +485,13 @@ class AndroidCamera2AgentImpl extends CameraAgent { break; } + case CameraActions.CANCEL_AUTO_FOCUS_FINISH: { + // Stop ignoring AUTO_FOCUS messages unless there are additional + // CANCEL_AUTO_FOCUSes that were added + mCancelAfPending--; + break; + } + case CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK: { mPassiveAfCallback = (CameraAFMoveCallback) msg.obj; break; @@ -493,12 +523,18 @@ class AndroidCamera2AgentImpl extends CameraAgent { case CameraActions.SET_DISPLAY_ORIENTATION: { // Only set the JPEG capture orientation if requested to do so; otherwise, - // capture in the sensor's physical orientation + // capture in the sensor's physical orientation. (e.g., JPEG rotation is + // necessary in auto-rotate mode. mPersistentSettings.set(CaptureRequest.JPEG_ORIENTATION, msg.arg2 > 0 ? mCameraProxy.getCharacteristics().getJpegOrientation(msg.arg1) : 0); break; } + case CameraActions.SET_JPEG_ORIENTATION: { + mPersistentSettings.set(CaptureRequest.JPEG_ORIENTATION, msg.arg1); + break; + } + case CameraActions.CAPTURE_PHOTO: { if (mCameraState.getState() < AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) { @@ -542,8 +578,8 @@ class AndroidCamera2AgentImpl extends CameraAgent { // trigger capture has made it into the pipeline, we'll start checking // for the completion of that convergence, capturing when that happens. Log.i(TAG, "Forcing pre-capture autoexposure convergence"); - CameraCaptureSession.CaptureListener deferredCallbackSetter = - new CameraCaptureSession.CaptureListener() { + CameraCaptureSession.CaptureCallback deferredCallbackSetter = + new CameraCaptureSession.CaptureCallback() { private boolean mAlreadyDispatched = false; @Override @@ -563,13 +599,13 @@ class AndroidCamera2AgentImpl extends CameraAgent { private void checkAeState(CaptureResult result) { if (result.get(CaptureResult.CONTROL_AE_STATE) != null && !mAlreadyDispatched) { - // Now our mCameraResultStateListener will invoke the + // Now our mCameraResultStateCallback will invoke the // callback once the autoexposure routine has converged. mAlreadyDispatched = true; mOneshotCaptureCallback = listener; // This is an optimization: check the AE state of this frame // instead of simply waiting for the next. - mCameraResultStateListener.monitorControlStates(result); + mCameraResultStateCallback.monitorControlStates(result); } } @@ -605,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)); @@ -622,12 +658,12 @@ 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); } } @@ -663,7 +699,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { mSession.setRepeatingRequest( mPersistentSettings.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW, mPreviewSurface), - /*listener*/mCameraResultStateListener, /*handler*/this); + /*listener*/mCameraResultStateCallback, /*handler*/this); } catch (CameraAccessException ex) { Log.e(TAG, "Failed to apply updated request settings", ex); } @@ -710,7 +746,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { try { mCamera.createCaptureSession( Arrays.asList(mPreviewSurface, mCaptureReader.getSurface()), - mCameraPreviewStateListener, this); + mCameraPreviewStateCallback, this); } catch (CameraAccessException ex) { Log.e(TAG, "Failed to create camera capture session", ex); } @@ -731,14 +767,14 @@ class AndroidCamera2AgentImpl extends CameraAgent { mCameraState.setState(newState); if (newState < AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) { mCurrentAeState = CaptureResult.CONTROL_AE_STATE_INACTIVE; - mCameraResultStateListener.resetState(); + mCameraResultStateCallback.resetState(); } } } - // This listener monitors our connection to and disconnection from camera devices. - private CameraDevice.StateListener mCameraDeviceStateListener = - new CameraDevice.StateListener() { + // This callback monitors our connection to and disconnection from camera devices. + private CameraDevice.StateCallback mCameraDeviceStateCallback = + new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCamera = camera; @@ -746,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); @@ -778,9 +816,9 @@ class AndroidCamera2AgentImpl extends CameraAgent { } }}; - // This listener monitors our camera session (i.e. our transition into and out of preview). - private CameraCaptureSession.StateListener mCameraPreviewStateListener = - new CameraCaptureSession.StateListener() { + // This callback monitors our camera session (i.e. our transition into and out of preview). + private CameraCaptureSession.StateCallback mCameraPreviewStateCallback = + new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { mSession = session; @@ -802,16 +840,16 @@ class AndroidCamera2AgentImpl extends CameraAgent { } }}; - private abstract class CameraResultStateListener - extends CameraCaptureSession.CaptureListener { + private abstract class CameraResultStateCallback + extends CameraCaptureSession.CaptureCallback { public abstract void monitorControlStates(CaptureResult result); public abstract void resetState(); } - // This listener monitors requested captures and notifies any relevant callbacks. - private CameraResultStateListener mCameraResultStateListener = - new CameraResultStateListener() { + // This callback monitors requested captures and notifies any relevant callbacks. + private CameraResultStateCallback mCameraResultStateCallback = + new CameraResultStateCallback() { private int mLastAfState = -1; private long mLastAfFrameNumber = -1; private long mLastAeFrameNumber = -1; @@ -905,7 +943,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { mPersistentSettings.createRequest(mCamera, CameraDevice.TEMPLATE_STILL_CAPTURE, mCaptureReader.getSurface()), - /*listener*/mOneshotCaptureCallback, + /*callback*/mOneshotCaptureCallback, /*handler*/Camera2Handler.this); } catch (CameraAccessException ex) { Log.e(TAG, "Unable to initiate capture", ex); @@ -935,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; @@ -942,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; @@ -972,6 +1015,10 @@ class AndroidCamera2AgentImpl extends CameraAgent { return mCapabilities; } + public CameraAgent getAgent() { + return mCameraAgent; + } + private AndroidCamera2Capabilities getSpecializedCapabilities() { return mCapabilities; } @@ -1014,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 @@ -1074,7 +1135,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { new CaptureAvailableListener() { @Override public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, - long timestamp) { + long timestamp, long frameNumber) { if (shutter != null) { handler.post(new Runnable() { @Override @@ -1102,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 @@ -1132,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 @@ -1355,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/AndroidCamera2Capabilities.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java index bd610cc..8001a37 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java @@ -204,7 +204,6 @@ public class AndroidCamera2Capabilities extends CameraCapabilities { return SceneMode.CANDLELIGHT; case CONTROL_SCENE_MODE_FIREWORKS: return SceneMode.FIREWORKS; - // TODO: We cannot support HDR case CONTROL_SCENE_MODE_LANDSCAPE: return SceneMode.LANDSCAPE; case CONTROL_SCENE_MODE_NIGHT: @@ -226,6 +225,11 @@ public class AndroidCamera2Capabilities extends CameraCapabilities { return SceneMode.THEATRE; // TODO: We cannot expose FACE_PRIORITY, or HIGH_SPEED_VIDEO } + + if (sm == LegacyVendorTags.CONTROL_SCENE_MODE_HDR) { + return SceneMode.HDR; + } + Log.w(TAG, "Unable to convert from API 2 scene mode: " + sm); return null; } 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 0d3ef26..0062097 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java @@ -206,7 +206,7 @@ public class AndroidCamera2Settings extends CameraSettings { } else if (setting == CONTROL_AF_REGIONS) { return mFocusAreas.size() == 0; } else if (setting == CONTROL_AE_TARGET_FPS_RANGE) { - Range defaultFpsRange = mTemplateSettings.get(CONTROL_AE_TARGET_FPS_RANGE); + Range<Integer> defaultFpsRange = mTemplateSettings.get(CONTROL_AE_TARGET_FPS_RANGE); return (mPreviewFpsRangeMin == 0 && mPreviewFpsRangeMax == 0) || (defaultFpsRange != null && mPreviewFpsRangeMin == defaultFpsRange.getLower() && mPreviewFpsRangeMax == defaultFpsRange.getUpper()); @@ -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; } @@ -419,7 +428,10 @@ public class AndroidCamera2Settings extends CameraSettings { mode = CONTROL_SCENE_MODE_FIREWORKS; break; } - // TODO: We cannot support HDR + case HDR: { + mode = LegacyVendorTags.CONTROL_SCENE_MODE_HDR; + break; + } case LANDSCAPE: { mode = CONTROL_SCENE_MODE_LANDSCAPE; break; 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 358d5f6..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; @@ -225,6 +245,10 @@ class AndroidCameraAgentImpl extends CameraAgent { public synchronized Parameters getBlocking() { if (mParameters == null) { mParameters = mCamera.getParameters(); + if (mParameters == null) { + Log.e(TAG, "Camera object returned null parameters!"); + throw new IllegalStateException("camera.getParameters returned null"); + } } return mParameters; } @@ -233,11 +257,12 @@ 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; private class CaptureCallbacks { public final ShutterCallback mShutter; @@ -254,8 +279,9 @@ class AndroidCameraAgentImpl extends CameraAgent { } } - CameraHandler(Looper looper) { + CameraHandler(CameraAgent agent, Looper looper) { super(looper); + mAgent = agent; } private void startFaceDetection() { @@ -293,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, @@ -312,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. @@ -319,8 +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; @@ -340,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) { @@ -359,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."); } @@ -373,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; } @@ -382,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; } @@ -447,17 +486,32 @@ class AndroidCameraAgentImpl extends CameraAgent { } case CameraActions.AUTO_FOCUS: { + if (mCancelAfPending > 0) { + Log.v(TAG, "handleMessage - Ignored AUTO_FOCUS because there was " + + mCancelAfPending + " pending CANCEL_AUTO_FOCUS messages"); + break; // ignore AF because a CANCEL_AF is queued after this + } mCameraState.setState(AndroidCameraStateHolder.CAMERA_FOCUSING); mCamera.autoFocus((AutoFocusCallback) msg.obj); break; } case CameraActions.CANCEL_AUTO_FOCUS: { + // Ignore all AFs that were already queued until we see + // a CANCEL_AUTO_FOCUS_FINISH + mCancelAfPending++; mCamera.cancelAutoFocus(); mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE); break; } + case CameraActions.CANCEL_AUTO_FOCUS_FINISH: { + // Stop ignoring AUTO_FOCUS messages unless there are additional + // CANCEL_AUTO_FOCUSes that were added + mCancelAfPending--; + break; + } + case CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK: { setAutoFocusMoveCallback(mCamera, msg.obj); break; @@ -468,11 +522,21 @@ class AndroidCameraAgentImpl extends CameraAgent { mCamera.setDisplayOrientation( mCharacteristics.getPreviewOrientation(msg.arg1)); // Only set the JPEG capture orientation if requested to do so; otherwise, - // capture in the sensor's physical orientation + // capture in the sensor's physical orientation. (e.g., JPEG rotation is + // necessary in auto-rotate mode. Parameters parameters = mParameterCache.getBlocking(); parameters.setRotation( msg.arg2 > 0 ? mCharacteristics.getJpegOrientation(msg.arg1) : 0); mCamera.setParameters(parameters); + mParameterCache.invalidate(); + break; + } + + case CameraActions.SET_JPEG_ORIENTATION: { + Parameters parameters = mParameterCache.getBlocking(); + parameters.setRotation(msg.arg1); + mCamera.setParameters(parameters); + mParameterCache.invalidate(); break; } @@ -496,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; @@ -542,45 +601,53 @@ 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) { - 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); } } @@ -636,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(); @@ -686,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; @@ -704,6 +778,9 @@ class AndroidCameraAgentImpl extends CameraAgent { @Deprecated @Override public android.hardware.Camera getCamera() { + if (getCameraState().isInvalid()) { + return null; + } return mCamera; } @@ -723,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() { @@ -785,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(); @@ -796,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 @@ -829,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 @@ -892,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 @@ -908,15 +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() { - Message getParametersMessage = mCameraHandler.obtainMessage( - CameraActions.GET_PARAMETERS, parametersHolder); - mCameraHandler.sendMessage(getParametersMessage); - 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]; } @@ -1026,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/AndroidCameraSettings.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java index ee69b54..4558fe3 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java @@ -17,15 +17,23 @@ package com.android.ex.camera2.portability; import android.hardware.Camera; +import com.android.ex.camera2.portability.debug.Log; /** * The subclass of {@link CameraSettings} for Android Camera 1 API. */ public class AndroidCameraSettings extends CameraSettings { + private static final Log.Tag TAG = new Log.Tag("AndCamSet"); + private static final String TRUE = "true"; private static final String RECORDING_HINT = "recording-hint"; public AndroidCameraSettings(CameraCapabilities capabilities, Camera.Parameters params) { + if (params == null) { + Log.w(TAG, "Settings ctor requires a non-null Camera.Parameters."); + return; + } + CameraCapabilities.Stringifier stringifier = capabilities.getStringifier(); setSizesLocked(false); 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 aae122b..63b1fec 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraActions.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraActions.java @@ -42,14 +42,82 @@ class CameraActions { public static final int CANCEL_AUTO_FOCUS = 302; public static final int SET_AUTO_FOCUS_MOVE_CALLBACK = 303; public static final int SET_ZOOM_CHANGE_LISTENER = 304; + public static final int CANCEL_AUTO_FOCUS_FINISH = 305; // Face detection 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; + public static final int SET_JPEG_ORIENTATION = 503; // Capture public static final int CAPTURE_PHOTO = 601; + + public static String stringify(int action) { + switch (action) { + case OPEN_CAMERA: + return "OPEN_CAMERA"; + case RELEASE: + return "RELEASE"; + case RECONNECT: + return "RECONNECT"; + case UNLOCK: + return "UNLOCK"; + case LOCK: + return "LOCK"; + case SET_PREVIEW_TEXTURE_ASYNC: + return "SET_PREVIEW_TEXTURE_ASYNC"; + case START_PREVIEW_ASYNC: + return "START_PREVIEW_ASYNC"; + case STOP_PREVIEW: + return "STOP_PREVIEW"; + case SET_PREVIEW_CALLBACK_WITH_BUFFER: + return "SET_PREVIEW_CALLBACK_WITH_BUFFER"; + case ADD_CALLBACK_BUFFER: + return "ADD_CALLBACK_BUFFER"; + case SET_PREVIEW_DISPLAY_ASYNC: + return "SET_PREVIEW_DISPLAY_ASYNC"; + case SET_PREVIEW_CALLBACK: + return "SET_PREVIEW_CALLBACK"; + case SET_ONE_SHOT_PREVIEW_CALLBACK: + return "SET_ONE_SHOT_PREVIEW_CALLBACK"; + case SET_PARAMETERS: + return "SET_PARAMETERS"; + case GET_PARAMETERS: + return "GET_PARAMETERS"; + case REFRESH_PARAMETERS: + return "REFRESH_PARAMETERS"; + case APPLY_SETTINGS: + return "APPLY_SETTINGS"; + case AUTO_FOCUS: + return "AUTO_FOCUS"; + case CANCEL_AUTO_FOCUS: + return "CANCEL_AUTO_FOCUS"; + case SET_AUTO_FOCUS_MOVE_CALLBACK: + return "SET_AUTO_FOCUS_MOVE_CALLBACK"; + case SET_ZOOM_CHANGE_LISTENER: + return "SET_ZOOM_CHANGE_LISTENER"; + case CANCEL_AUTO_FOCUS_FINISH: + return "CANCEL_AUTO_FOCUS_FINISH"; + case SET_FACE_DETECTION_LISTENER: + return "SET_FACE_DETECTION_LISTENER"; + case START_FACE_DETECTION: + return "START_FACE_DETECTION"; + case STOP_FACE_DETECTION: + return "STOP_FACE_DETECTION"; + case ENABLE_SHUTTER_SOUND: + return "ENABLE_SHUTTER_SOUND"; + case SET_DISPLAY_ORIENTATION: + return "SET_DISPLAY_ORIENTATION"; + case CAPTURE_PHOTO: + return "CAPTURE_PHOTO"; + default: + return "UNKNOWN(" + action + ")"; + } + } + + private CameraActions() { + throw new AssertionError(); + } } 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 b624b47..66762fd 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java @@ -23,6 +23,7 @@ import android.hardware.Camera.OnZoomChangeListener; import android.os.Build; import android.os.Handler; import android.os.Looper; +import android.os.Message; import android.view.SurfaceHolder; import com.android.ex.camera2.portability.debug.Log; @@ -42,7 +43,7 @@ import com.android.ex.camera2.portability.debug.Log; * {@code android.hardware.Camera.OnZoomChangeListener}, and */ public abstract class CameraAgent { - public static final long CAMERA_OPERATION_TIMEOUT_MS = 2500; + public static final long CAMERA_OPERATION_TIMEOUT_MS = 3500; private static final Log.Tag TAG = new Log.Tag("CamAgnt"); @@ -154,14 +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} */ @@ -292,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); + } } /** @@ -307,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); } } @@ -330,8 +336,7 @@ public abstract class CameraAgent { * Sets a callback for handling camera api runtime exceptions on * a handler. */ - public abstract void setCameraDefaultExceptionCallback(CameraExceptionCallback callback, - Handler handler); + public abstract void setCameraExceptionHandler(CameraExceptionHandler exceptionHandler); /** * Recycles the resources used by this instance. CameraAgent will be in @@ -355,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 @@ -387,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 @@ -398,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); + } } /** @@ -412,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); + } } /** @@ -426,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); + } } /** @@ -455,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); + } } /** @@ -479,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); + } } /** @@ -496,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); + } } /** @@ -522,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() { @@ -529,6 +587,9 @@ public abstract class CameraAgent { CameraStartPreviewCallbackForward.getNewInstance(h, cb)) .sendToTarget(); }}); + } catch (final RuntimeException ex) { + getAgent().getCameraExceptionHandler().onDispatchThreadException(ex); + } } /** @@ -537,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().sendEmptyMessage(CameraActions.STOP_PREVIEW); - getCameraHandler().post(bundle.mUnlockRunnable); - }}, 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); + } } /** @@ -582,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); + } } /** @@ -602,14 +675,18 @@ public abstract class CameraAgent { /** * Cancels the auto-focus process. + * + * <p>This action has the highest priority and will get processed before anything + * else that is pending. Moreover, any pending auto-focuses that haven't yet + * began will also be ignored.</p> */ public void cancelAutoFocus() { - getDispatchThread().runJob(new Runnable() { - @Override - public void run() { - getCameraHandler().removeMessages(CameraActions.AUTO_FOCUS); - getCameraHandler().sendEmptyMessage(CameraActions.CANCEL_AUTO_FOCUS); - }}); + // Do not use the dispatch thread since we want to avoid a wait-cycle + // between applySettingsHelper which waits until the state is not FOCUSING. + // cancelAutoFocus should get executed asap, set the state back to idle. + getCameraHandler().sendMessageAtFrontOfQueue( + getCameraHandler().obtainMessage(CameraActions.CANCEL_AUTO_FOCUS)); + getCameraHandler().sendEmptyMessage(CameraActions.CANCEL_AUTO_FOCUS_FINISH); } /** @@ -662,14 +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) { + 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); + } } /** @@ -692,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. @@ -765,19 +859,31 @@ 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; } /** * Applies the settings to the camera device. * + * <p>If the camera is either focusing or capturing; settings applications + * will be (asynchronously) deferred until those operations complete.</p> + * * @param settings The settings to use on the device. * @return Whether the settings can be applied. */ @@ -788,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); + } } /** @@ -802,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); + } } /** @@ -851,5 +965,20 @@ public abstract class CameraAgent { } }}; } + + /** + * Notify all synchronous waiters waiting on message completion with {@link #mWaitLock}. + * + * <p>This assumes that the message was sent with {@code this} as the {@code Message#obj}. + * Otherwise the message is ignored.</p> + */ + /*package*/ static void unblockSyncWaiters(Message msg) { + if (msg == null) return; + + if (msg.obj instanceof WaitDoneBundle) { + WaitDoneBundle bundle = (WaitDoneBundle)msg.obj; + bundle.mUnlockRunnable.run(); + } + } } } diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java index 0d7c302..31c47d3 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.EnumSet; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.TreeSet; @@ -184,7 +185,7 @@ public class CameraCapabilities { * Capture a scene using high dynamic range imaging techniques. * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_HDR}. */ - // TODO: Unsupported on API 2 + // Note: Supported as a vendor tag on the Camera2 API for some LEGACY devices. HDR, /** * Take pictures on distant objects. @@ -323,17 +324,17 @@ public class CameraCapabilities { * @return The converted string. */ private static String toApiCase(String enumCase) { - return enumCase.toLowerCase().replaceAll("_", "-"); + return enumCase.toLowerCase(Locale.US).replaceAll("_", "-"); } /** - * Conerts the string to underscore-delimited uppercase to match the enum constant names. + * Converts the string to underscore-delimited uppercase to match the enum constant names. * * @param apiCase An API-related string representation. * @return The converted string. */ private static String toEnumCase(String apiCase) { - return apiCase.toUpperCase().replaceAll("-", "_"); + return apiCase.toUpperCase(Locale.US).replaceAll("-", "_"); } /** 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 35ae51c..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,20 +24,56 @@ 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)); + } mState = state; 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. @@ -83,6 +119,7 @@ public abstract class CameraStateHolder { * reached. */ public boolean waitForStates(final int states) { + Log.v(TAG, "waitForStates - states = " + Integer.toBinaryString(states)); return waitForCondition(new ConditionChecker() { @Override public boolean success() { @@ -100,6 +137,7 @@ public abstract class CameraStateHolder { * reached. */ public boolean waitToAvoidStates(final int states) { + Log.v(TAG, "waitToAvoidStates - states = " + Integer.toBinaryString(states)); return waitForCondition(new ConditionChecker() { @Override public boolean success() { 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/camera2/portability/src/com/android/ex/camera2/portability/LegacyVendorTags.java b/camera2/portability/src/com/android/ex/camera2/portability/LegacyVendorTags.java new file mode 100644 index 0000000..7eb5c33 --- /dev/null +++ b/camera2/portability/src/com/android/ex/camera2/portability/LegacyVendorTags.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2014 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.hardware.camera2.CameraCharacteristics; +import android.util.Log; + +import java.lang.ExceptionInInitializerError; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * Vendor tag declarations for the Legacy Camera2 API implementation. + */ +public class LegacyVendorTags { + + private static final String TAG = "LegacyVendorTags"; + + /** + * Hidden enum for scene modes supported only by the Camera1 API. + */ + public static final int CONTROL_SCENE_MODE_HDR; + + static { + int tempSceneMode = -1; + try { + tempSceneMode = + Class.forName("android.hardware.camera2.CameraCharacteristics"). + getField("CONTROL_SCENE_MODE_HDR").getInt(null); + } catch (Exception e) { + Log.e(TAG, "Error while reflecting on SCENE_MODE_HDR enum, HDR will not be available: " + + e); + } finally { + CONTROL_SCENE_MODE_HDR = tempSceneMode; + } + } + + private LegacyVendorTags() { + throw new AssertionError(); + } +}
\ No newline at end of file diff --git a/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java b/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java index 02dbbba..407a08a 100644 --- a/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java +++ b/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java @@ -66,7 +66,7 @@ public class BlockingCameraManager { /** * Returns the error code {@link ERROR_DISCONNECTED} if disconnected, or one of - * {@code CameraDevice.StateListener#ERROR_*} if there was another error. + * {@code CameraDevice.StateCallback#ERROR_*} if there was another error. * * @return int Disconnect/error code */ @@ -81,7 +81,7 @@ public class BlockingCameraManager { * @param errorCode * @param message * - * @see {@link CameraDevice.StateListener#ERROR_CAMERA_DEVICE} + * @see {@link CameraDevice.StateCallback#ERROR_CAMERA_DEVICE} */ public BlockingOpenException(int errorCode, String message) { super(message); @@ -117,8 +117,8 @@ public class BlockingCameraManager { * does.</p> * * <p>Throws {@link BlockingOpenException} when the open fails asynchronously (due to - * {@link CameraDevice.StateListener#onDisconnected(CameraDevice)} or - * ({@link CameraDevice.StateListener#onError(CameraDevice)}.</p> + * {@link CameraDevice.StateCallback#onDisconnected(CameraDevice)} or + * ({@link CameraDevice.StateCallback#onError(CameraDevice)}.</p> * * <p>Throws {@link TimeoutRuntimeException} if opening times out. This is usually * highly unrecoverable, and all future calls to opening that camera will fail since the @@ -142,7 +142,7 @@ public class BlockingCameraManager { * @throws TimeoutRuntimeException * If opening times out. Typically unrecoverable. */ - public CameraDevice openCamera(String cameraId, CameraDevice.StateListener listener, + public CameraDevice openCamera(String cameraId, CameraDevice.StateCallback listener, Handler handler) throws CameraAccessException, BlockingOpenException { if (handler == null) { @@ -163,17 +163,17 @@ public class BlockingCameraManager { /** * Block until CameraManager#openCamera finishes with onOpened/onError/onDisconnected * - * <p>Pass-through all StateListener changes to the proxy.</p> + * <p>Pass-through all StateCallback changes to the proxy.</p> * * <p>Time out after {@link #OPEN_TIME_OUT} and unblock. Clean up camera if it arrives * later.</p> */ - private class OpenListener extends CameraDevice.StateListener { + private class OpenListener extends CameraDevice.StateCallback { private static final int ERROR_UNINITIALIZED = -1; private final String mCameraId; - private final CameraDevice.StateListener mProxy; + private final CameraDevice.StateCallback mProxy; private final Object mLock = new Object(); private final ConditionVariable mDeviceReady = new ConditionVariable(); @@ -187,7 +187,7 @@ public class BlockingCameraManager { private boolean mTimedOut = false; OpenListener(CameraManager manager, String cameraId, - CameraDevice.StateListener listener, Handler handler) + CameraDevice.StateCallback listener, Handler handler) throws CameraAccessException { mCameraId = cameraId; mProxy = listener; diff --git a/camera2/public/src/com/android/ex/camera2/blocking/BlockingCaptureListener.java b/camera2/public/src/com/android/ex/camera2/blocking/BlockingCaptureCallback.java index eae85d1..f55437f 100644 --- a/camera2/public/src/com/android/ex/camera2/blocking/BlockingCaptureListener.java +++ b/camera2/public/src/com/android/ex/camera2/blocking/BlockingCaptureCallback.java @@ -36,7 +36,7 @@ import com.android.ex.camera2.utils.StateWaiter; * * @see #getStateWaiter */ -public class BlockingCaptureListener extends CameraCaptureSession.CaptureListener { +public class BlockingCaptureCallback extends CameraCaptureSession.CaptureCallback { /** * {@link #onCaptureStarted} has been called. @@ -80,10 +80,10 @@ public class BlockingCaptureListener extends CameraCaptureSession.CaptureListene "CAPTURE_SEQUENCE_ABORTED" }; - private static final String TAG = "BlockingCaptureListener"; + private static final String TAG = "BlockingCaptureCallback"; private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); - private final CameraCaptureSession.CaptureListener mProxy; + private final CameraCaptureSession.CaptureCallback mProxy; private final StateWaiter mStateWaiter = new StateWaiter(sStateNames); private final StateChangeListener mStateChangeListener = mStateWaiter.getListener(); @@ -92,7 +92,7 @@ public class BlockingCaptureListener extends CameraCaptureSession.CaptureListene * Create a blocking capture listener without forwarding the capture listener invocations * to another capture listener. */ - public BlockingCaptureListener() { + public BlockingCaptureCallback() { mProxy = null; } @@ -104,7 +104,7 @@ public class BlockingCaptureListener extends CameraCaptureSession.CaptureListene * * @throws NullPointerException if {@code listener} was {@code null} */ - public BlockingCaptureListener(CameraCaptureSession.CaptureListener listener) { + public BlockingCaptureCallback(CameraCaptureSession.CaptureCallback listener) { if (listener == null) { throw new NullPointerException("listener must not be null"); } @@ -123,8 +123,8 @@ public class BlockingCaptureListener extends CameraCaptureSession.CaptureListene @Override public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, - long timestamp) { - if (mProxy != null) mProxy.onCaptureStarted(session, request, timestamp); + long timestamp, long frameNumber) { + if (mProxy != null) mProxy.onCaptureStarted(session, request, timestamp, frameNumber); mStateChangeListener.onStateChanged(CAPTURE_STARTED); } diff --git a/camera2/public/src/com/android/ex/camera2/blocking/BlockingSessionListener.java b/camera2/public/src/com/android/ex/camera2/blocking/BlockingSessionCallback.java index 26bb652..e041d27 100644 --- a/camera2/public/src/com/android/ex/camera2/blocking/BlockingSessionListener.java +++ b/camera2/public/src/com/android/ex/camera2/blocking/BlockingSessionCallback.java @@ -36,11 +36,11 @@ import java.util.concurrent.TimeoutException; * <p>Provides a waiter that can be used to block until the next unobserved state of the * requested type arrives.</p> * - * <p>Pass-through all StateListener changes to the proxy.</p> + * <p>Pass-through all StateCallback changes to the proxy.</p> * * @see #getStateWaiter */ -public class BlockingSessionListener extends CameraCaptureSession.StateListener { +public class BlockingSessionCallback extends CameraCaptureSession.StateCallback { /** * Session is configured, ready for captures */ @@ -71,10 +71,10 @@ public class BlockingSessionListener extends CameraCaptureSession.StateListener /* * Private fields */ - private static final String TAG = "BlockingSessionListener"; + private static final String TAG = "BlockingSessionCallback"; private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); - private final CameraCaptureSession.StateListener mProxy; + private final CameraCaptureSession.StateCallback mProxy; private final SessionFuture mSessionFuture = new SessionFuture(); private final StateWaiter mStateWaiter = new StateWaiter(sStateNames); @@ -92,7 +92,7 @@ public class BlockingSessionListener extends CameraCaptureSession.StateListener * Create a blocking session listener without forwarding the session listener invocations * to another session listener. */ - public BlockingSessionListener() { + public BlockingSessionCallback() { mProxy = null; } @@ -104,7 +104,7 @@ public class BlockingSessionListener extends CameraCaptureSession.StateListener * * @throws NullPointerException if {@code listener} was {@code null} */ - public BlockingSessionListener(CameraCaptureSession.StateListener listener) { + public BlockingSessionCallback(CameraCaptureSession.StateCallback listener) { if (listener == null) { throw new NullPointerException("listener must not be null"); } @@ -142,7 +142,7 @@ public class BlockingSessionListener extends CameraCaptureSession.StateListener } /* - * CameraCaptureSession.StateListener implementation + * CameraCaptureSession.StateCallback implementation */ @Override diff --git a/camera2/public/src/com/android/ex/camera2/blocking/BlockingStateListener.java b/camera2/public/src/com/android/ex/camera2/blocking/BlockingStateCallback.java index 02c2ba3..5f93fbc 100644 --- a/camera2/public/src/com/android/ex/camera2/blocking/BlockingStateListener.java +++ b/camera2/public/src/com/android/ex/camera2/blocking/BlockingStateCallback.java @@ -36,14 +36,14 @@ import java.util.concurrent.TimeUnit; * the last wait, or that will be received from the camera device in the * future.</p> * - * <p>Pass-through all StateListener changes to the proxy.</p> + * <p>Pass-through all StateCallback changes to the proxy.</p> * */ -public class BlockingStateListener extends CameraDevice.StateListener { - private static final String TAG = "BlockingStateListener"; +public class BlockingStateCallback extends CameraDevice.StateCallback { + private static final String TAG = "BlockingStateCallback"; private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); - private final CameraDevice.StateListener mProxy; + private final CameraDevice.StateCallback mProxy; // Guards mWaiting private final Object mLock = new Object(); @@ -99,11 +99,11 @@ public class BlockingStateListener extends CameraDevice.StateListener { */ private static int NUM_STATES = 4; - public BlockingStateListener() { + public BlockingStateCallback() { mProxy = null; } - public BlockingStateListener(CameraDevice.StateListener listener) { + public BlockingStateCallback(CameraDevice.StateCallback listener) { mProxy = listener; } diff --git a/camera2/public/src/com/android/ex/camera2/pos/AutoFocusStateMachine.java b/camera2/public/src/com/android/ex/camera2/pos/AutoFocusStateMachine.java index 9fa2af2..e8a3ab6 100644 --- a/camera2/public/src/com/android/ex/camera2/pos/AutoFocusStateMachine.java +++ b/camera2/public/src/com/android/ex/camera2/pos/AutoFocusStateMachine.java @@ -91,7 +91,7 @@ public class AutoFocusStateMachine { /** * Invoke every time we get a new CaptureResult via - * {@link CameraDevice.CaptureListener#onCaptureCompleted}. + * {@link CameraDevice.CaptureCallback#onCaptureCompleted}. * * <p>This function is responsible for dispatching updates via the * {@link AutoFocusStateListener} so without calling this on a regular basis, no diff --git a/camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureListenerForwarder.java b/camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureCallbackForwarder.java index 35b1c6d..e6988b5 100644 --- a/camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureListenerForwarder.java +++ b/camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureCallbackForwarder.java @@ -17,7 +17,7 @@ package com.android.ex.camera2.utils; import android.hardware.camera2.CameraCaptureSession; -import android.hardware.camera2.CameraCaptureSession.CaptureListener; +import android.hardware.camera2.CameraCaptureSession.CaptureCallback; import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; @@ -25,14 +25,14 @@ import android.hardware.camera2.TotalCaptureResult; import android.os.Handler; /** - * Proxy that forwards all updates to another {@link CaptureListener}, invoking + * Proxy that forwards all updates to another {@link CaptureCallback}, invoking * its callbacks on a separate {@link Handler}. */ -public class Camera2CaptureListenerForwarder extends CaptureListener { - private CaptureListener mListener; +public class Camera2CaptureCallbackForwarder extends CaptureCallback { + private CaptureCallback mListener; private Handler mHandler; - public Camera2CaptureListenerForwarder(CaptureListener listener, Handler handler) { + public Camera2CaptureCallbackForwarder(CaptureCallback listener, Handler handler) { mListener = listener; mHandler = handler; } @@ -89,11 +89,11 @@ public class Camera2CaptureListenerForwarder extends CaptureListener { @Override public void onCaptureStarted(final CameraCaptureSession session, final CaptureRequest request, - final long timestamp) { + final long timestamp, final long frameNumber) { mHandler.post(new Runnable() { @Override public void run() { - mListener.onCaptureStarted(session, request, timestamp); + mListener.onCaptureStarted(session, request, timestamp, frameNumber); }}); } } diff --git a/camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureListenerSplitter.java b/camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureCallbackSplitter.java index a13dc04..86c6070 100644 --- a/camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureListenerSplitter.java +++ b/camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureCallbackSplitter.java @@ -17,7 +17,7 @@ package com.android.ex.camera2.utils; import android.hardware.camera2.CameraCaptureSession; -import android.hardware.camera2.CameraCaptureSession.CaptureListener; +import android.hardware.camera2.CameraCaptureSession.CaptureCallback; import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; @@ -28,18 +28,18 @@ import java.util.LinkedList; import java.util.List; /** - * Junction that allows notifying multiple {@link CaptureListener}s whenever + * Junction that allows notifying multiple {@link CaptureCallback}s whenever * the {@link CameraCaptureSession} posts a capture-related update. */ -public class Camera2CaptureListenerSplitter extends CaptureListener { - private final List<CaptureListener> mRecipients = new LinkedList<>(); +public class Camera2CaptureCallbackSplitter extends CaptureCallback { + private final List<CaptureCallback> mRecipients = new LinkedList<>(); /** * @param recipients The listeners to notify. Any {@code null} passed here * will be completely ignored. */ - public Camera2CaptureListenerSplitter(CaptureListener... recipients) { - for (CaptureListener listener : recipients) { + public Camera2CaptureCallbackSplitter(CaptureCallback... recipients) { + for (CaptureCallback listener : recipients) { if (listener != null) { mRecipients.add(listener); } @@ -49,7 +49,7 @@ public class Camera2CaptureListenerSplitter extends CaptureListener { @Override public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { - for (CaptureListener target : mRecipients) { + for (CaptureCallback target : mRecipients) { target.onCaptureCompleted(session, request, result); } } @@ -57,7 +57,7 @@ public class Camera2CaptureListenerSplitter extends CaptureListener { @Override public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) { - for (CaptureListener target : mRecipients) { + for (CaptureCallback target : mRecipients) { target.onCaptureFailed(session, request, failure); } } @@ -65,14 +65,14 @@ public class Camera2CaptureListenerSplitter extends CaptureListener { @Override public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult) { - for (CaptureListener target : mRecipients) { + for (CaptureCallback target : mRecipients) { target.onCaptureProgressed(session, request, partialResult); } } @Override public void onCaptureSequenceAborted(CameraCaptureSession session, int sequenceId) { - for (CaptureListener target : mRecipients) { + for (CaptureCallback target : mRecipients) { target.onCaptureSequenceAborted(session, sequenceId); } } @@ -80,16 +80,16 @@ public class Camera2CaptureListenerSplitter extends CaptureListener { @Override public void onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId, long frameNumber) { - for (CaptureListener target : mRecipients) { + for (CaptureCallback target : mRecipients) { target.onCaptureSequenceCompleted(session, sequenceId, frameNumber); } } @Override public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, - long timestamp) { - for (CaptureListener target : mRecipients) { - target.onCaptureStarted(session, request, timestamp); + long timestamp, long frameNumber) { + for (CaptureCallback target : mRecipients) { + target.onCaptureStarted(session, request, timestamp, frameNumber); } } } diff --git a/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2DeviceTester.java b/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2DeviceTester.java index 4db6dfb..e8639ba 100644 --- a/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2DeviceTester.java +++ b/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2DeviceTester.java @@ -52,7 +52,7 @@ public class Camera2DeviceTester { @InjectContext public Context mContext; - private class DeviceCapturer extends CameraDevice.StateListener { + private class DeviceCapturer extends CameraDevice.StateCallback { private CameraDevice mCamera; public CameraDevice captureCameraDevice() throws Exception { diff --git a/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2UtilsTest.java b/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2UtilsTest.java index 7847526..ea320a7 100644 --- a/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2UtilsTest.java +++ b/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2UtilsTest.java @@ -24,7 +24,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.graphics.Rect; -import android.hardware.camera2.CameraCaptureSession.CaptureListener; +import android.hardware.camera2.CameraCaptureSession.CaptureCallback; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureRequest.Key; @@ -33,65 +33,65 @@ import android.view.Surface; import org.junit.Test; public class Camera2UtilsTest extends Camera2DeviceTester { - private void captureListenerSplitterAllCallbacksReceived(CaptureListener splitter, - CaptureListener... terminals) { + private void captureListenerSplitterAllCallbacksReceived(CaptureCallback splitter, + CaptureCallback... terminals) { splitter.onCaptureCompleted(null, null, null); - for (CaptureListener each : terminals) { + for (CaptureCallback each : terminals) { verify(each).onCaptureCompleted(null, null, null); } splitter.onCaptureFailed(null, null, null); - for (CaptureListener each : terminals) { + for (CaptureCallback each : terminals) { verify(each).onCaptureFailed(null, null, null); } splitter.onCaptureProgressed(null, null, null); - for (CaptureListener each : terminals) { + for (CaptureCallback each : terminals) { verify(each).onCaptureProgressed(null, null, null); } splitter.onCaptureSequenceAborted(null, 0); - for (CaptureListener each : terminals) { + for (CaptureCallback each : terminals) { verify(each).onCaptureSequenceAborted(null, 0); } splitter.onCaptureSequenceCompleted(null, 0, 0L); - for (CaptureListener each : terminals) { + for (CaptureCallback each : terminals) { verify(each).onCaptureSequenceCompleted(null, 0, 0L); } - splitter.onCaptureStarted(null, null, 0L); - for (CaptureListener each : terminals) { - verify(each).onCaptureStarted(null, null, 0L); + splitter.onCaptureStarted(null, null, 0L, 1L); + for (CaptureCallback each : terminals) { + verify(each).onCaptureStarted(null, null, 0L, 1L); } } @Test public void captureListenerSplitter() { - CaptureListener firstBackingListener = mock(CaptureListener.class); - CaptureListener secondBackingListener = mock(CaptureListener.class); + CaptureCallback firstBackingListener = mock(CaptureCallback.class); + CaptureCallback secondBackingListener = mock(CaptureCallback.class); captureListenerSplitterAllCallbacksReceived( - new Camera2CaptureListenerSplitter(firstBackingListener, secondBackingListener), + new Camera2CaptureCallbackSplitter(firstBackingListener, secondBackingListener), firstBackingListener, secondBackingListener); } @Test public void captureListenerSplitterEmpty() { - captureListenerSplitterAllCallbacksReceived(new Camera2CaptureListenerSplitter()); + captureListenerSplitterAllCallbacksReceived(new Camera2CaptureCallbackSplitter()); } @Test public void captureListenerSplitterNoNpe() { captureListenerSplitterAllCallbacksReceived( - new Camera2CaptureListenerSplitter((CaptureListener) null)); + new Camera2CaptureCallbackSplitter((CaptureCallback) null)); } @Test public void captureListenerSplitterMultipleNulls() { captureListenerSplitterAllCallbacksReceived( - new Camera2CaptureListenerSplitter(null, null, null)); + new Camera2CaptureCallbackSplitter(null, null, null)); } @Test public void captureListenerSplitterValidAndNull() { - CaptureListener onlyRealBackingListener = mock(CaptureListener.class); + CaptureCallback onlyRealBackingListener = mock(CaptureCallback.class); captureListenerSplitterAllCallbacksReceived( - new Camera2CaptureListenerSplitter(null, onlyRealBackingListener), + new Camera2CaptureCallbackSplitter(null, onlyRealBackingListener), onlyRealBackingListener); } |