Merge "Fix SupportedQualitiesVerificationTest" 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 7021369..0e99e30 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
@@ -16,6 +16,8 @@
 
 package androidx.camera.camera2.pipe.integration.impl
 
+import android.media.MediaCodec
+import android.os.Build
 import androidx.annotation.GuardedBy
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.core.Log
@@ -26,8 +28,9 @@
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraConfig
 import androidx.camera.camera2.pipe.integration.interop.Camera2CameraControl
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
-import androidx.camera.core.ImageCapture
 import androidx.camera.core.UseCase
+import androidx.camera.core.impl.DeferrableSurface
+import androidx.camera.core.impl.SessionConfig.ValidatingBuilder
 import javax.inject.Inject
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.joinAll
@@ -259,10 +262,14 @@
 
     @GuardedBy("lock")
     private fun shouldAddRepeatingUseCase(runningUseCases: Set<UseCase>): Boolean {
-        return !attachedUseCases.contains(meteringRepeating) &&
-            runningUseCases.only { it is ImageCapture }
-    }
+        val meteringRepeatingEnabled = attachedUseCases.contains(meteringRepeating)
 
+        val coreLibraryUseCases = runningUseCases.filterNot { it is MeteringRepeating }
+        val onlyVideoCapture = coreLibraryUseCases.onlyVideoCapture()
+        val requireMeteringRepeating = coreLibraryUseCases.requireMeteringRepeating()
+
+        return !meteringRepeatingEnabled && (onlyVideoCapture || requireMeteringRepeating)
+    }
     @GuardedBy("lock")
     private fun addRepeatingUseCase() {
         meteringRepeating.setupSession()
@@ -272,11 +279,13 @@
 
     @GuardedBy("lock")
     private fun shouldRemoveRepeatingUseCase(runningUseCases: Set<UseCase>): Boolean {
-        val onlyMeteringRepeatingEnabled = runningUseCases.only { it is MeteringRepeating }
-        val meteringRepeatingAndNonImageCaptureEnabled =
-            runningUseCases.any { it is MeteringRepeating } &&
-                runningUseCases.any { it !is MeteringRepeating && it !is ImageCapture }
-        return onlyMeteringRepeatingEnabled || meteringRepeatingAndNonImageCaptureEnabled
+        val meteringRepeatingEnabled = runningUseCases.contains(meteringRepeating)
+
+        val coreLibraryUseCases = runningUseCases.filterNot { it is MeteringRepeating }
+        val onlyVideoCapture = coreLibraryUseCases.onlyVideoCapture()
+        val requireMeteringRepeating = coreLibraryUseCases.requireMeteringRepeating()
+
+        return meteringRepeatingEnabled && !onlyVideoCapture && !requireMeteringRepeating
     }
 
     @GuardedBy("lock")
@@ -286,11 +295,31 @@
         meteringRepeating.onDetached()
     }
 
