Merge "Add test-only API to shutdown all cameras" into androidx-main
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
index aa924ea..3ac0b9a 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -28,6 +28,7 @@
 import android.os.Build
 import androidx.annotation.GuardedBy
 import androidx.annotation.VisibleForTesting
+import androidx.camera.camera2.pipe.CameraDevices
 import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.CameraGraph.OperatingMode
 import androidx.camera.camera2.pipe.CameraGraph.RepeatingRequestRequirementsBeforeCapture.CompletionBehavior.AT_LEAST
@@ -124,6 +125,7 @@
 @Inject
 constructor(
     private val cameraPipe: CameraPipe,
+    private val cameraDevices: CameraDevices,
     @GuardedBy("lock") private val cameraCoordinator: CameraCoordinator,
     private val callbackMap: CameraCallbackMap,
     private val requestListener: ComboRequestListener,
@@ -352,6 +354,7 @@
                 closingCameraJobs.toList()
             }
         closingJobs.joinAll()
+        cameraDevices.disconnectAll()
     }
 
     override fun toString(): String = "UseCaseManager<${cameraConfig.cameraId}>"
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
index cfc6583..6ef0d69 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
@@ -56,6 +56,8 @@
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraProperties
 import androidx.camera.camera2.pipe.integration.testing.FakeSessionProcessor
 import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCameraComponentBuilder
+import androidx.camera.camera2.pipe.testing.FakeCameraBackend
+import androidx.camera.camera2.pipe.testing.FakeCameraDevices
 import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
 import androidx.camera.core.ImageAnalysis
 import androidx.camera.core.ImageCapture
@@ -677,8 +679,15 @@
             FakeCameraMetadata(cameraId = cameraId, characteristics = characteristicsMap)
         val fakeCamera = FakeCamera()
         val cameraPipe = CameraPipe(CameraPipe.Config(ApplicationProvider.getApplicationContext()))
+        val fakeCameraBackend = FakeCameraBackend(mapOf(cameraId to fakeCameraMetadata))
         return UseCaseManager(
                 cameraPipe = cameraPipe,
+                cameraDevices =
+                    FakeCameraDevices(
+                        fakeCameraBackend.id,
+                        emptySet(),
+                        mapOf(fakeCameraBackend.id to listOf(fakeCameraMetadata))
+                    ),
                 cameraCoordinator = CameraCoordinatorAdapter(cameraPipe, cameraPipe.cameras()),
                 callbackMap = CameraCallbackMap(),
                 requestListener = ComboRequestListener(),
@@ -731,7 +740,7 @@
     private fun createFakePreview(customDeferrableSurface: DeferrableSurface? = null) =
         createFakeTestUseCase(
             "Preview",
-            CameraDevice.TEMPLATE_PREVIEW,
+            TEMPLATE_PREVIEW,
             Preview::class.java,
             customDeferrableSurface,
         )
@@ -747,7 +756,7 @@
     private fun createFakeImageAnalysis(customDeferrableSurface: DeferrableSurface? = null) =
         createFakeTestUseCase(
             "ImageAnalysis",
-            CameraDevice.TEMPLATE_PREVIEW,
+            TEMPLATE_PREVIEW,
             ImageAnalysis::class.java,
             customDeferrableSurface,
         )
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
index 5d5ee33..7b30d01 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
@@ -189,6 +189,9 @@
      */
     public val activeIds: Set<CameraBackendId>
 
+    /** This instructs all backends to each shutdown their respective cameras. */
+    public suspend fun shutdown()
+
     /**
      * Get a previously created [CameraBackend] instance, or create a new one. If the backend fails
      * to load or is not available, this method will return null.
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraPipeComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraPipeComponent.kt
index 6c16c76..e337f8e 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraPipeComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraPipeComponent.kt
@@ -70,6 +70,8 @@
 
     fun cameras(): CameraDevices
 
+    fun cameraBackends(): CameraBackends
+
     fun cameraSurfaceManager(): CameraSurfaceManager
 
     fun cameraAudioRestrictionController(): AudioRestrictionController
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraBackendsImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraBackendsImpl.kt
index c0ddb86..4ac087e 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraBackendsImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraBackendsImpl.kt
@@ -25,6 +25,7 @@
 import androidx.camera.camera2.pipe.CameraContext
 import androidx.camera.camera2.pipe.config.CameraPipeContext
 import androidx.camera.camera2.pipe.core.Threads
+import kotlinx.coroutines.joinAll
 
 /** Provides an implementation for interacting with CameraBackends. */
 internal class CameraBackendsImpl(
@@ -50,6 +51,11 @@
     override val activeIds: Set<CameraBackendId>
         get() = synchronized(lock) { activeCameraBackends.keys }
 
+    override suspend fun shutdown() {
+        val shutdownJobs = activeCameraBackends.map { it.value.shutdownAsync() }
+        shutdownJobs.joinAll()
+    }
+
     override fun get(backendId: CameraBackendId): CameraBackend? {
         synchronized(lock) {
             val existing = activeCameraBackends[backendId]