Merge "[FPS Range Settings] Improve the power and latency by setting the fps range before creating capture session" into androidx-main
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapter.kt
index 6bd2e52..6fe08bc 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapter.kt
@@ -18,6 +18,7 @@
 
 import android.hardware.camera2.CameraDevice
 import android.media.MediaCodec
+import android.util.Range
 import androidx.annotation.VisibleForTesting
 import androidx.camera.camera2.pipe.OutputStream
 import androidx.camera.camera2.pipe.core.Log
@@ -32,6 +33,7 @@
 import androidx.camera.core.UseCase
 import androidx.camera.core.impl.DeferrableSurface
 import androidx.camera.core.impl.SessionConfig
+import androidx.camera.core.impl.StreamSpec
 import androidx.camera.core.impl.UseCaseConfig
 import androidx.camera.core.streamsharing.StreamSharing
 import kotlinx.coroutines.CoroutineScope
@@ -118,6 +120,15 @@
         }
     }
 
+    fun getExpectedFrameRateRange(): Range<Int>? {
+        return if (
+            isSessionConfigValid() &&
+                sessionConfig.expectedFrameRateRange != StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED
+        )
+            sessionConfig.expectedFrameRateRange
+        else null
+    }
+
     /**
      * Populates the mapping between surfaces of a capture session and the Stream Use Case of their
      * associated stream.
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 b832f7b..0b760d2 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
@@ -874,6 +874,8 @@
                 }
             }
 
+            // Set fps range to capture request
+            val targetFpsRange = sessionConfigAdapter.getExpectedFrameRateRange()
             val defaultParameters =
                 buildMap<Any, Any?> {
                     if (isExtensions) {
@@ -884,7 +886,13 @@
                         CameraPipeKeys.camera2CaptureRequestTag,
                         "android.hardware.camera2.CaptureRequest.setTag.CX"
                     )
+                    targetFpsRange?.let {
+                        set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetFpsRange)
+                    }
                 }
+            targetFpsRange?.let {
+                sessionParameters[CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE] = targetFpsRange
+            }
 
             // TODO: b/327517884 - Add a quirk to not abort captures on stop for certain OEMs during
             //   extension sessions.
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
index a11268a..7bce113 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
@@ -18,7 +18,9 @@
 
 import android.graphics.SurfaceTexture
 import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW
 import android.os.Build
+import android.util.Range
 import android.view.Surface
 import androidx.camera.core.impl.DeferrableSurface
 import androidx.camera.core.impl.SessionConfig
@@ -121,6 +123,34 @@
     }
 
     @Test
+    fun getExpectedFrameRateRange() {
+        // Arrange
+        val testDeferrableSurface = createTestDeferrableSurface()
+
+        // Create an invalid SessionConfig which doesn't set the template
+        val fakeTestUseCase = createFakeTestUseCase {
+            it.setupSessionConfig(
+                SessionConfig.Builder().also { sessionConfigBuilder ->
+                    sessionConfigBuilder.addSurface(testDeferrableSurface)
+                    sessionConfigBuilder.setTemplateType(TEMPLATE_PREVIEW)
+                    sessionConfigBuilder.setExpectedFrameRateRange(Range(15, 24))
+                }
+            )
+        }
+
+        // Act
+        val sessionConfigAdapter = SessionConfigAdapter(useCases = listOf(fakeTestUseCase))
+
+        // Assert
+        assertThat(sessionConfigAdapter.isSessionConfigValid()).isTrue()
+        assertThat(sessionConfigAdapter.getValidSessionConfigOrNull()).isNotNull()
+        assertThat(sessionConfigAdapter.getExpectedFrameRateRange()).isEqualTo(Range(15, 24))
+
+        // Clean up
+        testDeferrableSurface.close()
+    }
+
+    @Test
     fun populateSurfaceToStreamUseCaseMappingEmptyUseCase() {
         val mapping = sessionConfigAdapter.getSurfaceToStreamUseCaseMapping(listOf(), listOf())
         TestCase.assertTrue(mapping.isEmpty())
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 89ed085..0e24668 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
@@ -19,11 +19,14 @@
 import android.content.Context
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW
 import android.hardware.camera2.CameraDevice.TEMPLATE_RECORD
 import android.hardware.camera2.CameraMetadata.CONTROL_CAPTURE_INTENT_PREVIEW
+import android.hardware.camera2.CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE
 import android.hardware.camera2.CaptureRequest.CONTROL_CAPTURE_INTENT
 import android.hardware.camera2.params.SessionConfiguration.SESSION_HIGH_SPEED
 import android.os.Build
+import android.util.Range
 import android.util.Size
 import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.CameraGraph.OperatingMode.Companion.HIGH_SPEED
@@ -523,6 +526,40 @@
     }
 
     @Test
+    fun createCameraGraphConfig_setTargetFpsRange() = runTest {
+        // Arrange
+        initializeUseCaseThreads(this)
+        val useCaseManager = createUseCaseManager()
+        val fakeUseCase =
+            FakeUseCase().apply {
+                updateSessionConfigForTesting(
+                    SessionConfig.Builder()
+                        .setTemplateType(TEMPLATE_PREVIEW)
+                        .setExpectedFrameRateRange(Range(15, 24))
+                        .build()
+                )
+            }
+        val sessionConfigAdapter = SessionConfigAdapter(setOf(fakeUseCase))
+        val streamConfigMap = mutableMapOf<CameraStream.Config, DeferrableSurface>()
+
+        // Act
+        val graphConfig =
+            useCaseManager.createCameraGraphConfig(
+                sessionConfigAdapter,
+                streamConfigMap,
+            )
+
+        // Assert
+        assertThat(graphConfig.sessionTemplate).isEqualTo(RequestTemplate(TEMPLATE_PREVIEW))
+        assertThat(graphConfig.sessionParameters).containsKey(CONTROL_AE_TARGET_FPS_RANGE)
+        assertThat(graphConfig.sessionParameters[CONTROL_AE_TARGET_FPS_RANGE])
+            .isEqualTo(Range(15, 24))
+        assertThat(graphConfig.defaultParameters).containsKey(CONTROL_AE_TARGET_FPS_RANGE)
+        assertThat(graphConfig.defaultParameters[CONTROL_AE_TARGET_FPS_RANGE])
+            .isEqualTo(Range(15, 24))
+    }
+
+    @Test
     fun overrideTemplateParams() = runTest {
         // Arrange
         initializeUseCaseThreads(this)
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java
index 70decc2..08ccc13 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java
@@ -237,6 +237,8 @@
         applyTemplateParamsOverrideWorkaround(builder, captureConfig.getTemplateType(),
                 templateParamsOverride);
 
+        applyAeFpsRange(captureConfig, builder);
+
         applyImplementationOptionToCaptureBuilder(builder,
                 captureConfig.getImplementationOptions());
 
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CaptureOptionSubmissionTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CaptureOptionSubmissionTest.kt
index f45d709..73dfc42 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CaptureOptionSubmissionTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CaptureOptionSubmissionTest.kt
@@ -141,11 +141,6 @@
     @Test
     fun canSubmitSupportedAeTargetFpsRanges_whenTargetFrameRateSetToPreviewOnly() = runBlocking {
         assumeTrue(
-            "TODO(b/331900702): Enable when the bug is fixed at camera-pipe",
-            implName != CameraPipeConfig::class.simpleName
-        )
-
-        assumeTrue(
             "TODO(b/332235883): Enable for legacy when the bug is resolved",
             !isHwLevelLegacy()
         )
@@ -184,11 +179,6 @@
     fun canSubmitSupportedAeTargetFpsRanges_whenTargetFrameRateSetToVideoCaptureOnly() =
         runBlocking {
             assumeTrue(
-                "TODO(b/331900702): Enable when the bug is fixed at camera-pipe",
-                implName != CameraPipeConfig::class.simpleName
-            )
-
-            assumeTrue(
                 "TODO(b/332235883): Enable for legacy when the bug is resolved",
                 !isHwLevelLegacy()
             )
@@ -233,11 +223,6 @@
     @Test
     fun canSetAeTargetFpsRangeWithCamera2Interop() = runBlocking {
         assumeTrue(
-            "TODO(b/331900702): Enable when the bug is fixed at camera-pipe",
-            implName != CameraPipeConfig::class.simpleName
-        )
-
-        assumeTrue(
             "TODO(b/332235883): Enable for legacy when the bug is resolved",
             !isHwLevelLegacy()
         )
@@ -292,11 +277,6 @@
     @Test
     fun canOverwriteFpsRangeWithCamera2Interop_whenAnotherSetViaSetTargetFrameRate() = runBlocking {
         assumeTrue(
-            "TODO(b/331900702): Enable when the bug is fixed at camera-pipe",
-            implName != CameraPipeConfig::class.simpleName
-        )
-
-        assumeTrue(
             "TODO(b/332235883): Enable for legacy when the bug is resolved",
             !isHwLevelLegacy()
         )