summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--camera2/Android.mk1
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java575
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java12
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java160
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java474
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraCapabilities.java7
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java14
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraActions.java70
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java436
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java37
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraExceptionHandler.java123
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java99
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraStateHolder.java38
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/HistoryHandler.java4
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/LegacyVendorTags.java55
-rw-r--r--camera2/portability/tests/src/com/android/ex/camera2/portability/Camera2PortabilityTest.java32
-rw-r--r--camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java18
-rw-r--r--camera2/public/src/com/android/ex/camera2/blocking/BlockingCaptureCallback.java (renamed from camera2/public/src/com/android/ex/camera2/blocking/BlockingCaptureListener.java)14
-rw-r--r--camera2/public/src/com/android/ex/camera2/blocking/BlockingSessionCallback.java (renamed from camera2/public/src/com/android/ex/camera2/blocking/BlockingSessionListener.java)14
-rw-r--r--camera2/public/src/com/android/ex/camera2/blocking/BlockingStateCallback.java (renamed from camera2/public/src/com/android/ex/camera2/blocking/BlockingStateListener.java)12
-rw-r--r--camera2/public/src/com/android/ex/camera2/pos/AutoFocusStateMachine.java2
-rw-r--r--camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureCallbackForwarder.java (renamed from camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureListenerForwarder.java)14
-rw-r--r--camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureCallbackSplitter.java (renamed from camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureListenerSplitter.java)28
-rw-r--r--camera2/utils/src/com/android/ex/camera2/utils/Camera2RequestSettingsSet.java17
-rw-r--r--camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2DeviceTester.java2
-rw-r--r--camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2UtilsTest.java84
-rw-r--r--common/java/com/android/common/widget/CompositeCursorAdapter.java11
27 files changed, 1674 insertions, 679 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..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;
@@ -198,6 +211,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);
}
@@ -205,14 +221,16 @@ 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;
int cameraIndex = msg.arg1;
- if (mCameraState.getState() != AndroidCamera2StateHolder.CAMERA_UNOPENED) {
+ if (mCameraState.getState() > AndroidCamera2StateHolder.CAMERA_UNOPENED) {
openCallback.onDeviceOpenedAlready(cameraIndex,
generateHistoryString(cameraIndex));
break;
@@ -228,7 +246,7 @@ class AndroidCamera2AgentImpl extends CameraAgent {
mOpenCallback.onCameraDisabled(msg.arg1);
break;
}
- mCameraManager.openCamera(mCameraId, mCameraDeviceStateListener, this);
+ mCameraManager.openCamera(mCameraId, mCameraDeviceStateCallback, this);
break;
}
@@ -263,7 +281,7 @@ class AndroidCamera2AgentImpl extends CameraAgent {
mPhotoSize = null;
mCameraIndex = 0;
mCameraId = null;
- mCameraState.setState(AndroidCamera2StateHolder.CAMERA_UNOPENED);
+ changeState(AndroidCamera2StateHolder.CAMERA_UNOPENED);
break;
}
@@ -289,19 +307,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 +332,7 @@ class AndroidCamera2AgentImpl extends CameraAgent {
}
mSession.stopRepeating();
- mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY);
+ changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY);
break;
}
@@ -353,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
@@ -368,19 +395,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 +439,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,12 +451,15 @@ 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;
}
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) {
@@ -418,7 +468,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,12 +480,18 @@ 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;
}
+ 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;
@@ -467,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) {
@@ -486,8 +548,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 +571,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
@@ -541,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));
@@ -558,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);
}
}
@@ -599,13 +699,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 +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);
}
@@ -659,12 +759,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;
@@ -672,15 +782,17 @@ 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);
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 +816,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 +840,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 +917,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());
@@ -815,18 +973,27 @@ 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;
private final AndroidCamera2Capabilities mCapabilities;
+ 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;
mCapabilities = new AndroidCamera2Capabilities(properties);
+ mLastSettings = null;
+ mShutterSoundEnabled = true;
}
// TODO: Implement
@@ -848,10 +1015,34 @@ class AndroidCamera2AgentImpl extends CameraAgent {
return mCapabilities;
}
+ public CameraAgent getAgent() {
+ return mCameraAgent;
+ }
+
private AndroidCamera2Capabilities getSpecializedCapabilities() {
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) {}
@@ -870,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
@@ -930,12 +1135,14 @@ 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
public void run() {
- mNoisemaker.play(MediaActionSound.SHUTTER_CLICK);
+ if (mShutterSoundEnabled) {
+ mNoisemaker.play(MediaActionSound.SHUTTER_CLICK);
+ }
shutter.onShutter(AndroidCamera2ProxyImpl.this);
}});
}
@@ -956,14 +1163,20 @@ class AndroidCamera2AgentImpl extends CameraAgent {
}
}
}};
- mDispatchThread.runJob(new Runnable() {
- @Override
- public void run() {
- mCameraState.waitForStates(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE |
- AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED);
- 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
@@ -985,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
@@ -997,7 +1206,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 +1223,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 +1260,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 +1396,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) {
@@ -1196,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 51c1422..8001a37 100644
--- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java
+++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java
@@ -81,7 +81,7 @@ public class AndroidCamera2Capabilities extends CameraCapabilities {
mMaxNumOfFacesSupported = p.get(STATISTICS_INFO_MAX_FACE_COUNT);
mMaxNumOfMeteringArea = p.get(CONTROL_MAX_REGIONS_AE);
- // TODO: Populate mMaxZoomRatio
+ mMaxZoomRatio = p.get(SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
// TODO: Populate mHorizontalViewAngle
// TODO: Populate mVerticalViewAngle
// TODO: Populate mZoomRatioList
@@ -97,6 +97,10 @@ public class AndroidCamera2Capabilities extends CameraCapabilities {
mSupportedFeatures.add(Feature.METERING_AREA);
}
+ if (mMaxZoomRatio > CameraCapabilities.ZOOM_RATIO_UNZOOMED) {
+ mSupportedFeatures.add(Feature.ZOOM);
+ }
+
// TODO: Detect other features
}
@@ -200,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:
@@ -222,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 efa68e8..0062097 100644
--- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java
+++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java
@@ -18,10 +18,13 @@ 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;
+import android.location.Location;
import android.util.Range;
import com.android.ex.camera2.portability.CameraCapabilities.FlashMode;
@@ -41,8 +44,13 @@ public class AndroidCamera2Settings extends CameraSettings {
private static final Log.Tag TAG = new Log.Tag("AndCam2Set");
private final Builder mTemplateSettings;
- private final Rect mActiveArray;
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
@@ -56,19 +64,33 @@ public class AndroidCamera2Settings extends CameraSettings {
* their effective values when submitting a capture request will be those of
* the template that is provided to the camera framework at that time.</p>
*
- * @param camera Device from which to draw default settings.
+ * @param camera Device from which to draw default settings
+ * (non-{@code null}).
* @param template Specific template to use for the defaults.
- * @param activeArray Boundary coordinates of the sensor's active array.
+ * @param activeArray Boundary coordinates of the sensor's active array
+ * (non-{@code null}).
* @param preview Dimensions of preview streams.
* @param photo Dimensions of captured images.
*
+ * @throws IllegalArgumentException If {@code camera} or {@code activeArray}
+ * is {@code null}.
* @throws CameraAccessException Upon internal framework/driver failure.
*/
public AndroidCamera2Settings(CameraDevice camera, int template, Rect activeArray,
Size preview, Size photo) throws CameraAccessException {
+ if (camera == null) {
+ throw new NullPointerException("camera must not be null");
+ }
+ if (activeArray == null) {
+ throw new NullPointerException("activeArray must not be null");
+ }
+
mTemplateSettings = camera.createCaptureRequest(template);
- mActiveArray = activeArray;
mRequestSettings = new Camera2RequestSettingsSet();
+ 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) {
@@ -79,8 +101,8 @@ public class AndroidCamera2Settings extends CameraSettings {
setPhotoSize(photo);
mJpegCompressQuality = queryTemplateDefaultOrMakeOneUp(JPEG_QUALITY, (byte) 0);
// TODO: mCurrentPhotoFormat
- // TODO: mCurrentZoomRatio
- mCurrentZoomRatio = 1.0f;
+ // NB: We're assuming that templates won't be zoomed in by default.
+ mCurrentZoomRatio = CameraCapabilities.ZOOM_RATIO_UNZOOMED;
// TODO: mCurrentZoomIndex
mExposureCompensationIndex =
queryTemplateDefaultOrMakeOneUp(CONTROL_AE_EXPOSURE_COMPENSATION, 0);
@@ -116,8 +138,9 @@ public class AndroidCamera2Settings extends CameraSettings {
public AndroidCamera2Settings(AndroidCamera2Settings other) {
super(other);
mTemplateSettings = other.mTemplateSettings;
- mActiveArray = other.mActiveArray;
mRequestSettings = new Camera2RequestSettingsSet(other.mRequestSettings);
+ mActiveArray = other.mActiveArray;
+ mCropRectangle = new Rect(other.mCropRectangle);
}
@Override
@@ -159,13 +182,31 @@ public class AndroidCamera2Settings extends CameraSettings {
return null;
}
+ @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()),
+ toIntConstrained(
+ 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) {
if (setting == CONTROL_AE_REGIONS) {
return mMeteringAreas.size() == 0;
} 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());
@@ -187,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 &&
@@ -213,7 +259,7 @@ public class AndroidCamera2Settings extends CameraSettings {
// TODO: mCurrentPreviewFormat
updateRequestSettingOrForceToDefault(JPEG_QUALITY, mJpegCompressQuality);
// TODO: mCurrentPhotoFormat
- // TODO: mCurrentZoomRatio
+ mRequestSettings.set(SCALER_CROP_REGION, mCropRectangle);
// TODO: mCurrentZoomIndex
updateRequestSettingOrForceToDefault(CONTROL_AE_EXPOSURE_COMPENSATION,
mExposureCompensationIndex);
@@ -231,10 +277,14 @@ public class AndroidCamera2Settings extends CameraSettings {
updateRequestSettingOrForceToDefault(CONTROL_AE_LOCK, mAutoExposureLocked);
updateRequestSettingOrForceToDefault(CONTROL_AWB_LOCK, mAutoWhiteBalanceLocked);
// TODO: mRecordingHintEnabled
- // TODO: mGpsData
- updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE,
- new android.util.Size(
- mExifThumbnailSize.width(), mExifThumbnailSize.height()));
+ updateRequestGpsData();
+ if (mExifThumbnailSize != null) {
+ updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE,
+ new android.util.Size(
+ mExifThumbnailSize.width(), mExifThumbnailSize.height()));
+ } else {
+ updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE, null);
+ }
return mRequestSettings;
}
@@ -243,25 +293,25 @@ public class AndroidCamera2Settings extends CameraSettings {
List<android.hardware.Camera.Area> reference) {
MeteringRectangle[] transformed = null;
if (reference.size() > 0) {
-
transformed = new MeteringRectangle[reference.size()];
for (int index = 0; index < reference.size(); ++index) {
android.hardware.Camera.Area source = reference.get(index);
Rect rectangle = source.rect;
// Old API coordinates were [-1000,1000]; new ones are [0,ACTIVE_ARRAY_SIZE).
+ // We're also going from preview image--relative to sensor active array--relative.
double oldLeft = (rectangle.left + 1000) / 2000.0;
double oldTop = (rectangle.top + 1000) / 2000.0;
double oldRight = (rectangle.right + 1000) / 2000.0;
double oldBottom = (rectangle.bottom + 1000) / 2000.0;
- int left = toIntConstrained( mActiveArray.width() * oldLeft + mActiveArray.left,
- 0, mActiveArray.width() - 1);
- int top = toIntConstrained( mActiveArray.height() * oldTop + mActiveArray.top,
- 0, mActiveArray.height() - 1);
- int right = toIntConstrained( mActiveArray.width() * oldRight + mActiveArray.left,
- 0, mActiveArray.width() - 1);
- int bottom = toIntConstrained( mActiveArray.height() * oldBottom + mActiveArray.top,
- 0, mActiveArray.height() - 1);
+ int left = mCropRectangle.left + toIntConstrained(
+ mCropRectangle.width() * oldLeft, 0, mCropRectangle.width() - 1);
+ int top = mCropRectangle.top + toIntConstrained(
+ mCropRectangle.height() * oldTop, 0, mCropRectangle.height() - 1);
+ int right = mCropRectangle.left + toIntConstrained(
+ mCropRectangle.width() * oldRight, 0, mCropRectangle.width() - 1);
+ int bottom = mCropRectangle.top + toIntConstrained(
+ mCropRectangle.height() * oldBottom, 0, mCropRectangle.height() - 1);
transformed[index] = new MeteringRectangle(left, top, right - left, bottom - top,
source.weight);
}
@@ -378,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;
@@ -469,4 +522,65 @@ public class AndroidCamera2Settings extends CameraSettings {
}
mRequestSettings.set(CONTROL_AWB_MODE, mode);
}
+
+ private void updateRequestGpsData() {
+ if (mGpsData == null || mGpsData.processingMethod == null) {
+ // It's a hack since we always use GPS time stamp but does
+ // not use other fields sometimes. Setting processing
+ // method to null means the other fields should not be used.
+ mRequestSettings.set(JPEG_GPS_LOCATION, null);
+ } else {
+ Location location = new Location(mGpsData.processingMethod);
+ location.setTime(mGpsData.timeStamp);
+ location.setAltitude(mGpsData.altitude);
+ location.setLatitude(mGpsData.latitude);
+ location.setLongitude(mGpsData.longitude);
+ 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 c26a1a3..201a905 100644
--- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java
+++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java
@@ -38,6 +38,8 @@ import android.view.SurfaceHolder;
import com.android.ex.camera2.portability.debug.Log;
import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
import java.util.StringTokenizer;
/**
@@ -54,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
@@ -103,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;
@@ -223,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;
}
@@ -231,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;
@@ -252,8 +279,9 @@ class AndroidCameraAgentImpl extends CameraAgent {
}
}
- CameraHandler(Looper looper) {
+ CameraHandler(CameraAgent agent, Looper looper) {
super(looper);
+ mAgent = agent;
}
private void startFaceDetection() {
@@ -291,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,
@@ -310,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.
@@ -317,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;
@@ -338,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) {
@@ -357,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.");
}
@@ -371,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;
}
@@ -380,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;
}
@@ -398,6 +439,7 @@ class AndroidCameraAgentImpl extends CameraAgent {
break;
}
+ // TODO: Lock the CameraSettings object's sizes
case CameraActions.SET_PREVIEW_TEXTURE_ASYNC: {
setPreviewTexture(msg.obj);
break;
@@ -422,6 +464,7 @@ class AndroidCameraAgentImpl extends CameraAgent {
break;
}
+ // TODO: Unlock the CameraSettings object's sizes
case CameraActions.STOP_PREVIEW: {
mCamera.stopPreview();
break;
@@ -443,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;
@@ -464,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;
}
@@ -492,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;
@@ -538,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);
}
}
@@ -596,8 +667,8 @@ class AndroidCameraAgentImpl extends CameraAgent {
parameters.setPreviewFormat(settings.getCurrentPreviewFormat());
parameters.setJpegQuality(settings.getPhotoJpegCompressionQuality());
if (mCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
- // Should use settings.getCurrentZoomRatio() instead here.
- parameters.setZoom(settings.getCurrentZoomIndex());
+ parameters.setZoom(zoomRatioToIndex(settings.getCurrentZoomRatio(),
+ parameters.getZoomRatios()));
}
parameters.setExposureCompensation(settings.getExposureCompensationIndex());
if (mCapabilities.supports(CameraCapabilities.Feature.AUTO_EXPOSURE_LOCK)) {
@@ -610,11 +681,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) {
@@ -628,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();
@@ -648,6 +725,29 @@ class AndroidCameraAgentImpl extends CameraAgent {
}
}
+
+ /**
+ * @param ratio Desired zoom ratio, in [1.0f,+Inf).
+ * @param percentages Available zoom ratios, as percentages.
+ * @return Index of the closest corresponding ratio, rounded up toward
+ * that of the maximum available ratio.
+ */
+ private int zoomRatioToIndex(float ratio, List<Integer> percentages) {
+ int percent = (int) (ratio * AndroidCameraCapabilities.ZOOM_MULTIPLIER);
+ int index = Collections.binarySearch(percentages, percent);
+ if (index >= 0) {
+ // Found the desired ratio in the supported list
+ return index;
+ } else {
+ // Didn't find an exact match. Where would it have been?
+ index = -(index + 1);
+ if (index == percentages.size()) {
+ // Put it back in bounds by setting to the maximum allowable zoom
+ --index;
+ }
+ return index;
+ }
+ }
}
/**
@@ -655,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;
@@ -673,6 +778,9 @@ class AndroidCameraAgentImpl extends CameraAgent {
@Deprecated
@Override
public android.hardware.Camera getCamera() {
+ if (getCameraState().isInvalid()) {
+ return null;
+ }
return mCamera;
}
@@ -692,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() {
@@ -754,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();
@@ -765,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
@@ -798,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
@@ -861,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
@@ -877,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];
}
@@ -995,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 acff9c6..9892d4a 100644
--- a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraCapabilities.java
+++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraCapabilities.java
@@ -31,6 +31,9 @@ class AndroidCameraCapabilities extends CameraCapabilities {
private static Log.Tag TAG = new Log.Tag("AndCamCapabs");
+ /** Conversion from ratios to percentages. */
+ public static final float ZOOM_MULTIPLIER = 100f;
+
private FpsComparator mFpsComparator = new FpsComparator();
private SizeComparator mSizeComparator = new SizeComparator();
@@ -44,9 +47,6 @@ class AndroidCameraCapabilities extends CameraCapabilities {
mPreferredPreviewSizeForVideo = new Size(p.getPreferredPreviewSizeForVideo());
mSupportedPreviewFormats.addAll(p.getSupportedPreviewFormats());
mSupportedPhotoFormats.addAll(p.getSupportedPictureFormats());
- mMaxZoomIndex = p.getMaxZoom();
- mZoomRatioList.addAll(p.getZoomRatios());
- mMaxZoomRatio = mZoomRatioList.get(mMaxZoomIndex);
mHorizontalViewAngle = p.getHorizontalViewAngle();
mVerticalViewAngle = p.getVerticalViewAngle();
buildPreviewFpsRange(p);
@@ -59,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 ceab7fe..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,17 +17,27 @@
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);
+
// Preview
Camera.Size paramPreviewSize = params.getPreviewSize();
setPreviewSize(new Size(paramPreviewSize.width, paramPreviewSize.height));
@@ -41,10 +51,8 @@ public class AndroidCameraSettings extends CameraSettings {
// Capture: Focus, flash, zoom, exposure, scene mode.
if (capabilities.supports(CameraCapabilities.Feature.ZOOM)) {
setZoomRatio(params.getZoomRatios().get(params.getZoom()) / 100f);
- setZoomIndex(params.getZoom());
} else {
- setZoomRatio(1.0f);
- setZoomIndex(0);
+ setZoomRatio(CameraCapabilities.ZOOM_RATIO_UNZOOMED);
}
setExposureCompensationIndex(params.getExposureCompensation());
setFlashMode(stringifier.flashModeFromString(params.getFlashMode()));
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 dd4f77c..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,44 +459,84 @@ 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);
+ }
}
/**
* 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
- 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);
+ }
}
/**
* 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) {
+ // 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);
+ }
}
/**
@@ -472,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);
+ }
}
/**
@@ -498,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() {
@@ -505,6 +587,9 @@ public abstract class CameraAgent {
CameraStartPreviewCallbackForward.getNewInstance(h, cb))
.sendToTarget();
}});
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
@@ -513,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);
+ }
}
/**
@@ -558,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);
+ }
}
/**
@@ -578,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);
}
/**
@@ -638,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);
+ }
}
/**
@@ -668,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.
@@ -732,27 +850,40 @@ public abstract class CameraAgent {
protected boolean applySettingsHelper(CameraSettings settings,
final int statesToAwait) {
if (settings == null) {
- Log.v(TAG, "null parameters in applySettings()");
+ Log.v(TAG, "null argument in applySettings()");
return false;
}
if (!getCapabilities().supports(settings)) {
+ Log.w(TAG, "Unsupported settings in applySettings()");
return false;
}
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.
*/
@@ -763,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);
+ }
}
/**
@@ -777,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);
+ }
}
/**
@@ -826,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 3dc19f7..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;
@@ -36,6 +37,9 @@ public class CameraCapabilities {
private static Log.Tag TAG = new Log.Tag("CamCapabs");
+ /** Zoom ratio used for seeing sensor's full field of view. */
+ protected static final float ZOOM_RATIO_UNZOOMED = 1.0f;
+
/* All internal states are declared final and should be thread-safe. */
protected final ArrayList<int[]> mSupportedPreviewFpsRange = new ArrayList<int[]>();
@@ -57,12 +61,10 @@ public class CameraCapabilities {
protected int mMaxNumOfFacesSupported;
protected int mMaxNumOfFocusAreas;
protected int mMaxNumOfMeteringArea;
- protected int mMaxZoomRatio;
+ protected float mMaxZoomRatio;
protected float mHorizontalViewAngle;
protected float mVerticalViewAngle;
private final Stringifier mStringifier;
- protected final ArrayList<Integer> mZoomRatioList = new ArrayList<Integer>();
- protected int mMaxZoomIndex;
/**
* Focus modes.
@@ -183,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.
@@ -322,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("-", "_");
}
/**
@@ -488,8 +490,6 @@ public class CameraCapabilities {
mMaxNumOfFacesSupported = src.mMaxNumOfFacesSupported;
mMaxNumOfFocusAreas = src.mMaxNumOfFocusAreas;
mMaxNumOfMeteringArea = src.mMaxNumOfMeteringArea;
- mMaxZoomIndex = src.mMaxZoomIndex;
- mZoomRatioList.addAll(src.mZoomRatioList);
mMaxZoomRatio = src.mMaxZoomRatio;
mHorizontalViewAngle = src.mHorizontalViewAngle;
mVerticalViewAngle = src.mVerticalViewAngle;
@@ -635,17 +635,6 @@ public class CameraCapabilities {
return mMaxZoomRatio;
}
- // We'll replace these old style methods with new ones.
- @Deprecated
- public int getMaxZoomIndex() {
- return mMaxZoomIndex;
- }
-
- @Deprecated
- public List<Integer> getZoomRatioList() {
- return new ArrayList<Integer>(mZoomRatioList);
- }
-
/**
* @return The min exposure compensation index. The EV is the compensation
* index multiplied by the step value. If unsupported, both this method and
@@ -689,17 +678,15 @@ public class CameraCapabilities {
private boolean zoomCheck(final CameraSettings settings) {
final float ratio = settings.getCurrentZoomRatio();
- final int index = settings.getCurrentZoomIndex();
if (!supports(Feature.ZOOM)) {
- if (ratio != 1.0f || index != 0) {
+ if (ratio != ZOOM_RATIO_UNZOOMED) {
Log.v(TAG, "Zoom is not supported");
return false;
}
} else {
- if (settings.getCurrentZoomRatio() > getMaxZoomRatio() ||
- index > getMaxZoomIndex()) {
+ if (settings.getCurrentZoomRatio() > getMaxZoomRatio()) {
Log.v(TAG, "Zoom ratio is not supported: ratio = " +
- settings.getCurrentZoomRatio() + ", index = " + index);
+ settings.getCurrentZoomRatio());
return false;
}
}
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 26d0f85..ccd980a 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;
@@ -47,7 +48,6 @@ public abstract class CameraSettings {
protected byte mJpegCompressQuality;
protected int mCurrentPhotoFormat;
protected float mCurrentZoomRatio;
- protected int mCurrentZoomIndex;
protected int mExposureCompensationIndex;
protected CameraCapabilities.FlashMode mCurrentFlashMode;
protected CameraCapabilities.FocusMode mCurrentFocusMode;
@@ -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.
@@ -73,9 +73,20 @@ public abstract class CameraSettings {
public final long timeStamp;
public final String processingMethod;
- /** Constructor. */
+ /**
+ * Construct what may or may not actually represent a location,
+ * depending on the value of {@code processingMethod}.
+ *
+ * <p>Setting {@code processingMethod} to {@code null} means that
+ * {@code latitude}, {@code longitude}, and {@code altitude} will be
+ * completely ignored.</p>
+ */
public GpsData(double latitude, double longitude, double altitude, long timeStamp,
String processingMethod) {
+ if (processingMethod == null &&
+ (latitude != 0.0 || longitude != 0.0 || altitude != 0.0)) {
+ Log.w(TAG, "GpsData's nonzero data will be ignored due to null processingMethod");
+ }
this.latitude = latitude;
this.longitude = longitude;
this.altitude = altitude;
@@ -106,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;
@@ -117,7 +129,6 @@ public abstract class CameraSettings {
mJpegCompressQuality = src.mJpegCompressQuality;
mCurrentPhotoFormat = src.mCurrentPhotoFormat;
mCurrentZoomRatio = src.mCurrentZoomRatio;
- mCurrentZoomIndex = src.mCurrentZoomIndex;
mExposureCompensationIndex = src.mExposureCompensationIndex;
mCurrentFlashMode = src.mCurrentFlashMode;
mCurrentFocusMode = src.mCurrentFocusMode;
@@ -142,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 **/
/**
@@ -203,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;
}
/**
@@ -236,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;
}
/**
@@ -301,16 +337,6 @@ public abstract class CameraSettings {
mCurrentZoomRatio = ratio;
}
- @Deprecated
- public int getCurrentZoomIndex() {
- return mCurrentZoomIndex;
- }
-
- @Deprecated
- public void setZoomIndex(int index) {
- mCurrentZoomIndex = index;
- }
-
/** Exposure **/
public void setExposureCompensationIndex(int index) {
@@ -332,6 +358,14 @@ public abstract class CameraSettings {
return mAutoExposureLocked;
}
+ /**
+ * @param areas The areas for autoexposure. The coordinate system has domain
+ * and range [-1000,1000], measured relative to the visible
+ * preview image, with orientation matching that of the sensor.
+ * This means the coordinates must be transformed to account
+ * for the devices rotation---but not the zoom level---before
+ * being passed into this method.
+ */
public void setMeteringAreas(List<Camera.Area> areas) {
mMeteringAreas.clear();
if (areas != null) {
@@ -371,7 +405,12 @@ public abstract class CameraSettings {
}
/**
- * @param areas The areas to focus.
+ * @param areas The areas to focus. The coordinate system has domain and
+ * range [-1000,1000], measured relative to the visible preview
+ * image, with orientation matching that of the sensor. This
+ * means the coordinates must be transformed to account for
+ * the devices rotation---but not the zoom level---before being
+ * passed into this method.
*/
public void setFocusAreas(List<Camera.Area> areas) {
mFocusAreas.clear();
@@ -452,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/portability/tests/src/com/android/ex/camera2/portability/Camera2PortabilityTest.java b/camera2/portability/tests/src/com/android/ex/camera2/portability/Camera2PortabilityTest.java
index 034fac7..1f340fd 100644
--- a/camera2/portability/tests/src/com/android/ex/camera2/portability/Camera2PortabilityTest.java
+++ b/camera2/portability/tests/src/com/android/ex/camera2/portability/Camera2PortabilityTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import android.content.Context;
+import android.graphics.Rect;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
@@ -40,6 +41,11 @@ import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
public class Camera2PortabilityTest extends Camera2DeviceTester {
+ /**
+ * Ensure that applying {@code Stringifier#.*FromString()} reverses
+ * {@link Stringifier#stringify} for {@link FocusMode}, {@link FlashMode},
+ * {@link SceneMode}, and {@link WhiteBalance}.
+ */
@Test
public void cameraCapabilitiesStringifier() {
Stringifier strfy = new Stringifier();
@@ -57,6 +63,11 @@ public class Camera2PortabilityTest extends Camera2DeviceTester {
}
}
+ /**
+ * Ensure that {@code Stringifier#.*FromString()} default to the correct
+ * {@link FocusMode}, {@link FlashMode}, {@link SceneMode}, and
+ * {@link WhiteBalance} when given a {@code null}.
+ */
@Test
public void cameraCapabilitiesStringifierNull() {
Stringifier strfy = new Stringifier();
@@ -66,6 +77,11 @@ public class Camera2PortabilityTest extends Camera2DeviceTester {
assertEquals(strfy.whiteBalanceFromString(null), WhiteBalance.AUTO);
}
+ /**
+ * Ensure that {@code Stringifier#.*FromString()} default to the correct
+ * {@link FocusMode}, {@link FlashMode}, {@link SceneMode}, and
+ * {@link WhiteBalance} when given an unrecognized string.
+ */
@Test
public void cameraCapabilitiesStringifierInvalid() {
Stringifier strfy = new Stringifier();
@@ -86,10 +102,19 @@ public class Camera2PortabilityTest extends Camera2DeviceTester {
assertEquals(apiVal, setts.getRequestSettings().get(apiKey));
}
+ /**
+ * Ensure that {@link AndroidCamera2Settings} correctly translates its
+ * {@code FocusMode}, {@code SceneMode}, and {@code WhiteBalance} to the
+ * corresponding framework API 2 representations.
+ */
@Test
public void camera2SettingsSetOptionsAndGetRequestSettings() throws CameraAccessException {
+ // We're only testing the focus modes, scene modes and white balances,
+ // and won't use the activeArray, previewSize, or photoSize. The
+ // constructor requires the former, so pass a degenerate rectangle.
AndroidCamera2Settings set = new AndroidCamera2Settings(
- mCamera, CameraDevice.TEMPLATE_PREVIEW, null, null, null);
+ mCamera, CameraDevice.TEMPLATE_PREVIEW, /*activeArray*/new Rect(),
+ /*previewSize*/null, /*photoSize*/null);
// Focus modes
set.setFocusMode(FocusMode.AUTO);
@@ -158,6 +183,11 @@ public class Camera2PortabilityTest extends Camera2DeviceTester {
// TODO: Add a test checking whether stringification matches API 1 representation
+ /**
+ * Ensure that {@code AndroidCamera2Capabilities#.*FromInt} correctly
+ * translates from framework API 2 representations to the equivalent
+ * {@code FocusMode}s, {@code SceneMode}s, and {@code WhiteBalance}s.
+ */
@Test
public void camera2CapabilitiesFocusModeFromInt() throws CameraAccessException {
CameraCharacteristics chars = buildFrameworkCharacteristics();
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/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 bb23e37..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
@@ -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) {
- 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);
}
@@ -234,22 +235,61 @@ 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();
- setUp.createRequest(null, 0);
+ setUp.createRequest(null, CameraDevice.TEMPLATE_PREVIEW);
}
@Test(expected=NullPointerException.class)
public void requestSettingsSetNullArgToCreateRequest2() throws Exception {
Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
- setUp.createRequest(mCamera, 0, (Surface) null);
+ setUp.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW, (Surface) null);
}
@Test(expected=NullPointerException.class)
public void requestSettingsSetNullArgToCreateRequest02() throws Exception {
Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
- setUp.createRequest(null, 0, (Surface) null);
+ setUp.createRequest(null, CameraDevice.TEMPLATE_PREVIEW, (Surface) null);
}
@Test
diff --git a/common/java/com/android/common/widget/CompositeCursorAdapter.java b/common/java/com/android/common/widget/CompositeCursorAdapter.java
index dddbcf6..8a3fa9b 100644
--- a/common/java/com/android/common/widget/CompositeCursorAdapter.java
+++ b/common/java/com/android/common/widget/CompositeCursorAdapter.java
@@ -170,7 +170,12 @@ public abstract class CompositeCursorAdapter extends BaseAdapter {
mCount = 0;
for (Partition partition : mPartitions) {
Cursor cursor = partition.cursor;
- int count = cursor != null ? cursor.getCount() : 0;
+ int count;
+ if (cursor == null || cursor.isClosed()) {
+ count = 0;
+ } else {
+ count = cursor.getCount();
+ }
if (partition.hasHeader) {
if (count != 0 || partition.showIfEmpty) {
count++;
@@ -428,7 +433,9 @@ public abstract class CompositeCursorAdapter extends BaseAdapter {
return null;
}
Cursor cursor = mPartition.cursor;
- cursor.moveToPosition(offset);
+ if (cursor == null || cursor.isClosed() || !cursor.moveToPosition(offset)) {
+ return null;
+ }
return cursor;
}
start = end;