-    /**
-     * Returns true when the collection only has elements (1 or more) that verify the predicate,
-     * false otherwise.
-     */
-    private fun <T> Collection<T>.only(predicate: (T) -> Boolean): Boolean {
-        return isNotEmpty() && all(predicate)
+    private fun Collection<UseCase>.onlyVideoCapture(): Boolean {
+        return isNotEmpty() && checkSurfaces { _, sessionSurfaces ->
+            sessionSurfaces.isNotEmpty() && sessionSurfaces.all {
+                it.containerClass == MediaCodec::class.java
+            }
+        }
+    }
+
+    private fun Collection<UseCase>.requireMeteringRepeating(): Boolean {
+        return isNotEmpty() && checkSurfaces { repeatingSurfaces, sessionSurfaces ->
+            // There is no repeating UseCases
+            sessionSurfaces.isNotEmpty() && repeatingSurfaces.isEmpty()
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+    private fun Collection<UseCase>.checkSurfaces(
+        predicate: (
+            repeatingSurfaces: List<DeferrableSurface>,
+            sessionSurfaces: List<DeferrableSurface>
+        ) -> Boolean
+    ): Boolean = ValidatingBuilder().let { validatingBuilder ->
+        forEach { useCase -> validatingBuilder.add(useCase.sessionConfig) }
+        val sessionConfig = validatingBuilder.build()
+        val captureConfig = sessionConfig.repeatingCaptureConfig
+        return predicate(captureConfig.surfaces, sessionConfig.surfaces)
     }
 }
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 a98af2c..5f4d5d2 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
@@ -17,6 +17,7 @@
 package androidx.camera.camera2.pipe.integration.impl
 
 import android.os.Build
+import android.util.Size
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
@@ -28,12 +29,18 @@
 import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCameraComponentBuilder
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.Preview
+import androidx.camera.core.UseCase
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.testing.SurfaceTextureProvider
+import androidx.camera.testing.fakes.FakeCamera
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.runBlocking
+import org.junit.After
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.annotation.Config
@@ -41,6 +48,8 @@
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 class UseCaseManagerTest {
+    private val useCaseManagerList = mutableListOf<UseCaseManager>()
+    private val useCaseList = mutableListOf<UseCase>()
     private val useCaseThreads by lazy {
         val dispatcher = Dispatchers.Default
         val cameraScope = CoroutineScope(
@@ -55,11 +64,17 @@
         )
     }
 
+    @After
+    fun tearDown() = runBlocking {
+        useCaseManagerList.forEach { it.close() }
+        useCaseList.forEach { it.onDetached() }
+    }
+
     @Test
     fun enabledUseCasesEmpty_whenUseCaseAttachedOnly() {
         // Arrange
         val useCaseManager = createUseCaseManager()
-        val useCase = Preview.Builder().build()
+        val useCase = createPreview()
 
         // Act
         useCaseManager.attach(listOf(useCase))
@@ -73,7 +88,7 @@
     fun enabledUseCasesNotEmpty_whenUseCaseEnabled() {
         // Arrange
         val useCaseManager = createUseCaseManager()
-        val useCase = Preview.Builder().build()
+        val useCase = createPreview()
         useCaseManager.attach(listOf(useCase))
 
         // Act
@@ -88,8 +103,8 @@
     fun meteringRepeatingNotEnabled_whenPreviewEnabled() {
         // Arrange
         val useCaseManager = createUseCaseManager()
-        val preview = Preview.Builder().build()
-        val imageCapture = ImageCapture.Builder().build()
+        val preview = createPreview()
+        val imageCapture = createImageCapture()
         useCaseManager.attach(listOf(preview, imageCapture))
 
         // Act
@@ -105,7 +120,7 @@
     fun meteringRepeatingEnabled_whenOnlyImageCaptureEnabled() {
         // Arrange
         val useCaseManager = createUseCaseManager()
-        val imageCapture = ImageCapture.Builder().build()
+        val imageCapture = createImageCapture()
         useCaseManager.attach(listOf(imageCapture))
 
         // Act
@@ -123,12 +138,12 @@
     fun meteringRepeatingDisabled_whenPreviewBecomesEnabled() {
         // Arrange
         val useCaseManager = createUseCaseManager()
-        val imageCapture = ImageCapture.Builder().build()
+        val imageCapture = createImageCapture()
         useCaseManager.attach(listOf(imageCapture))
         useCaseManager.activate(imageCapture)
 
         // Act
-        val preview = Preview.Builder().build()
+        val preview = createPreview()
         useCaseManager.attach(listOf(preview))
         useCaseManager.activate(preview)
 
@@ -141,8 +156,8 @@
     fun meteringRepeatingEnabled_afterAllUseCasesButImageCaptureDisabled() {
         // Arrange
         val useCaseManager = createUseCaseManager()
-        val preview = Preview.Builder().build()
-        val imageCapture = ImageCapture.Builder().build()
+        val preview = createPreview()
+        val imageCapture = createImageCapture()
         useCaseManager.attach(listOf(preview, imageCapture))
         useCaseManager.activate(preview)
         useCaseManager.activate(imageCapture)
@@ -162,7 +177,7 @@
     fun meteringRepeatingDisabled_whenAllUseCasesDisabled() {
         // Arrange
         val useCaseManager = createUseCaseManager()
-        val imageCapture = ImageCapture.Builder().build()
+        val imageCapture = createImageCapture()
         useCaseManager.attach(listOf(imageCapture))
         useCaseManager.activate(imageCapture)
 
@@ -187,6 +202,36 @@
             ComboRequestListener()
         ),
         cameraStateAdapter = CameraStateAdapter(),
-        displayInfoManager = DisplayInfoManager(ApplicationProvider.getApplicationContext()),
-    )
+        displayInfoManager = DisplayInfoManager(ApplicationProvider.getApplicationContext())
+    ).also {
+        useCaseManagerList.add(it)
+    }
+
+    private fun createImageCapture(): ImageCapture =
+        ImageCapture.Builder()
+            .setCaptureOptionUnpacker { _, _ -> }
+            .setSessionOptionUnpacker() { _, _ -> }
+            .build().also {
+                it.simulateActivation()
+                useCaseList.add(it)
+            }
+
+    private fun createPreview(): Preview =
+        Preview.Builder()
+            .setCaptureOptionUnpacker { _, _ -> }
+            .setSessionOptionUnpacker() { _, _ -> }
+            .build().apply {
+                setSurfaceProvider(
+                    CameraXExecutors.mainThreadExecutor(),
+                    SurfaceTextureProvider.createSurfaceTextureProvider()
+                )
+            }.also {
+                it.simulateActivation()
+                useCaseList.add(it)
+            }
+
+    private fun UseCase.simulateActivation() {
+        onAttach(FakeCamera("0"), null, null)
+        updateSuggestedResolution(Size(640, 480))
+    }
 }