Do not process state closures if the state is closed.

When the camera state is closed due to an error, it is possible we
update the state of the controller even after it is closed. This makes
it possible for camera controller to attempt a restart if the state was
erroneously updated to ERROR.

This CL makes early returns such that we do not process state closures
if the state is closed, and add a unit test for such cases.

Bug: 380282226
Test: CameraPipe test app.
Change-Id: I96dafdbb6291dfdac415a86b55f257710cb2debf
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt
index 203ca4c..9c2ec94 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt
@@ -259,6 +259,9 @@
     private fun onCameraStatusChanged(cameraStatus: CameraStatus) {
         Log.debug { "$this ($cameraId) camera status changed: $cameraStatus" }
         synchronized(lock) {
+            if (controllerState == ControllerState.CLOSED) {
+                return
+            }
             when (cameraStatus) {
                 is CameraStatus.CameraAvailable -> cameraAvailability = cameraStatus
                 is CameraStatus.CameraUnavailable -> cameraAvailability = cameraStatus
@@ -355,6 +358,9 @@
 
     private fun onStateClosed(cameraState: CameraStateClosed) {
         synchronized(lock) {
+            if (controllerState == ControllerState.CLOSED) {
+                return
+            }
             if (cameraState.cameraErrorCode != null) {
                 lastCameraError = cameraState.cameraErrorCode
                 if (
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/Camera2CameraControllerTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/Camera2CameraControllerTest.kt
index 2c1314b..4b7cf51 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/Camera2CameraControllerTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/Camera2CameraControllerTest.kt
@@ -314,6 +314,27 @@
         }
 
     @Test
+    fun testControllerStateDoesNotChangeAfterClosed() =
+        testScope.runTest {
+            val cameraController = createCamera2CameraController()
+            cameraController.updateSurfaceMap(mapOf(streamId1 to fakeSurface))
+            cameraController.start()
+            testScope.advanceUntilIdle()
+
+            cameraController.close()
+            testScope.advanceUntilIdle()
+            assertEquals(cameraController.controllerState, ControllerState.CLOSED)
+
+            fakeCamera2DeviceManager.simulateCameraError(cameraId, CameraError.ERROR_CAMERA_DEVICE)
+            testScope.advanceUntilIdle()
+            assertEquals(cameraController.controllerState, ControllerState.CLOSED)
+
+            fakeCamera2DeviceManager.simulateCameraError(cameraId, CameraError.ERROR_CAMERA_IN_USE)
+            testScope.advanceUntilIdle()
+            assertEquals(cameraController.controllerState, ControllerState.CLOSED)
+        }
+
+    @Test
     fun testControllerStateErrorWhenNonrecoverableCameraError() =
         testScope.runTest {
             val cameraController = createCamera2CameraController()