am ba164dfb: Merge "camera2: Fix race conditions and deadlocks around configuration" into lmp-dev
* commit 'ba164dfbcab8d1358075e18199b3ef55f85c8b88':
camera2: Fix race conditions and deadlocks around configuration
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index b9507d7..68926d0 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -633,7 +633,11 @@
@Override
public void onDrained() {
if (VERBOSE) Log.v(TAG, mIdString + "onIdleDrained");
- synchronized (CameraCaptureSessionImpl.this) {
+
+ // Take device lock before session lock so that we can call back into device
+ // without causing a deadlock
+ synchronized (mDeviceImpl.mInterfaceLock) {
+ synchronized (CameraCaptureSessionImpl.this) {
/*
* The device is now IDLE, and has settled. It will not transition to
* ACTIVE or BUSY again by itself.
@@ -642,28 +646,31 @@
*
* This operation is idempotent; a session will not be closed twice.
*/
- if (VERBOSE) Log.v(TAG, mIdString + "Session drain complete, skip unconfigure: " +
- mSkipUnconfigure);
+ if (VERBOSE)
+ Log.v(TAG, mIdString + "Session drain complete, skip unconfigure: " +
+ mSkipUnconfigure);
- // Fast path: A new capture session has replaced this one; don't unconfigure.
- if (mSkipUnconfigure) {
- mStateCallback.onClosed(CameraCaptureSessionImpl.this);
- return;
+ // Fast path: A new capture session has replaced this one; don't unconfigure.
+ if (mSkipUnconfigure) {
+ mStateCallback.onClosed(CameraCaptureSessionImpl.this);
+ return;
+ }
+
+ // Slow path: #close was called explicitly on this session; unconfigure first
+
+ try {
+ mUnconfigureDrainer.taskStarted();
+ mDeviceImpl
+ .configureOutputsChecked(null); // begin transition to unconfigured
+ } catch (CameraAccessException e) {
+ // OK: do not throw checked exceptions.
+ Log.e(TAG, mIdString + "Exception while configuring outputs: ", e);
+
+ // TODO: call onError instead of onClosed if this happens
+ }
+
+ mUnconfigureDrainer.beginDrain();
}
-
- // Slow path: #close was called explicitly on this session; unconfigure first
-
- try {
- mUnconfigureDrainer.taskStarted();
- mDeviceImpl.configureOutputsChecked(null); // begin transition to unconfigured
- } catch (CameraAccessException e) {
- // OK: do not throw checked exceptions.
- Log.e(TAG, mIdString + "Exception while configuring outputs: ", e);
-
- // TODO: call onError instead of onClosed if this happens
- }
-
- mUnconfigureDrainer.beginDrain();
}
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 2578093..ec450bd1 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -60,7 +60,7 @@
private ICameraDeviceUser mRemoteDevice;
// Lock to synchronize cross-thread access to device public interface
- private final Object mInterfaceLock = new Object();
+ final Object mInterfaceLock = new Object(); // access from this class and Session only!
private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
private final StateCallback mDeviceCallback;
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
index e96c15f..89e2d98 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
@@ -73,6 +73,7 @@
void onError(int errorCode, RequestHolder holder);
void onConfiguring();
void onIdle();
+ void onBusy();
void onCaptureStarted(RequestHolder holder, long timestamp);
void onCaptureResult(CameraMetadataNative result, RequestHolder holder);
}
@@ -217,6 +218,20 @@
}
Log.i(TAG, "Legacy camera service transitioning to state " + stateName);
}
+
+ // If we transitioned into a non-IDLE/non-ERROR state then mark the device as busy
+ if(newState != STATE_ERROR && newState != STATE_IDLE) {
+ if (mCurrentState != newState && mCurrentHandler != null &&
+ mCurrentListener != null) {
+ mCurrentHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCurrentListener.onBusy();
+ }
+ });
+ }
+ }
+
switch(newState) {
case STATE_ERROR:
if (mCurrentState != STATE_ERROR && mCurrentHandler != null &&
diff --git a/core/java/android/hardware/camera2/legacy/GLThreadManager.java b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
index c8e0147..64c532b 100644
--- a/core/java/android/hardware/camera2/legacy/GLThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
@@ -113,6 +113,9 @@
case MSG_ALLOW_FRAMES:
mDroppingFrames = false;
break;
+ case RequestHandlerThread.MSG_POKE_IDLE_HANDLER:
+ // OK: Ignore message.
+ break;
default:
Log.e(TAG, "Unhandled message " + msg.what + " on GLThread.");
break;
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index 4587c6f..3a976ba 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -21,6 +21,7 @@
import android.hardware.Camera;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.impl.CameraDeviceImpl;
import android.hardware.camera2.impl.CaptureResultExtras;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.params.StreamConfiguration;
@@ -95,7 +96,25 @@
new CameraDeviceState.CameraDeviceStateListener() {
@Override
public void onError(final int errorCode, final RequestHolder holder) {
- mIdle.open();
+ if (DEBUG) {
+ Log.d(TAG, "onError called, errorCode = " + errorCode);
+ }
+ switch (errorCode) {
+ /*
+ * Only be considered idle if we hit a fatal error
+ * and no further requests can be processed.
+ */
+ case CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DISCONNECTED:
+ case CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_SERVICE:
+ case CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE: {
+ mIdle.open();
+
+ if (DEBUG) {
+ Log.d(TAG, "onError - opening idle");
+ }
+ }
+ }
+
final CaptureResultExtras extras = getExtrasFromRequest(holder);
mResultHandler.post(new Runnable() {
@Override
@@ -124,6 +143,10 @@
@Override
public void onIdle() {
+ if (DEBUG) {
+ Log.d(TAG, "onIdle called");
+ }
+
mIdle.open();
mResultHandler.post(new Runnable() {
@@ -143,6 +166,15 @@
}
@Override
+ public void onBusy() {
+ mIdle.close();
+
+ if (DEBUG) {
+ Log.d(TAG, "onBusy called");
+ }
+ }
+
+ @Override
public void onCaptureStarted(final RequestHolder holder, final long timestamp) {
final CaptureResultExtras extras = getExtrasFromRequest(holder);
diff --git a/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java b/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java
index 36cd907..0699ffb 100644
--- a/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java
+++ b/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java
@@ -23,6 +23,15 @@
import android.os.MessageQueue;
public class RequestHandlerThread extends HandlerThread {
+
+ /**
+ * Ensure that the MessageQueue's idle handler gets run by poking the message queue;
+ * normally if the message queue is already idle, the idle handler won't get invoked.
+ *
+ * <p>Users of this handler thread should ignore this message.</p>
+ */
+ public final static int MSG_POKE_IDLE_HANDLER = -1;
+
private final ConditionVariable mStarted = new ConditionVariable(false);
private final ConditionVariable mIdle = new ConditionVariable(true);
private Handler.Callback mCallback;
@@ -86,12 +95,15 @@
// Blocks until thread is idling
public void waitUntilIdle() {
- Looper looper = waitAndGetHandler().getLooper();
+ Handler handler = waitAndGetHandler();
+ Looper looper = handler.getLooper();
if (looper.isIdling()) {
return;
}
mIdle.close();
looper.getQueue().addIdleHandler(mIdleHandler);
+ // Ensure that the idle handler gets run even if the looper already went idle
+ handler.sendEmptyMessage(MSG_POKE_IDLE_HANDLER);
if (looper.isIdling()) {
return;
}
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index a7ea89c..17ce248 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -864,6 +864,9 @@
}
resetJpegSurfaceFormats(mCallbackOutputs);
break;
+ case RequestHandlerThread.MSG_POKE_IDLE_HANDLER:
+ // OK: Ignore message.
+ break;
default:
throw new AssertionError("Unhandled message " + msg.what +
" on RequestThread.");