Implement CameraState in camera2
Add Camera2 implementation of the public camera state API. More specifically, compute and publish new camera states from within Camera2CameraImpl.
Bug: 150921286
Test: Camera2CameraImplStateTest
Change-Id: I2a88fa300dd8747dedba96a9e46a57f555b26494
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplStateTest.kt b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplStateTest.kt
new file mode 100644
index 0000000..49a77ea
--- /dev/null
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplStateTest.kt
@@ -0,0 +1,513 @@
+/*
+ * Copyright 2021 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 androidx.camera.camera2.internal
+
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraManager
+import android.os.Handler
+import android.os.HandlerThread
+import androidx.camera.camera2.internal.compat.CameraAccessExceptionCompat
+import androidx.camera.camera2.internal.compat.CameraAccessExceptionCompat.CAMERA_UNAVAILABLE_DO_NOT_DISTURB
+import androidx.camera.camera2.internal.compat.CameraManagerCompat
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.CameraState
+import androidx.camera.core.CameraState.ERROR_CAMERA_IN_USE
+import androidx.camera.core.CameraState.ERROR_DO_NOT_DISTURB_MODE_ENABLED
+import androidx.camera.core.CameraState.create
+import androidx.camera.core.impl.CameraInternal
+import androidx.camera.core.impl.CameraStateRegistry
+import androidx.camera.core.impl.Observable
+import androidx.camera.core.impl.utils.MainThreadAsyncHandler
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.testing.CameraUtil
+import androidx.camera.testing.fakes.FakeCamera
+import androidx.core.os.HandlerCompat
+import androidx.lifecycle.Observer
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeout
+import org.junit.After
+import org.junit.AfterClass
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import java.util.concurrent.Executor
+import java.util.concurrent.Semaphore
+import java.util.concurrent.TimeUnit
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+internal class Camera2CameraImplStateTest {
+
+ @get:Rule
+ val cameraRule = CameraUtil.grantCameraPermissionAndPreTest()
+
+ private lateinit var cameraId: String
+ private lateinit var camera: Camera2CameraImpl
+ private lateinit var cameraStateRegistry: CameraStateRegistry
+
+ @Before
+ fun setCameraId() {
+ val nullableCameraId = CameraUtil.getCameraIdWithLensFacing(CameraSelector.LENS_FACING_BACK)
+ assumeFalse("Device doesn't have an available back facing camera", nullableCameraId == null)
+ cameraId = nullableCameraId!!
+ }
+
+ @After
+ fun releaseCameraResources() {
+ if (::camera.isInitialized) {
+ camera.release().get()
+ }
+ }
+
+ @Test
+ fun shouldEmitClosedStateInitially() {
+ val cameraManager = TestCameraManager()
+ initializeCamera(cameraManager)
+
+ assertCameraStateAfterAction(
+ action = {},
+ expectedState = create(CameraState.Type.CLOSED)
+ )
+ }
+
+ @Test
+ fun shouldEmitPendingOpenState_whenOpeningClosedCamera_andCameraUnavailable() {
+ val cameraManager = TestCameraManager()
+ initializeCamera(cameraManager)
+
+ // Open fake camera
+ val fakeCamera = FakeCamera()
+ cameraStateRegistry.registerCamera(fakeCamera, CameraXExecutors.directExecutor(), {})
+ cameraStateRegistry.tryOpenCamera(fakeCamera)
+ cameraStateRegistry.markCameraState(fakeCamera, CameraInternal.State.OPEN)
+
+ // Try to open camera. This should be prevented since fakeCamera is already open.
+ assertCameraStateAfterAction(
+ action = { camera.open() },
+ expectedState = create(CameraState.Type.PENDING_OPEN)
+ )
+ }
+
+ @Test
+ fun shouldEmitOpeningState_whenOpeningClosedCamera_andCameraAvailable() {
+ val cameraManager = TestCameraManager()
+ initializeCamera(cameraManager)
+
+ assertCameraStateAfterAction(
+ action = { camera.open() },
+ expectedState = create(CameraState.Type.OPENING)
+ )
+ }
+
+ @Test
+ fun shouldEmitOpenState_whenCameraOpened() {
+ val cameraManager = TestCameraManager()
+ initializeCamera(cameraManager)
+
+ assertCameraStateAfterAction(
+ action = {
+ camera.open()
+ camera.awaitCameraOpen()
+ },
+ expectedState = create(CameraState.Type.OPEN)
+ )
+ }
+
+ @Test
+ fun shouldEmitClosedState_afterOpeningCameraThrowsDNDException() {
+ val cameraManager = TestCameraManager(
+ onOpenCamera = {
+ throw CameraAccessExceptionCompat(CAMERA_UNAVAILABLE_DO_NOT_DISTURB)
+ }
+ )
+ initializeCamera(cameraManager)
+
+ assertCameraStateAfterAction(
+ action = { camera.open() },
+ expectedStatePredicate = { state ->
+ state.type == CameraState.Type.CLOSED &&
+ state.error?.code == ERROR_DO_NOT_DISTURB_MODE_ENABLED
+ }
+ )
+ }
+
+ @Test
+ fun shouldEmitOpeningState_whenOpeningCameraThrowsSecurityException() {
+ val cameraManager = TestCameraManager(onOpenCamera = { throw SecurityException() })
+ initializeCamera(cameraManager)
+
+ assertCameraStateAfterAction(
+ action = { camera.open() },
+ expectedState = create(CameraState.Type.OPENING)
+ )
+ }
+
+ @Test
+ fun shouldEmitOpeningState_whenOpeningCameraEncountersRecoverableError() {
+ val cameraManager = TestCameraManager(onOpenCamera = { })
+ initializeCamera(cameraManager)
+
+ assertCameraStateAfterAction(
+ action = {
+ camera.open()
+ cameraManager.triggerRecoverableError()
+ },
+ expectedStatePredicate = { state ->
+ state.type == CameraState.Type.OPENING && state.error != null
+ }
+ )
+
+ // Clean up
+ camera.close()
+ cameraManager.triggerClose()
+ }
+
+ @Test
+ fun shouldEmitClosingState_whenOpeningCameraEncountersCriticalError() {
+ val cameraManager = TestCameraManager(onOpenCamera = {})
+ initializeCamera(cameraManager)
+
+ assertCameraStateAfterAction(
+ action = {
+ camera.open()
+ cameraManager.triggerCriticalError()
+ },
+ expectedStatePredicate = { state ->
+ state.type == CameraState.Type.CLOSING && state.error != null
+ }
+ )
+
+ // Clean up
+ camera.close()
+ cameraManager.triggerClose()
+ }
+
+ @Test
+ fun shouldEmitPendingOpenState_afterReachingMaxReopenAttempts() {
+ val semaphore = Semaphore(0)
+ val cameraManager = TestCameraManager(
+ onOpenCamera = {
+ semaphore.release()
+ throw SecurityException()
+ }
+ )
+ initializeCamera(cameraManager)
+
+ assertCameraStateAfterAction(
+ action = {
+ camera.open()
+ awaitMaxReopenAttemptsReached(semaphore)
+ },
+ expectedState = create(CameraState.Type.PENDING_OPEN)
+ )
+ }
+
+ @Test
+ fun shouldEmitOpeningState_whenOpenCameraEncountersRecoverableError() {
+ val cameraManager = TestCameraManager()
+ initializeCamera(cameraManager)
+
+ camera.open()
+ camera.awaitCameraOpen()
+
+ assertCameraStateAfterAction(
+ action = { cameraManager.triggerDisconnect() },
+ expectedState = create(
+ CameraState.Type.OPENING,
+ CameraState.StateError.create(ERROR_CAMERA_IN_USE)
+ )
+ )
+
+ // Clean up
+ camera.close()
+ cameraManager.triggerClose()
+ }
+
+ @Test
+ fun shouldEmitClosingState_whenOpenCameraEncountersCriticalError() {
+ val cameraManager = TestCameraManager()
+ initializeCamera(cameraManager)
+
+ camera.open()
+ camera.awaitCameraOpen()
+
+ assertCameraStateAfterAction(
+ action = { cameraManager.triggerCriticalError() },
+ expectedStatePredicate = { state ->
+ state.type == CameraState.Type.CLOSING && state.error != null
+ }
+ )
+
+ // Clean up
+ camera.close()
+ cameraManager.triggerClose()
+ }
+
+ @Test
+ fun shouldEmitClosingState_whenOpenCameraGetsCloseSignal() {
+ val cameraManager = TestCameraManager()
+ initializeCamera(cameraManager)
+
+ camera.open()
+ camera.awaitCameraOpen()
+
+ assertCameraStateAfterAction(
+ action = { camera.close() },
+ expectedState = create(CameraState.Type.CLOSING)
+ )
+ }
+
+ @Test
+ fun shouldEmitClosedState_whenCameraClosed() {
+ val cameraManager = TestCameraManager()
+ initializeCamera(cameraManager)
+
+ camera.open()
+ camera.awaitCameraOpen()
+
+ assertCameraStateAfterAction(
+ action = {
+ camera.close()
+ camera.awaitCameraClosed()
+ },
+ expectedState = create(CameraState.Type.CLOSED)
+ )
+ }
+
+ @Test
+ fun shouldEmitOpeningState_whenPendingOpenCameraReceivesOpenSignal() {
+ val cameraManager = TestCameraManager()
+ initializeCamera(cameraManager)
+
+ // Open fake camera
+ val fakeCamera = FakeCamera()
+ cameraStateRegistry.registerCamera(fakeCamera, CameraXExecutors.directExecutor(), {})
+ cameraStateRegistry.tryOpenCamera(fakeCamera)
+ cameraStateRegistry.markCameraState(fakeCamera, CameraInternal.State.OPEN)
+
+ // Try to open camera. This should be prevented since fakeCamera is already open.
+ assertCameraStateAfterAction(
+ action = { camera.open() },
+ expectedState = create(CameraState.Type.PENDING_OPEN)
+ )
+
+ // The camera should start opening when fakeCamera is closed
+ assertCameraStateAfterAction(
+ action = {
+ // Close fake camera
+ fakeCamera.close()
+ cameraStateRegistry.markCameraState(fakeCamera, CameraInternal.State.CLOSED)
+ },
+ expectedState = create(CameraState.Type.OPENING)
+ )
+ }
+
+ private fun initializeCamera(cameraManager: TestCameraManager) {
+ // Build camera manager wrapper
+ val cameraManagerCompat = CameraManagerCompat.from(cameraManager)
+
+ // Build camera info
+ val camera2CameraInfo = Camera2CameraInfoImpl(
+ cameraId,
+ cameraManagerCompat.getCameraCharacteristicsCompat(cameraId)
+ )
+
+ // Initialize camera state registry and only allow 1 open camera at most inside CameraX
+ cameraStateRegistry = CameraStateRegistry(1)
+
+ // Initialize camera instance
+ camera = Camera2CameraImpl(
+ cameraManagerCompat,
+ cameraId,
+ camera2CameraInfo,
+ cameraStateRegistry,
+ CameraXExecutors.directExecutor(),
+ cameraHandler
+ )
+ }
+
+ private fun Camera2CameraImpl.awaitCameraOpen() {
+ awaitInternalState(CameraInternal.State.OPEN)
+ }
+
+ private fun Camera2CameraImpl.awaitCameraClosed() {
+ awaitInternalState(CameraInternal.State.CLOSED)
+ }
+
+ private fun Camera2CameraImpl.awaitInternalState(state: CameraInternal.State) = runBlocking {
+ val receivedState = CompletableDeferred<Unit>()
+ val observer = object : Observable.Observer<CameraInternal.State> {
+ override fun onNewData(value: CameraInternal.State?) {
+ if (value == state) {
+ receivedState.complete(Unit)
+ }
+ }
+
+ override fun onError(t: Throwable) {
+ // No-op
+ }
+ }
+ cameraState.addObserver(CameraXExecutors.directExecutor(), observer)
+
+ try {
+ withTimeout(CAMERA_OPEN_CLOSE_WAIT) { receivedState.await() }
+ } finally {
+ cameraState.removeObserver(observer)
+ }
+ }
+
+ private fun awaitMaxReopenAttemptsReached(semaphore: Semaphore) {
+ while (true) {
+ val cameraOpenAttempted =
+ semaphore.tryAcquire(CAMERA_REOPEN_WAIT, TimeUnit.MILLISECONDS)
+ if (!cameraOpenAttempted) {
+ return
+ }
+ }
+ }
+
+ private fun assertCameraStateAfterAction(action: () -> Unit, expectedState: CameraState) {
+ assertCameraStateAfterAction(action, { state -> state == expectedState })
+ }
+
+ private fun assertCameraStateAfterAction(
+ action: () -> Unit,
+ expectedStatePredicate: ((CameraState) -> Boolean)
+ ) = runBlocking {
+ val nextStateReceived = CompletableDeferred<Unit>()
+ val stateObserver = Observer<CameraState> { state ->
+ if (expectedStatePredicate.invoke(state)) {
+ nextStateReceived.complete(Unit)
+ }
+ }
+
+ withContext(Dispatchers.Main) {
+ camera.cameraInfo.cameraState.observeForever(stateObserver)
+ }
+
+ action.invoke()
+
+ try {
+ withTimeout(CAMERA_STATE_WAIT) { nextStateReceived.await() }
+ } finally {
+ withContext(Dispatchers.Main) {
+ camera.cameraInfo.cameraState.removeObserver(stateObserver)
+ }
+ }
+ }
+
+ class TestCameraManager(private val onOpenCamera: (() -> Unit)? = null) :
+ CameraManagerCompat.CameraManagerCompatImpl {
+
+ private val forwardCameraManager = CameraManagerCompat.CameraManagerCompatImpl.from(
+ ApplicationProvider.getApplicationContext(),
+ MainThreadAsyncHandler.getInstance()
+ )
+ private var stateCallback: CameraDevice.StateCallback? = null
+
+ override fun getCameraIdList(): Array<String> {
+ return forwardCameraManager.cameraIdList
+ }
+
+ override fun registerAvailabilityCallback(
+ executor: Executor,
+ callback: CameraManager.AvailabilityCallback
+ ) {
+ // No-op
+ }
+
+ override fun unregisterAvailabilityCallback(callback: CameraManager.AvailabilityCallback) {
+ // No-op
+ }
+
+ override fun getCameraCharacteristics(cameraId: String): CameraCharacteristics {
+ return forwardCameraManager.getCameraCharacteristics(cameraId)
+ }
+
+ override fun openCamera(
+ cameraId: String,
+ executor: Executor,
+ callback: CameraDevice.StateCallback
+ ) {
+ stateCallback = callback
+ if (onOpenCamera == null) {
+ forwardCameraManager.openCamera(cameraId, executor, callback)
+ } else {
+ onOpenCamera.invoke()
+ }
+ }
+
+ override fun getCameraManager(): CameraManager {
+ return forwardCameraManager.cameraManager
+ }
+
+ fun triggerRecoverableError() {
+ stateCallback?.onError(
+ Mockito.mock(CameraDevice::class.java),
+ CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE
+ )
+ }
+
+ fun triggerCriticalError() {
+ stateCallback?.onError(
+ Mockito.mock(CameraDevice::class.java),
+ CameraDevice.StateCallback.ERROR_CAMERA_DISABLED
+ )
+ }
+
+ fun triggerDisconnect() {
+ stateCallback?.onDisconnected(Mockito.mock(CameraDevice::class.java))
+ }
+
+ fun triggerClose() {
+ stateCallback?.onClosed(Mockito.mock(CameraDevice::class.java))
+ }
+ }
+
+ companion object {
+ private const val CAMERA_OPEN_CLOSE_WAIT = 5000.toLong() // 5 seconds
+ private const val CAMERA_REOPEN_WAIT = 3000.toLong() // 3 seconds
+ private const val CAMERA_STATE_WAIT = 1000.toLong() // 1 second
+
+ private lateinit var cameraHandlerThread: HandlerThread
+ private lateinit var cameraHandler: Handler
+
+ @JvmStatic
+ @BeforeClass
+ fun classSetup() {
+ cameraHandlerThread = HandlerThread("CameraThread")
+ cameraHandlerThread.start()
+ cameraHandler = HandlerCompat.createAsync(cameraHandlerThread.looper)
+ }
+
+ @JvmStatic
+ @AfterClass
+ fun classTeardown() {
+ cameraHandlerThread.quitSafely()
+ }
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
index a6a4244..feba09a 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
@@ -38,6 +38,7 @@
import androidx.camera.camera2.internal.compat.CameraAccessExceptionCompat;
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
import androidx.camera.camera2.internal.compat.CameraManagerCompat;
+import androidx.camera.core.CameraState;
import androidx.camera.core.CameraUnavailableException;
import androidx.camera.core.Logger;
import androidx.camera.core.Preview;
@@ -953,7 +954,8 @@
case CameraAccessExceptionCompat.CAMERA_UNAVAILABLE_DO_NOT_DISTURB:
// Camera2 is unable to call the onError() callback for this case. It has to
// reset the state here.
- setState(InternalState.INITIALIZED);
+ setState(InternalState.INITIALIZED, CameraState.StateError.create(
+ CameraState.ERROR_DO_NOT_DISTURB_MODE_ENABLED, e));
break;
default:
// Camera2 will call the onError() callback with the specific error code that
@@ -1021,11 +1023,7 @@
@Override
@ExecutedBy("mExecutor")
public void onFailure(Throwable t) {
- if (t instanceof CameraAccessException) {
- debugLog("Unable to configure camera due to " + t.getMessage());
- } else if (t instanceof CancellationException) {
- debugLog("Unable to configure camera cancelled");
- } else if (t instanceof DeferrableSurface.SurfaceClosedException) {
+ if (t instanceof DeferrableSurface.SurfaceClosedException) {
SessionConfig sessionConfig =
findSessionConfigForSurface(
((DeferrableSurface.SurfaceClosedException) t)
@@ -1033,13 +1031,31 @@
if (sessionConfig != null) {
postSurfaceClosedError(sessionConfig);
}
+ return;
+ }
+
+ // A CancellationException is thrown when (1) A CaptureSession is closed while it
+ // is opening. In this case, another CaptureSession should be opened shortly
+ // after or (2) When opening a CaptureSession fails.
+ // TODO(b/183504720): Distinguish between both scenarios, and communicate the
+ // second one to the developer.
+ if (t instanceof CancellationException) {
+ debugLog("Unable to configure camera cancelled");
+ return;
+ }
+
+ // Only report camera config error if the camera is open. Ignore otherwise.
+ if (mState == InternalState.OPENED) {
+ setState(InternalState.OPENED,
+ CameraState.StateError.create(CameraState.ERROR_STREAM_CONFIG, t));
+ }
+
+ if (t instanceof CameraAccessException) {
+ debugLog("Unable to configure camera due to " + t.getMessage());
} else if (t instanceof TimeoutException) {
// TODO: Consider to handle the timeout error.
Logger.e(TAG, "Unable to configure camera " + mCameraInfoInternal.getCameraId()
+ ", timeout!");
- } else {
- // Throw the unexpected error.
- throw new RuntimeException(t);
}
}
}, mExecutor);
@@ -1270,7 +1286,13 @@
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
@ExecutedBy("mExecutor")
void setState(@NonNull InternalState state) {
- setState(state, /*notifyImmediately=*/true);
+ setState(state, /*stateError=*/null);
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ @ExecutedBy("mExecutor")
+ void setState(@NonNull InternalState state, @Nullable CameraState.StateError stateError) {
+ setState(state, stateError, /*notifyImmediately=*/true);
}
/**
@@ -1283,7 +1305,8 @@
*/
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
@ExecutedBy("mExecutor")
- void setState(@NonNull InternalState state, boolean notifyImmediately) {
+ void setState(@NonNull InternalState state, @Nullable CameraState.StateError stateError,
+ boolean notifyImmediately) {
debugLog("Transitioning camera internal state: " + mState + " --> " + state);
mState = state;
// Convert the internal state to the publicly visible state
@@ -1316,6 +1339,7 @@
}
mCameraStateRegistry.markCameraState(this, publicState, notifyImmediately);
mObservableState.postValue(publicState);
+ mCameraStateMachine.updateState(publicState, stateError);
}
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
@@ -1470,7 +1494,7 @@
// this will wait for the next available camera.
Logger.d(TAG, String.format("Attempt to reopen camera[%s] after error[%s]",
cameraDevice.getId(), getErrorMessage(error)));
- reopenCameraAfterError();
+ reopenCameraAfterError(error);
break;
default:
// TODO: Properly handle other errors. For now, we will close the camera.
@@ -1481,14 +1505,20 @@
+ ": "
+ getErrorMessage(error)
+ " closing camera.");
- setState(InternalState.CLOSING);
+
+ int publicErrorCode =
+ error == CameraDevice.StateCallback.ERROR_CAMERA_DISABLED
+ ? CameraState.ERROR_CAMERA_DISABLED
+ : CameraState.ERROR_CAMERA_FATAL_ERROR;
+ setState(InternalState.CLOSING, CameraState.StateError.create(publicErrorCode));
+
closeCamera(/*abortInFlightCaptures=*/false);
break;
}
}
@ExecutedBy("mExecutor")
- private void reopenCameraAfterError() {
+ private void reopenCameraAfterError(int error) {
// After an error, we must close the current camera device before we can open a new
// one. To accomplish this, we will close the current camera and wait for the
// onClosed() callback to reopen the device. It is also possible that the device can
@@ -1496,7 +1526,21 @@
Preconditions.checkState(mCameraDeviceError != ERROR_NONE,
"Can only reopen camera device after error if the camera device is actually "
+ "in an error state.");
- setState(InternalState.REOPENING);
+
+ int publicErrorCode;
+ switch (error) {
+ case CameraDevice.StateCallback.ERROR_CAMERA_IN_USE:
+ publicErrorCode = CameraState.ERROR_CAMERA_IN_USE;
+ break;
+ case CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE:
+ publicErrorCode = CameraState.ERROR_MAX_CAMERAS_IN_USE;
+ break;
+ default:
+ publicErrorCode = CameraState.ERROR_OTHER_RECOVERABLE_ERROR;
+ break;
+ }
+ setState(InternalState.REOPENING, CameraState.StateError.create(publicErrorCode));
+
closeCamera(/*abortInFlightCaptures=*/false);
}
@@ -1521,7 +1565,9 @@
// Set the state to PENDING_OPEN, so that an attempt to reopen the camera is made if
// it later becomes available to open, but ignore immediate reopen attempt from
// CameraStateRegistry.OnOpenAvailableListener.
- setState(InternalState.PENDING_OPEN, /*notifyImmediately=*/false);
+ setState(InternalState.PENDING_OPEN,
+ /*stateError=*/null,
+ /*notifyImmediately=*/false);
}
}