diff options
Diffstat (limited to 'camera2')
20 files changed, 573 insertions, 178 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 d139c62..62cb700 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java @@ -160,7 +160,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { } private static abstract class CaptureAvailableListener - extends CameraCaptureSession.CaptureListener + extends CameraCaptureSession.CaptureCallback implements ImageReader.OnImageAvailableListener {}; private class Camera2Handler extends HistoryHandler { @@ -198,6 +198,9 @@ class AndroidCamera2AgentImpl extends CameraAgent { // Available whenever setAutoFocusMoveCallback() was last invoked with a non-null argument: private CameraAFMoveCallback mPassiveAfCallback; + // Gets reset on every state change + private int mCurrentAeState = CaptureResult.CONTROL_AE_STATE_INACTIVE; + Camera2Handler(Looper looper) { super(looper); } @@ -212,7 +215,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { CameraOpenCallback openCallback = (CameraOpenCallback) msg.obj; int cameraIndex = msg.arg1; - if (mCameraState.getState() != AndroidCamera2StateHolder.CAMERA_UNOPENED) { + if (mCameraState.getState() > AndroidCamera2StateHolder.CAMERA_UNOPENED) { openCallback.onDeviceOpenedAlready(cameraIndex, generateHistoryString(cameraIndex)); break; @@ -228,7 +231,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { mOpenCallback.onCameraDisabled(msg.arg1); break; } - mCameraManager.openCamera(mCameraId, mCameraDeviceStateListener, this); + mCameraManager.openCamera(mCameraId, mCameraDeviceStateCallback, this); break; } @@ -263,7 +266,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { mPhotoSize = null; mCameraIndex = 0; mCameraId = null; - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_UNOPENED); + changeState(AndroidCamera2StateHolder.CAMERA_UNOPENED); break; } @@ -289,19 +292,23 @@ class AndroidCamera2AgentImpl extends CameraAgent { } mOneshotPreviewingCallback = (CameraStartPreviewCallback) msg.obj; - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE); + changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE); try { mSession.setRepeatingRequest( mPersistentSettings.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW, mPreviewSurface), - /*listener*/mCameraFocusStateListener, /*handler*/this); + /*listener*/mCameraResultStateCallback, /*handler*/this); } catch(CameraAccessException ex) { Log.w(TAG, "Unable to start preview", ex); - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); + changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); } break; } + // FIXME: We need to tear down the CameraCaptureSession here + // (and unlock the CameraSettings object from our + // CameraProxy) so that the preview/photo sizes can be + // changed again while no preview is running. case CameraActions.STOP_PREVIEW: { if (mCameraState.getState() < AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) { @@ -310,7 +317,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { } mSession.stopRepeating(); - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); + changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); break; } @@ -368,19 +375,39 @@ class AndroidCamera2AgentImpl extends CameraAgent { } // The earliest we can reliably tell whether the autofocus has locked in - // response to our latest request is when our one-time capture completes. + // response to our latest request is when our one-time capture progresses. // 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 + public void onCaptureProgressed(CameraCaptureSession session, + CaptureRequest request, + CaptureResult result) { + checkAfState(result); + } + @Override public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { - // Now our mCameraFocusStateListener will invoke the callback the - // first time it finds the focus motor to be locked. - mOneshotAfCallback = callback; + checkAfState(result); + } + + private void checkAfState(CaptureResult result) { + if (result.get(CaptureResult.CONTROL_AF_STATE) != null && + !mAlreadyDispatched) { + // 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. + mCameraResultStateCallback.monitorControlStates(result); + } } @Override @@ -392,7 +419,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { }}; // Send a one-time capture to trigger the camera driver to lock focus. - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); + changeState(AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); Camera2RequestSettingsSet trigger = new Camera2RequestSettingsSet(mPersistentSettings); trigger.set(CaptureRequest.CONTROL_AF_TRIGGER, @@ -404,7 +431,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { /*listener*/deferredCallbackSetter, /*handler*/ this); } catch(CameraAccessException ex) { Log.e(TAG, "Unable to lock autofocus", ex); - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE); + changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE); } break; } @@ -418,7 +445,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { } // Send a one-time capture to trigger the camera driver to resume scanning. - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE); + changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE); Camera2RequestSettingsSet cancel = new Camera2RequestSettingsSet(mPersistentSettings); cancel.set(CaptureRequest.CONTROL_AF_TRIGGER, @@ -430,8 +457,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { /*listener*/null, /*handler*/this); } catch(CameraAccessException ex) { Log.e(TAG, "Unable to cancel autofocus", ex); - mCameraState.setState( - AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); + changeState(AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); } break; } @@ -486,8 +512,21 @@ class AndroidCamera2AgentImpl extends CameraAgent { final CaptureAvailableListener listener = (CaptureAvailableListener) msg.obj; - if (mLegacyDevice) { - // Just snap the shot + if (mLegacyDevice || + (mCurrentAeState == CaptureResult.CONTROL_AE_STATE_CONVERGED && + !mPersistentSettings.matches(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH) && + !mPersistentSettings.matches(CaptureRequest.FLASH_MODE, + CaptureRequest.FLASH_MODE_SINGLE))) + { + // Legacy devices don't support the precapture state keys and instead + // perform autoexposure convergence automatically upon capture. + + // On other devices, as long as it has already converged, it determined + // that flash was not required, and we're not going to invalidate the + // current exposure levels by forcing the force on, we can save + // significant capture time by not forcing a recalculation. + Log.i(TAG, "Skipping pre-capture autoexposure convergence"); mCaptureReader.setOnImageAvailableListener(listener, /*handler*/this); try { mSession.capture( @@ -496,17 +535,42 @@ class AndroidCamera2AgentImpl extends CameraAgent { mCaptureReader.getSurface()), listener, /*handler*/this); } catch (CameraAccessException ex) { - Log.e(TAG, "Unable to initiate legacy capture", ex); + Log.e(TAG, "Unable to initiate immediate capture", ex); } } else { - // Not a legacy device, so we need to let AE converge before capturing - CameraCaptureSession.CaptureListener deferredCallbackSetter = - new CameraCaptureSession.CaptureListener() { + // We need to let AE converge before capturing. Once our one-time + // 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.CaptureCallback deferredCallbackSetter = + new CameraCaptureSession.CaptureCallback() { + private boolean mAlreadyDispatched = false; + + @Override + public void onCaptureProgressed(CameraCaptureSession session, + CaptureRequest request, + CaptureResult result) { + checkAeState(result); + } + @Override public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { - mOneshotCaptureCallback = listener; + checkAeState(result); + } + + private void checkAeState(CaptureResult result) { + if (result.get(CaptureResult.CONTROL_AE_STATE) != null && + !mAlreadyDispatched) { + // 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. + mCameraResultStateCallback.monitorControlStates(result); + } } @Override @@ -599,13 +663,13 @@ class AndroidCamera2AgentImpl extends CameraAgent { mSession.setRepeatingRequest( mPersistentSettings.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW, mPreviewSurface), - /*listener*/mCameraFocusStateListener, /*handler*/this); + /*listener*/mCameraResultStateCallback, /*handler*/this); } catch (CameraAccessException ex) { Log.e(TAG, "Failed to apply updated request settings", ex); } } else if (mCameraState.getState() < AndroidCamera2StateHolder.CAMERA_PREVIEW_READY) { // If we're already ready to preview, this doesn't regress our state - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_CONFIGURED); + changeState(AndroidCamera2StateHolder.CAMERA_CONFIGURED); } } @@ -646,7 +710,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); } @@ -659,12 +723,22 @@ class AndroidCamera2AgentImpl extends CameraAgent { } catch (CameraAccessException ex) { Log.e(TAG, "Failed to close existing camera capture session", ex); } - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_CONFIGURED); + changeState(AndroidCamera2StateHolder.CAMERA_CONFIGURED); } - // This listener monitors our connection to and disconnection from camera devices. - private CameraDevice.StateListener mCameraDeviceStateListener = - new CameraDevice.StateListener() { + private void changeState(int newState) { + if (mCameraState.getState() != newState) { + mCameraState.setState(newState); + if (newState < AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) { + mCurrentAeState = CaptureResult.CONTROL_AE_STATE_INACTIVE; + mCameraResultStateCallback.resetState(); + } + } + } + + // 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; @@ -680,7 +754,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { mLegacyDevice = props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_UNCONFIGURED); + changeState(AndroidCamera2StateHolder.CAMERA_UNCONFIGURED); mOpenCallback.onCameraOpened(mCameraProxy); } catch (CameraAccessException ex) { mOpenCallback.onDeviceOpenFailure(mCameraIndex, @@ -704,13 +778,13 @@ 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; - mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); + changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); } @Override @@ -728,49 +802,76 @@ class AndroidCamera2AgentImpl extends CameraAgent { } }}; - // This listener monitors requested captures and notifies any relevant callbacks. - private CameraCaptureSession.CaptureListener mCameraFocusStateListener = - new CameraCaptureSession.CaptureListener() { + private abstract class CameraResultStateCallback + extends CameraCaptureSession.CaptureCallback { + public abstract void monitorControlStates(CaptureResult result); + + public abstract void resetState(); + } + + // 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; + + @Override + public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, + CaptureResult result) { + monitorControlStates(result); + } @Override public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { + monitorControlStates(result); + } + + @Override + public void monitorControlStates(CaptureResult result) { Integer afStateMaybe = result.get(CaptureResult.CONTROL_AF_STATE); if (afStateMaybe != null) { int afState = afStateMaybe; - boolean afStateChanged = false; - if (afState != mLastAfState) { + // Since we handle both partial and total results for multiple frames here, we + // might get the final callbacks for an earlier frame after receiving one or + // more that correspond to the next one. To prevent our data from oscillating, + // we never consider AF states that are older than the last one we've seen. + if (result.getFrameNumber() > mLastAfFrameNumber) { + boolean afStateChanged = afState != mLastAfState; mLastAfState = afState; - afStateChanged = true; - } - - switch (afState) { - case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN: - case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED: - case CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED: { - if (afStateChanged && mPassiveAfCallback != null) { - // A CameraAFMoveCallback is attached. If we just started to scan, - // the motor is moving; otherwise, it has settled. - mPassiveAfCallback.onAutoFocusMoving( - afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN, - mCameraProxy); + mLastAfFrameNumber = result.getFrameNumber(); + + switch (afState) { + case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN: + case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED: + case CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED: { + if (afStateChanged && mPassiveAfCallback != null) { + // A CameraAFMoveCallback is attached. If we just started to + // scan, the motor is moving; otherwise, it has settled. + mPassiveAfCallback.onAutoFocusMoving( + afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN, + mCameraProxy); + } + break; } - break; - } - case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED: - case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED: { - if (mOneshotAfCallback != null) { - // A call to autoFocus() was just made to request a focus lock. - // Notify the caller that the lens is now indefinitely fixed, and - // report whether the image we're now stuck with is in focus. - mOneshotAfCallback.onAutoFocus( - afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED, - mCameraProxy); - mOneshotAfCallback = null; + case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED: + case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED: { + // This check must be made regardless of whether the focus state has + // changed recently to avoid infinite waiting during autoFocus() + // when the algorithm has already either converged or failed to. + if (mOneshotAfCallback != null) { + // A call to autoFocus() was just made to request a focus lock. + // Notify the caller that the lens is now indefinitely fixed, + // and report whether the image we're stuck with is in focus. + mOneshotAfCallback.onAutoFocus( + afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED, + mCameraProxy); + mOneshotAfCallback = null; + } + break; } - break; } } } @@ -778,36 +879,55 @@ class AndroidCamera2AgentImpl extends CameraAgent { Integer aeStateMaybe = result.get(CaptureResult.CONTROL_AE_STATE); if (aeStateMaybe != null) { int aeState = aeStateMaybe; - - switch (aeState) { - case CaptureResult.CONTROL_AE_STATE_CONVERGED: - case CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED: - case CaptureResult.CONTROL_AE_STATE_LOCKED: { - if (mOneshotCaptureCallback != null) { - // A call to takePicture() was just made, and autoexposure converged - // so it's time to initiate the capture! - mCaptureReader.setOnImageAvailableListener(mOneshotCaptureCallback, - /*handler*/Camera2Handler.this); - try { - mSession.capture( - mPersistentSettings.createRequest(mCamera, - CameraDevice.TEMPLATE_STILL_CAPTURE, - mCaptureReader.getSurface()), + // Since we handle both partial and total results for multiple frames here, we + // might get the final callbacks for an earlier frame after receiving one or + // more that correspond to the next one. To prevent our data from oscillating, + // we never consider AE states that are older than the last one we've seen. + if (result.getFrameNumber() > mLastAeFrameNumber) { + mCurrentAeState = aeStateMaybe; + mLastAeFrameNumber = result.getFrameNumber(); + + switch (aeState) { + case CaptureResult.CONTROL_AE_STATE_CONVERGED: + case CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED: + case CaptureResult.CONTROL_AE_STATE_LOCKED: { + // This check must be made regardless of whether the exposure state + // has changed recently to avoid infinite waiting during + // takePicture() when the algorithm has already converged. + if (mOneshotCaptureCallback != null) { + // A call to takePicture() was just made, and autoexposure + // converged so it's time to initiate the capture! + mCaptureReader.setOnImageAvailableListener( /*listener*/mOneshotCaptureCallback, /*handler*/Camera2Handler.this); - } catch (CameraAccessException ex) { - Log.e(TAG, "Unable to initiate capture", ex); - } finally { - mOneshotCaptureCallback = null; + try { + mSession.capture( + mPersistentSettings.createRequest(mCamera, + CameraDevice.TEMPLATE_STILL_CAPTURE, + mCaptureReader.getSurface()), + /*callback*/mOneshotCaptureCallback, + /*handler*/Camera2Handler.this); + } catch (CameraAccessException ex) { + Log.e(TAG, "Unable to initiate capture", ex); + } finally { + mOneshotCaptureCallback = null; + } } + break; } - break; } } } } @Override + public void resetState() { + mLastAfState = -1; + mLastAfFrameNumber = -1; + mLastAeFrameNumber = -1; + } + + @Override public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) { Log.e(TAG, "Capture attempt failed with reason " + failure.getReason()); @@ -819,6 +939,8 @@ class AndroidCamera2AgentImpl extends CameraAgent { private final CameraDevice mCamera; private final CameraDeviceInfo.Characteristics mCharacteristics; private final AndroidCamera2Capabilities mCapabilities; + private CameraSettings mLastSettings; + private boolean mShutterSoundEnabled; public AndroidCamera2ProxyImpl(int cameraIndex, CameraDevice camera, CameraDeviceInfo.Characteristics characteristics, @@ -827,6 +949,8 @@ class AndroidCamera2AgentImpl extends CameraAgent { mCamera = camera; mCharacteristics = characteristics; mCapabilities = new AndroidCamera2Capabilities(properties); + mLastSettings = null; + mShutterSoundEnabled = true; } // TODO: Implement @@ -852,6 +976,26 @@ class AndroidCamera2AgentImpl extends CameraAgent { return mCapabilities; } + // FIXME: Unlock the sizes in stopPreview(), as per the corresponding + // explanation on the STOP_PREVIEW case in the handler. + @Override + public void setPreviewTexture(SurfaceTexture surfaceTexture) { + // Once the Surface has been selected, we configure the session and + // are no longer able to change the sizes. + getSettings().setSizesLocked(true); + super.setPreviewTexture(surfaceTexture); + } + + // FIXME: Unlock the sizes in stopPreview(), as per the corresponding + // explanation on the STOP_PREVIEW case in the handler. + @Override + public void setPreviewTextureSync(SurfaceTexture surfaceTexture) { + // Once the Surface has been selected, we configure the session and + // are no longer able to change the sizes. + getSettings().setSizesLocked(true); + super.setPreviewTexture(surfaceTexture); + } + // TODO: Implement @Override public void setPreviewDataCallback(Handler handler, CameraPreviewDataCallback cb) {} @@ -935,7 +1079,9 @@ class AndroidCamera2AgentImpl extends CameraAgent { handler.post(new Runnable() { @Override public void run() { - mNoisemaker.play(MediaActionSound.SHUTTER_CLICK); + if (mShutterSoundEnabled) { + mNoisemaker.play(MediaActionSound.SHUTTER_CLICK); + } shutter.onShutter(AndroidCamera2ProxyImpl.this); }}); } @@ -959,8 +1105,9 @@ class AndroidCamera2AgentImpl extends CameraAgent { mDispatchThread.runJob(new Runnable() { @Override public void run() { - mCameraState.waitForStates(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE | - AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED); + // Wait until PREVIEW_ACTIVE or better + mCameraState.waitForStates( + ~(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE - 1)); mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO, picListener) .sendToTarget(); }}); @@ -997,7 +1144,10 @@ class AndroidCamera2AgentImpl extends CameraAgent { @Override public CameraSettings getSettings() { - return mCameraHandler.buildSettings(mCapabilities); + if (mLastSettings == null) { + mLastSettings = mCameraHandler.buildSettings(mCapabilities); + } + return mLastSettings; } @Override @@ -1011,9 +1161,17 @@ class AndroidCamera2AgentImpl extends CameraAgent { return false; } - return applySettingsHelper(settings, AndroidCamera2StateHolder.CAMERA_UNCONFIGURED | - AndroidCamera2StateHolder.CAMERA_CONFIGURED | - AndroidCamera2StateHolder.CAMERA_PREVIEW_READY); + // Wait for any state that isn't OPENED + if (applySettingsHelper(settings, ~AndroidCamera2StateHolder.CAMERA_UNOPENED)) { + mLastSettings = settings; + return true; + } + return false; + } + + @Override + public void enableShutterSound(boolean enable) { + mShutterSoundEnabled = enable; } // TODO: Implement @@ -1040,19 +1198,22 @@ class AndroidCamera2AgentImpl extends CameraAgent { private static class AndroidCamera2StateHolder extends CameraStateHolder { // Usage flow: openCamera() -> applySettings() -> setPreviewTexture() -> startPreview() -> // autoFocus() -> takePicture() + // States are mutually exclusive, but must be separate bits so that they can be used with + // the StateHolder#waitForStates() and StateHolder#waitToAvoidStates() methods. + // Do not set the state to be a combination of these values! /* Camera states */ /** No camera device is opened. */ - public static final int CAMERA_UNOPENED = 1; + public static final int CAMERA_UNOPENED = 1 << 0; /** A camera is opened, but no settings have been provided. */ - public static final int CAMERA_UNCONFIGURED = 2; + public static final int CAMERA_UNCONFIGURED = 1 << 1; /** The open camera has been configured by providing it with settings. */ - public static final int CAMERA_CONFIGURED = 3; + public static final int CAMERA_CONFIGURED = 1 << 2; /** A capture session is ready to stream a preview, but still has no repeating request. */ - public static final int CAMERA_PREVIEW_READY = 4; + public static final int CAMERA_PREVIEW_READY = 1 << 3; /** A preview is currently being streamed. */ - public static final int CAMERA_PREVIEW_ACTIVE = 5; + public static final int CAMERA_PREVIEW_ACTIVE = 1 << 4; /** The lens is locked on a particular region. */ - public static final int CAMERA_FOCUS_LOCKED = 6; + public static final int CAMERA_FOCUS_LOCKED = 1 << 5; public AndroidCamera2StateHolder() { this(CAMERA_UNOPENED); @@ -1173,9 +1334,7 @@ class AndroidCamera2AgentImpl extends CameraAgent { @Override public boolean canDisableShutterSound() { - // The new API doesn't support this operation, so don't encourage people to try it. - // TODO: What kind of assumptions have callers made about this result's meaning? - return false; + return true; } private static float[] convertRectToPoly(RectF rf) { 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 a286fb8..540d8df 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java @@ -18,7 +18,9 @@ package com.android.ex.camera2.portability; import static android.hardware.camera2.CaptureRequest.*; +import android.graphics.Matrix; import android.graphics.Rect; +import android.graphics.RectF; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.params.MeteringRectangle; @@ -43,8 +45,12 @@ public class AndroidCamera2Settings extends CameraSettings { private final Builder mTemplateSettings; private final Camera2RequestSettingsSet mRequestSettings; + /** Sensor's active array bounds. */ private final Rect mActiveArray; + /** Crop rectangle for digital zoom (measured WRT the active array). */ private final Rect mCropRectangle; + /** Bounds of visible preview portion (measured WRT the active array). */ + private Rect mVisiblePreviewRectangle; /** * Create a settings representation that answers queries of unspecified @@ -84,6 +90,8 @@ public class AndroidCamera2Settings extends CameraSettings { mActiveArray = activeArray; mCropRectangle = new Rect(0, 0, activeArray.width(), activeArray.height()); + mSizesLocked = false; + Range<Integer> previewFpsRange = mTemplateSettings.get(CONTROL_AE_TARGET_FPS_RANGE); if (previewFpsRange != null) { setPreviewFpsRange(previewFpsRange.getLower(), previewFpsRange.getUpper()); @@ -177,6 +185,8 @@ public class AndroidCamera2Settings extends CameraSettings { @Override public void setZoomRatio(float ratio) { super.setZoomRatio(ratio); + + // Compute the crop rectangle to be passed to the framework mCropRectangle.set(0, 0, toIntConstrained( mActiveArray.width() / mCurrentZoomRatio, 0, mActiveArray.width()), @@ -184,6 +194,10 @@ public class AndroidCamera2Settings extends CameraSettings { mActiveArray.height() / mCurrentZoomRatio, 0, mActiveArray.height())); mCropRectangle.offsetTo((mActiveArray.width() - mCropRectangle.width()) / 2, (mActiveArray.height() - mCropRectangle.height()) / 2); + + // Compute the effective crop rectangle to be used for computing focus/metering coordinates + mVisiblePreviewRectangle = + effectiveCropRectFromRequested(mCropRectangle, mCurrentPreviewSize); } private boolean matchesTemplateDefault(Key<?> setting) { @@ -405,7 +419,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; @@ -512,4 +529,49 @@ public class AndroidCamera2Settings extends CameraSettings { mRequestSettings.set(JPEG_GPS_LOCATION, location); } } + + /** + * Calculate the effective crop rectangle for this preview viewport; + * assumes the preview is centered to the sensor and scaled to fit across one of the dimensions + * without skewing. + * + * <p>Assumes the zoom level of the provided desired crop rectangle.</p> + * + * @param requestedCrop Desired crop rectangle, in active array space. + * @param previewSize Size of the preview buffer render target, in pixels (not in sensor space). + * @return A rectangle that serves as the preview stream's effective crop region (unzoomed), in + * sensor space. + * + * @throws NullPointerException + * If any of the args were {@code null}. + */ + private static Rect effectiveCropRectFromRequested(Rect requestedCrop, Size previewSize) { + float aspectRatioArray = requestedCrop.width() * 1.0f / requestedCrop.height(); + float aspectRatioPreview = previewSize.width() * 1.0f / previewSize.height(); + + float cropHeight, cropWidth; + if (aspectRatioPreview < aspectRatioArray) { + // The new width must be smaller than the height, so scale the width by AR + cropHeight = requestedCrop.height(); + cropWidth = cropHeight * aspectRatioPreview; + } else { + // The new height must be smaller (or equal) than the width, so scale the height by AR + cropWidth = requestedCrop.width(); + cropHeight = cropWidth / aspectRatioPreview; + } + + Matrix translateMatrix = new Matrix(); + RectF cropRect = new RectF(/*left*/0, /*top*/0, cropWidth, cropHeight); + + // Now center the crop rectangle so its center is in the center of the active array + translateMatrix.setTranslate(requestedCrop.exactCenterX(), requestedCrop.exactCenterY()); + translateMatrix.postTranslate(-cropRect.centerX(), -cropRect.centerY()); + + translateMatrix.mapRect(/*inout*/cropRect); + + // Round the rect corners towards the nearest integer values + Rect result = new Rect(); + cropRect.roundOut(result); + return result; + } } 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 f8a2e38..358d5f6 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java @@ -400,6 +400,7 @@ class AndroidCameraAgentImpl extends CameraAgent { break; } + // TODO: Lock the CameraSettings object's sizes case CameraActions.SET_PREVIEW_TEXTURE_ASYNC: { setPreviewTexture(msg.obj); break; @@ -424,6 +425,7 @@ class AndroidCameraAgentImpl extends CameraAgent { break; } + // TODO: Unlock the CameraSettings object's sizes case CameraActions.STOP_PREVIEW: { mCamera.stopPreview(); break; @@ -612,11 +614,15 @@ class AndroidCameraAgentImpl extends CameraAgent { if (mCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA)) { if (settings.getFocusAreas().size() != 0) { parameters.setFocusAreas(settings.getFocusAreas()); + } else { + parameters.setFocusAreas(null); } } if (mCapabilities.supports(CameraCapabilities.Feature.METERING_AREA)) { if (settings.getMeteringAreas().size() != 0) { parameters.setMeteringAreas(settings.getMeteringAreas()); + } else { + parameters.setMeteringAreas(null); } } if (settings.getCurrentFlashMode() != CameraCapabilities.FlashMode.NO_FLASH) { 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 f5421d6..ee69b54 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java @@ -28,6 +28,8 @@ public class AndroidCameraSettings extends CameraSettings { public AndroidCameraSettings(CameraCapabilities capabilities, Camera.Parameters params) { CameraCapabilities.Stringifier stringifier = capabilities.getStringifier(); + setSizesLocked(false); + // Preview Camera.Size paramPreviewSize = params.getPreviewSize(); setPreviewSize(new Size(paramPreviewSize.width, paramPreviewSize.height)); 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 df94a41..b624b47 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java @@ -436,8 +436,24 @@ public abstract class CameraAgent { /** * Sets the {@link android.graphics.SurfaceTexture} for preview. * + * <p>Note that, once this operation has been performed, it is no longer + * possible to change the preview or photo sizes in the + * {@link CameraSettings} instance for this camera, and the mutators for + * these fields are allowed to ignore all further invocations until the + * preview is stopped with {@link #stopPreview}.</p> + * * @param surfaceTexture The {@link SurfaceTexture} for preview. - */ + * + * @see CameraSettings#setPhotoSize + * @see CameraSettings#setPreviewSize + */ + // XXX: Despite the above documentation about locking the sizes, the API + // 1 implementation doesn't currently enforce this at all, although the + // Camera class warns that preview sizes shouldn't be changed while a + // preview is running. Furthermore, the API 2 implementation doesn't yet + // unlock the sizes when stopPreview() is invoked (see related FIXME on + // 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 @@ -452,7 +468,15 @@ public abstract class CameraAgent { * Blocks until a {@link android.graphics.SurfaceTexture} has been set * for preview. * + * <p>Note that, once this operation has been performed, it is no longer + * possible to change the preview or photo sizes in the + * {@link CameraSettings} instance for this camera, and the mutators for + * these fields are allowed to ignore all further invocations.</p> + * * @param surfaceTexture The {@link SurfaceTexture} for preview. + * + * @see CameraSettings#setPhotoSize + * @see CameraSettings#setPreviewSize */ public void setPreviewTextureSync(final SurfaceTexture surfaceTexture) { final WaitDoneBundle bundle = new WaitDoneBundle(); 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..60c8cb2 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java @@ -184,7 +184,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. 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 d0600e8..87e9adf 100644 --- a/camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java +++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java @@ -38,6 +38,7 @@ public abstract class CameraSettings { protected final Map<String, String> mGeneralSetting = new TreeMap<>(); protected final List<Camera.Area> mMeteringAreas = new ArrayList<>(); protected final List<Camera.Area> mFocusAreas = new ArrayList<>(); + protected boolean mSizesLocked; protected int mPreviewFpsRangeMin; protected int mPreviewFpsRangeMax; protected int mPreviewFrameRate; @@ -116,6 +117,7 @@ public abstract class CameraSettings { mGeneralSetting.putAll(src.mGeneralSetting); mMeteringAreas.addAll(src.mMeteringAreas); mFocusAreas.addAll(src.mFocusAreas); + mSizesLocked = src.mSizesLocked; mPreviewFpsRangeMin = src.mPreviewFpsRangeMin; mPreviewFpsRangeMax = src.mPreviewFpsRangeMax; mPreviewFrameRate = src.mPreviewFrameRate; @@ -151,6 +153,19 @@ public abstract class CameraSettings { mGeneralSetting.put(key, value); } + /** + * Changes whether classes outside this class are allowed to set the preview + * and photo capture sizes. + * + * @param locked Whether to prevent changes to these fields. + * + * @see #setPhotoSize + * @see #setPreviewSize + */ + /*package*/ void setSizesLocked(boolean locked) { + mSizesLocked = locked; + } + /** Preview **/ /** @@ -212,9 +227,16 @@ public abstract class CameraSettings { /** * @param previewSize The size to use for preview. + * @return Whether the operation was allowed (i.e. the sizes are unlocked). */ - public void setPreviewSize(Size previewSize) { + public boolean setPreviewSize(Size previewSize) { + if (mSizesLocked) { + Log.w(TAG, "Attempt to change preview size while locked"); + return false; + } + mCurrentPreviewSize = new Size(previewSize); + return true; } /** @@ -245,12 +267,17 @@ public abstract class CameraSettings { } /** - * Sets the size for the photo. - * - * @param photoSize The photo size. + * @param photoSize The size to use for preview. + * @return Whether the operation was allowed (i.e. the sizes are unlocked). */ - public void setPhotoSize(Size photoSize) { + public boolean setPhotoSize(Size photoSize) { + if (mSizesLocked) { + Log.w(TAG, "Attempt to change photo size while locked"); + return false; + } + mCurrentPhotoSize = new Size(photoSize); + return true; } /** 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..4fb2c43 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"); } 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..97b5b6e 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; } 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..f813076 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,7 +80,7 @@ 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); } } @@ -88,7 +88,7 @@ public class Camera2CaptureListenerSplitter extends CaptureListener { @Override public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, long timestamp) { - for (CaptureListener target : mRecipients) { + for (CaptureCallback target : mRecipients) { target.onCaptureStarted(session, request, timestamp); } } diff --git a/camera2/utils/src/com/android/ex/camera2/utils/Camera2RequestSettingsSet.java b/camera2/utils/src/com/android/ex/camera2/utils/Camera2RequestSettingsSet.java index fac8f1a..6e1d825 100644 --- a/camera2/utils/src/com/android/ex/camera2/utils/Camera2RequestSettingsSet.java +++ b/camera2/utils/src/com/android/ex/camera2/utils/Camera2RequestSettingsSet.java @@ -146,7 +146,7 @@ public class Camera2RequestSettingsSet { * to its default value or simply unset. While {@link #get} will return * {@code null} in both these cases, this method will return {@code true} * and {@code false}, respectively.</p> - + * * @param key Which setting to look for. * @return Whether that setting has a value that will propagate with unions. * @@ -160,8 +160,23 @@ public class Camera2RequestSettingsSet { } /** + * Check whether the value of the specified setting matches the given one. + * + * <p>This method uses the {@code T} type's {@code equals} method, but is + * {@code null}-tolerant.</p> + * + * @param key Which of this class's settings to check. + * @param value Value to test for equality against. + * @return Whether they are the same. + */ + public <T> boolean matches(Key<T> key, T value) { + return Objects.equals(get(key), value); + } + + /** * Get this set of settings's revision identifier, which can be compared * against cached past values to determine whether it has been modified. + * * <p>Distinct revisions across the same object do not necessarily indicate * that the object's key/value pairs have changed at all, but the same * revision on the same object does imply that they've stayed the same.</p> 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 01eda8f..3156cf7 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 @@ -23,7 +23,8 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import android.hardware.camera2.CameraCaptureSession.CaptureListener; +import android.graphics.Rect; +import android.hardware.camera2.CameraCaptureSession.CaptureCallback; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureRequest.Key; @@ -32,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) { + for (CaptureCallback each : terminals) { verify(each).onCaptureStarted(null, null, 0L); } } @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); } @@ -234,6 +235,45 @@ public class Camera2UtilsTest extends Camera2DeviceTester { setUp.get(null); } + @Test + public void requestSettingsSetMatchesPrimitives() { + Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet(); + assertTrue(setUp.matches(CaptureRequest.CONTROL_AE_LOCK, null)); + assertFalse(setUp.matches(CaptureRequest.CONTROL_AE_LOCK, false)); + assertFalse(setUp.matches(CaptureRequest.CONTROL_AE_LOCK, true)); + + setUp.set(CaptureRequest.CONTROL_AE_LOCK, null); + assertTrue(setUp.matches(CaptureRequest.CONTROL_AE_LOCK, null)); + assertFalse(setUp.matches(CaptureRequest.CONTROL_AE_LOCK, false)); + assertFalse(setUp.matches(CaptureRequest.CONTROL_AE_LOCK, true)); + + setUp.set(CaptureRequest.CONTROL_AE_LOCK, false); + assertFalse(setUp.matches(CaptureRequest.CONTROL_AE_LOCK, null)); + assertTrue(setUp.matches(CaptureRequest.CONTROL_AE_LOCK, false)); + assertFalse(setUp.matches(CaptureRequest.CONTROL_AE_LOCK, true)); + + setUp.set(CaptureRequest.CONTROL_AE_LOCK, true); + assertFalse(setUp.matches(CaptureRequest.CONTROL_AE_LOCK, null)); + assertFalse(setUp.matches(CaptureRequest.CONTROL_AE_LOCK, false)); + assertTrue(setUp.matches(CaptureRequest.CONTROL_AE_LOCK, true)); + } + + @Test + public void requestSettingsSetMatchesReferences() { + Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet(); + assertTrue(setUp.matches(CaptureRequest.SCALER_CROP_REGION, null)); + assertFalse(setUp.matches(CaptureRequest.SCALER_CROP_REGION, new Rect(0, 0, 0, 0))); + + setUp.set(CaptureRequest.SCALER_CROP_REGION, null); + assertTrue(setUp.matches(CaptureRequest.SCALER_CROP_REGION, null)); + assertFalse(setUp.matches(CaptureRequest.SCALER_CROP_REGION, new Rect(0, 0, 0, 0))); + + setUp.set(CaptureRequest.SCALER_CROP_REGION, new Rect(0, 0, 0, 0)); + assertFalse(setUp.matches(CaptureRequest.SCALER_CROP_REGION, null)); + assertTrue(setUp.matches(CaptureRequest.SCALER_CROP_REGION, new Rect(0, 0, 0, 0))); + assertFalse(setUp.matches(CaptureRequest.SCALER_CROP_REGION, new Rect(0, 0, 1, 1))); + } + @Test(expected=NullPointerException.class) public void requestSettingsSetNullArgToCreateRequest0() throws Exception { Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet(); |