Add CaptureIntentPreviewQuirk and TemplateParamsOverride workaround in camera layer

 This quirk is used to replace the workaround which changes UseCase's default template type.

 For detail, see go/camerax-video-quality-enhancement-for-template-preview

Bug: 340385870
Test: ./gradlew camera:camera-camera2:testDebug; ./gradlew camera:camera-camera2-pipe-integration:testDebug
Change-Id: I4aac0800162d4e6d0bfa533fe487712f6d24a61e
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
index 97a2497..70dba7c 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
@@ -31,6 +31,7 @@
 import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
 import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
 import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpInactiveSurfaceCloser
+import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpTemplateParamsOverride
 import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
 import androidx.camera.camera2.pipe.integration.config.CameraConfig
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraConfig
@@ -101,7 +102,8 @@
                 cameraConfig,
                 cameraQuirks,
                 null,
-                ZslControlNoOpImpl()
+                ZslControlNoOpImpl(),
+                NoOpTemplateParamsOverride,
             )
         val cameraGraph = cameraPipe.create(cameraGraphConfig)
 
@@ -141,7 +143,8 @@
                     UseCaseCameraState(
                         useCaseCameraGraphConfig,
                         threads,
-                        sessionProcessorManager = null
+                        sessionProcessorManager = null,
+                        templateParamsOverride = NoOpTemplateParamsOverride,
                     ),
                 useCaseGraphConfig = useCaseCameraGraphConfig,
             )
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/CameraCompatModule.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/CameraCompatModule.kt
index 6472c76..157343e 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/CameraCompatModule.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/CameraCompatModule.kt
@@ -19,6 +19,7 @@
 import androidx.camera.camera2.pipe.integration.compat.workaround.AutoFlashAEModeDisabler
 import androidx.camera.camera2.pipe.integration.compat.workaround.InactiveSurfaceCloser
 import androidx.camera.camera2.pipe.integration.compat.workaround.MeteringRegionCorrection
+import androidx.camera.camera2.pipe.integration.compat.workaround.TemplateParamsOverride
 import androidx.camera.camera2.pipe.integration.compat.workaround.UseFlashModeTorchFor3aUpdate
 import androidx.camera.camera2.pipe.integration.compat.workaround.UseTorchAsFlash
 import dagger.Module
@@ -32,6 +33,7 @@
             MeteringRegionCorrection.Bindings::class,
             UseFlashModeTorchFor3aUpdate.Bindings::class,
             UseTorchAsFlash.Bindings::class,
+            TemplateParamsOverride.Bindings::class,
         ],
 )
 abstract class CameraCompatModule
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CaptureIntentPreviewQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CaptureIntentPreviewQuirk.kt
new file mode 100644
index 0000000..9bf8a0f
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CaptureIntentPreviewQuirk.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2024 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.pipe.integration.compat.quirk
+
+import android.hardware.camera2.CaptureRequest
+import androidx.camera.core.impl.Quirk
+import androidx.camera.core.impl.Quirks
+
+/**
+ * A Quirk interface denotes devices have specific issue and can be workaround by using
+ * [CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW] to replace
+ * [CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD].
+ * - Subclasses of this quirk may contain device specific information.
+ */
+interface CaptureIntentPreviewQuirk : Quirk {
+    /**
+     * Returns if the device specific issue can be workaround by using
+     * [CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW] to replace
+     * [CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD].
+     */
+    fun workaroundByCaptureIntentPreview() = true
+
+    companion object {
+        /**
+         * Returns if input quirks contains at least one [CaptureIntentPreviewQuirk] which
+         * [CaptureIntentPreviewQuirk.workaroundByCaptureIntentPreview] is true.
+         */
+        fun workaroundByCaptureIntentPreview(quirks: Quirks): Boolean {
+            for (quirk in quirks.getAll(CaptureIntentPreviewQuirk::class.java)) {
+                if (quirk.workaroundByCaptureIntentPreview()) {
+                    return true
+                }
+            }
+            return false
+        }
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/TemplateParamsOverride.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/TemplateParamsOverride.kt
new file mode 100644
index 0000000..c7783a7
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/TemplateParamsOverride.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2024 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.pipe.integration.compat.workaround
+
+import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraMetadata.CONTROL_CAPTURE_INTENT_PREVIEW
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureRequest.CONTROL_CAPTURE_INTENT
+import androidx.camera.camera2.pipe.RequestTemplate
+import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
+import androidx.camera.camera2.pipe.integration.compat.quirk.CaptureIntentPreviewQuirk.Companion.workaroundByCaptureIntentPreview
+import dagger.Module
+import dagger.Provides
+
+/**
+ * Workaround to get those capture parameters used to override the template default parameters.
+ * - This workaround should only be applied on repeating request but not on single request.
+ */
+interface TemplateParamsOverride {
+    /** Returns capture parameters used to override the default parameters of the input template. */
+    fun getOverrideParams(template: RequestTemplate?): Map<CaptureRequest.Key<*>, Any>
+
+    @Module
+    abstract class Bindings {
+        companion object {
+            @Provides
+            fun provideTemplateParamsOverride(quirks: CameraQuirks): TemplateParamsOverride {
+                return if (workaroundByCaptureIntentPreview(quirks.quirks))
+                    TemplateParamsQuirkOverride
+                else NoOpTemplateParamsOverride
+            }
+        }
+    }
+}
+
+object TemplateParamsQuirkOverride : TemplateParamsOverride {
+    override fun getOverrideParams(template: RequestTemplate?): Map<CaptureRequest.Key<*>, Any> {
+        if (template?.value == CameraDevice.TEMPLATE_RECORD) {
+            return mapOf(CONTROL_CAPTURE_INTENT to CONTROL_CAPTURE_INTENT_PREVIEW)
+        }
+        return emptyMap()
+    }
+}
+
+object NoOpTemplateParamsOverride : TemplateParamsOverride {
+    override fun getOverrideParams(template: RequestTemplate?): Map<CaptureRequest.Key<*>, Any> {
+        return emptyMap()
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
index e5552c8..a508301 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
@@ -33,6 +33,7 @@
 import androidx.camera.camera2.pipe.RequestTemplate
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.core.Log.debug
+import androidx.camera.camera2.pipe.integration.compat.workaround.TemplateParamsOverride
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
 import androidx.camera.core.Preview
@@ -64,6 +65,7 @@
     useCaseGraphConfig: UseCaseGraphConfig,
     private val threads: UseCaseThreads,
     private val sessionProcessorManager: SessionProcessorManager?,
+    private val templateParamsOverride: TemplateParamsOverride,
 ) {
     private val lock = Any()
 
@@ -276,7 +278,9 @@
                                 Request(
                                     template = currentTemplate,
                                     streams = currentStreams.toList(),
-                                    parameters = currentParameters.toMap(),
+                                    parameters =
+                                        templateParamsOverride.getOverrideParams(currentTemplate) +
+                                            currentParameters.toMap(),
                                     extras =
                                         currentInternalParameters.toMutableMap().also { parameters
                                             ->
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 93319bd..1efebfd 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
@@ -49,6 +49,7 @@
 import androidx.camera.camera2.pipe.integration.compat.quirk.CloseCaptureSessionOnVideoQuirk
 import androidx.camera.camera2.pipe.integration.compat.quirk.DeviceQuirks
 import androidx.camera.camera2.pipe.integration.compat.quirk.DisableAbortCapturesOnStopWithSessionProcessorQuirk
+import androidx.camera.camera2.pipe.integration.compat.workaround.TemplateParamsOverride
 import androidx.camera.camera2.pipe.integration.config.CameraConfig
 import androidx.camera.camera2.pipe.integration.config.CameraScope
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraComponent
@@ -124,6 +125,7 @@
     private val cameraInternal: Provider<CameraInternal>,
     private val useCaseThreads: Provider<UseCaseThreads>,
     private val cameraInfoInternal: Provider<CameraInfoInternal>,
+    private val templateParamsOverride: TemplateParamsOverride,
     context: Context,
     cameraProperties: CameraProperties,
     displayInfoManager: DisplayInfoManager,
@@ -588,6 +590,7 @@
             cameraQuirks,
             cameraGraphFlags,
             zslControl,
+            templateParamsOverride,
             isExtensions,
         )
     }
@@ -698,6 +701,7 @@
             cameraQuirks: CameraQuirks,
             cameraGraphFlags: CameraGraph.Flags?,
             zslControl: ZslControl,
+            templateParamsOverride: TemplateParamsOverride,
             isExtensions: Boolean = false,
         ): CameraGraph.Config {
             var containsVideo = false
@@ -717,6 +721,7 @@
                 if (sessionConfig.templateType != CaptureConfig.TEMPLATE_TYPE_NONE) {
                     sessionTemplate = RequestTemplate(sessionConfig.templateType)
                 }
+                sessionParameters.putAll(templateParamsOverride.getOverrideParams(sessionTemplate))
                 sessionParameters.putAll(sessionConfig.implementationOptions.toParameters())
 
                 val physicalCameraIdForAllStreams =
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/TemplateParamsOverrideTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/TemplateParamsOverrideTest.kt
new file mode 100644
index 0000000..0fdd71e
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/TemplateParamsOverrideTest.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2024 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.pipe.integration.compat.workaround
+
+import android.hardware.camera2.CameraDevice.TEMPLATE_MANUAL
+import android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW
+import android.hardware.camera2.CameraDevice.TEMPLATE_RECORD
+import android.hardware.camera2.CameraDevice.TEMPLATE_STILL_CAPTURE
+import android.hardware.camera2.CameraDevice.TEMPLATE_VIDEO_SNAPSHOT
+import android.hardware.camera2.CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG
+import android.hardware.camera2.CameraMetadata.CONTROL_CAPTURE_INTENT_PREVIEW
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureRequest.CONTROL_CAPTURE_INTENT
+import androidx.camera.camera2.pipe.RequestTemplate
+import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
+import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
+import androidx.camera.camera2.pipe.integration.compat.quirk.CaptureIntentPreviewQuirk
+import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
+import androidx.camera.core.impl.Quirk
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.ParameterizedRobolectricTestRunner
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadows.StreamConfigurationMapBuilder
+
+/** Unit test for [TemplateParamsOverride]. */
+@RunWith(ParameterizedRobolectricTestRunner::class)
+@DoNotInstrument
+class TemplateParamsOverrideTest(
+    private val testName: String,
+    private val quirk: Quirk?,
+    private val expectedParamsMap: Map<Int, Map<CaptureRequest.Key<*>, Any>>,
+) {
+    companion object {
+        private val emptyParamsMap = emptyMap<CaptureRequest.Key<*>, Any>()
+        private val paramsMapForCaptureIntentPreviewQuirk =
+            mapOf(
+                TEMPLATE_RECORD to mapOf(CONTROL_CAPTURE_INTENT to CONTROL_CAPTURE_INTENT_PREVIEW)
+            )
+
+        @JvmStatic
+        @ParameterizedRobolectricTestRunner.Parameters(name = "testName={0}")
+        fun data() =
+            mutableListOf<Array<Any?>>().apply {
+                add(
+                    arrayOf(
+                        "no quirk",
+                        null,
+                        emptyParamsMap,
+                    )
+                )
+                add(
+                    arrayOf(
+                        "CaptureIntentPreviewQuirk with false workaround flag",
+                        object : CaptureIntentPreviewQuirk {
+                            override fun workaroundByCaptureIntentPreview() = false
+                        },
+                        emptyParamsMap,
+                    )
+                )
+                add(
+                    arrayOf(
+                        "CaptureIntentPreviewQuirk with true workaround flag",
+                        object : CaptureIntentPreviewQuirk {
+                            override fun workaroundByCaptureIntentPreview() = true
+                        },
+                        paramsMapForCaptureIntentPreviewQuirk,
+                    )
+                )
+            }
+    }
+
+    private lateinit var cameraQuirks: CameraQuirks
+
+    @Before
+    fun setup() {
+        cameraQuirks =
+            CameraQuirks(
+                FakeCameraMetadata(),
+                StreamConfigurationMapCompat(
+                    StreamConfigurationMapBuilder.newBuilder().build(),
+                    OutputSizesCorrector(
+                        FakeCameraMetadata(),
+                        StreamConfigurationMapBuilder.newBuilder().build()
+                    )
+                )
+            )
+        if (quirk != null) {
+            cameraQuirks.quirks.addQuirkForTesting(quirk)
+        }
+    }
+
+    @Test
+    fun getOverrideParams() {
+        for (template in
+            listOf(
+                TEMPLATE_PREVIEW,
+                TEMPLATE_RECORD,
+                TEMPLATE_STILL_CAPTURE,
+                TEMPLATE_MANUAL,
+                TEMPLATE_VIDEO_SNAPSHOT,
+                TEMPLATE_ZERO_SHUTTER_LAG
+            )) {
+            val templateParamsOverride =
+                TemplateParamsOverride_Bindings_Companion_ProvideTemplateParamsOverrideFactory
+                    .provideTemplateParamsOverride(cameraQuirks)
+            val params = templateParamsOverride.getOverrideParams(RequestTemplate(template))
+            val expectedParams = expectedParamsMap[template] ?: emptyParamsMap
+            assertWithMessage("getOverrideParams with template: $template")
+                .that(params)
+                .isEqualTo(expectedParams)
+        }
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
index e4bc387..6991cef 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
@@ -49,6 +49,7 @@
 import androidx.camera.camera2.pipe.integration.compat.workaround.AeFpsRange
 import androidx.camera.camera2.pipe.integration.compat.workaround.CapturePipelineTorchCorrection
 import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpAutoFlashAEModeDisabler
+import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpTemplateParamsOverride
 import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseFlashModeTorchFor3aUpdate
 import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseTorchAsFlash
 import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
@@ -355,6 +356,7 @@
                 fakeUseCaseGraphConfig,
                 fakeUseCaseThreads,
                 sessionProcessorManager = null,
+                templateParamsOverride = NoOpTemplateParamsOverride,
             )
 
         capturePipeline =
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestTest.kt
index 6102b91..e9e7a29 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestTest.kt
@@ -23,6 +23,7 @@
 import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
 import androidx.camera.camera2.pipe.integration.adapter.ZslControlNoOpImpl
+import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpTemplateParamsOverride
 import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseFlashModeTorchFor3aUpdate
 import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseTorchAsFlash
 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
@@ -420,6 +421,7 @@
                 useCaseGraphConfig = fakeUseCaseGraphConfig,
                 threads = fakeUseCaseThreads,
                 sessionProcessorManager = null,
+                templateParamsOverride = NoOpTemplateParamsOverride,
             )
         val torchControl =
             TorchControl(fakeCameraProperties, fakeState3AControl, fakeUseCaseThreads)
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt
index 3ffa532..a29dfa3 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt
@@ -27,6 +27,7 @@
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
+import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpTemplateParamsOverride
 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraph
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraProperties
@@ -79,6 +80,7 @@
             useCaseGraphConfig = fakeUseCaseGraphConfig,
             threads = useCaseThreads,
             sessionProcessorManager = null,
+            templateParamsOverride = NoOpTemplateParamsOverride,
         )
     private val requestControl =
         UseCaseCameraRequestControlImpl(
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraStateTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraStateTest.kt
index 5f9c231..fc87955 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraStateTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraStateTest.kt
@@ -16,11 +16,17 @@
 
 package androidx.camera.camera2.pipe.integration.impl
 
+import android.hardware.camera2.CameraDevice.TEMPLATE_RECORD
+import android.hardware.camera2.CaptureRequest.CONTROL_CAPTURE_INTENT
+import android.hardware.camera2.CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW
 import android.os.Build
+import androidx.camera.camera2.pipe.RequestTemplate
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
 import androidx.camera.camera2.pipe.integration.adapter.asListenableFuture
+import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpTemplateParamsOverride
+import androidx.camera.camera2.pipe.integration.compat.workaround.TemplateParamsQuirkOverride
 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraph
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraphSession
@@ -30,6 +36,7 @@
 import androidx.camera.camera2.pipe.integration.testing.FakeSurface
 import androidx.camera.core.impl.DeferrableSurface
 import androidx.testutils.MainDispatcherRule
+import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import com.google.common.util.concurrent.ListenableFuture
 import java.util.concurrent.ExecutionException
@@ -81,6 +88,7 @@
             useCaseGraphConfig = fakeUseCaseGraphConfig,
             threads = useCaseThreads,
             sessionProcessorManager = null,
+            templateParamsOverride = NoOpTemplateParamsOverride,
         )
 
     @Before
@@ -189,6 +197,37 @@
         assertFutureCompletes(result)
     }
 
+    @Test
+    fun updateAsync_overrideTemplateParams(): Unit = runBlocking {
+        val useCaseCameraState =
+            UseCaseCameraState(
+                useCaseGraphConfig = fakeUseCaseGraphConfig,
+                threads = useCaseThreads,
+                sessionProcessorManager = null,
+                templateParamsOverride = TemplateParamsQuirkOverride,
+            )
+
+        // startRepeating is called when there is at least one stream after updateAsync call
+        val template = RequestTemplate(TEMPLATE_RECORD)
+        val result =
+            useCaseCameraState
+                .updateAsync(
+                    streams = setOf(StreamId(0)),
+                    template = template,
+                )
+                .asListenableFuture()
+
+        // simulate startRepeating request being completed in camera
+        fakeCameraGraphSession.startRepeatingSignal.complete(TOTAL_CAPTURE_DONE)
+
+        assertFutureCompletes(result)
+
+        assertThat(fakeCameraGraphSession.repeatingRequests.size).isEqualTo(1)
+        val request = fakeCameraGraphSession.repeatingRequests[0]
+        assertThat(request.template).isEqualTo(template)
+        assertThat(request[CONTROL_CAPTURE_INTENT]).isEqualTo(CONTROL_CAPTURE_INTENT_PREVIEW)
+    }
+
     private fun <T> assertFutureCompletes(future: ListenableFuture<T>) {
         future[3, TimeUnit.SECONDS]
     }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt
index 3bfb852..df4439a 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt
@@ -24,6 +24,7 @@
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
 import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
 import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpInactiveSurfaceCloser
+import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpTemplateParamsOverride
 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraph
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraProperties
@@ -72,6 +73,7 @@
             useCaseGraphConfig = fakeUseCaseGraphConfig,
             threads = useCaseThreads,
             sessionProcessorManager = null,
+            templateParamsOverride = NoOpTemplateParamsOverride,
         )
     private val requestControl =
         UseCaseCameraRequestControlImpl(
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 ecaf6bc..f13500f 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
@@ -41,7 +41,10 @@
 import androidx.camera.camera2.pipe.integration.adapter.ZslControlNoOpImpl
 import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
 import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
+import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpTemplateParamsOverride
 import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
+import androidx.camera.camera2.pipe.integration.compat.workaround.TemplateParamsOverride
+import androidx.camera.camera2.pipe.integration.compat.workaround.TemplateParamsQuirkOverride
 import androidx.camera.camera2.pipe.integration.config.CameraConfig
 import androidx.camera.camera2.pipe.integration.impl.UseCaseCamera.RunningUseCasesChangeListener
 import androidx.camera.camera2.pipe.integration.interop.Camera2CameraControl
@@ -517,12 +520,40 @@
             .isEqualTo(mapOf(CONTROL_CAPTURE_INTENT to CONTROL_CAPTURE_INTENT_PREVIEW))
     }
 
+    @Test
+    fun overrideTemplateParams() = runTest {
+        // Arrange
+        initializeUseCaseThreads(this)
+        val useCaseManager =
+            createUseCaseManager(templateParamsOverride = TemplateParamsQuirkOverride)
+        val fakeUseCase =
+            FakeUseCase().apply {
+                updateSessionConfigForTesting(
+                    SessionConfig.Builder().setTemplateType(TEMPLATE_RECORD).build()
+                )
+            }
+        val sessionConfigAdapter = SessionConfigAdapter(setOf(fakeUseCase))
+        val streamConfigMap = mutableMapOf<CameraStream.Config, DeferrableSurface>()
+
+        // Act.
+        val cameraGraphConfig =
+            useCaseManager.createCameraGraphConfig(
+                sessionConfigAdapter,
+                streamConfigMap,
+            )
+
+        // Assert
+        assertThat(cameraGraphConfig.sessionParameters[CONTROL_CAPTURE_INTENT])
+            .isEqualTo(CONTROL_CAPTURE_INTENT_PREVIEW)
+    }
+
     @OptIn(ExperimentalCamera2Interop::class)
     @Suppress("UNCHECKED_CAST", "PLATFORM_CLASS_MAPPED_TO_KOTLIN")
     private fun createUseCaseManager(
         controls: Set<UseCaseCameraControl> = emptySet(),
         useCaseCameraComponentBuilder: FakeUseCaseCameraComponentBuilder =
             FakeUseCaseCameraComponentBuilder(),
+        templateParamsOverride: TemplateParamsOverride = NoOpTemplateParamsOverride,
     ): UseCaseManager {
         val cameraId = CameraId("0")
         val characteristicsMap: Map<CameraCharacteristics.Key<*>, Any?> =
@@ -576,6 +607,7 @@
                     DisplayInfoManager(ApplicationProvider.getApplicationContext()),
                 context = ApplicationProvider.getApplicationContext(),
                 cameraInfoInternal = { fakeCamera.cameraInfoInternal },
+                templateParamsOverride = templateParamsOverride,
                 useCaseThreads = { useCaseThreads },
             )
             .also { useCaseManagerList.add(it) }
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 592ad30..4d36f185 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
@@ -29,6 +29,7 @@
 import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
+import androidx.camera.camera2.internal.compat.workaround.TemplateParamsOverride;
 import androidx.camera.camera2.interop.CaptureRequestOptions;
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.Logger;
@@ -77,6 +78,17 @@
         return surfaceList;
     }
 
+    private static void applyTemplateParamsOverrideWorkaround(
+            @NonNull CaptureRequest.Builder builder, int template,
+            @NonNull TemplateParamsOverride templateParamsOverride) {
+        for (Map.Entry<CaptureRequest.Key<?>, Object> entry :
+                templateParamsOverride.getOverrideParams(template).entrySet()) {
+            @SuppressWarnings("unchecked")
+            CaptureRequest.Key<Object> key = (CaptureRequest.Key<Object>) entry.getKey();
+            builder.set(key, entry.getValue());
+        }
+    }
+
     @OptIn(markerClass = ExperimentalCamera2Interop.class)
     private static void applyImplementationOptionToCaptureBuilder(
             CaptureRequest.Builder builder, Config config) {
@@ -137,7 +149,7 @@
     public static CaptureRequest build(@NonNull CaptureConfig captureConfig,
             @Nullable CameraDevice device,
             @NonNull Map<DeferrableSurface, Surface> configuredSurfaceMap,
-            boolean isRepeatingRequest)
+            boolean isRepeatingRequest, @NonNull TemplateParamsOverride mTemplateParamsOverride)
             throws CameraAccessException {
         if (device == null) {
             return null;
@@ -170,6 +182,11 @@
             }
         }
 
+        if (isRepeatingRequest) {
+            applyTemplateParamsOverrideWorkaround(builder, captureConfig.getTemplateType(),
+                    mTemplateParamsOverride);
+        }
+
         applyAeFpsRange(captureConfig, builder);
 
         applyVideoStabilization(captureConfig, builder);
@@ -211,7 +228,7 @@
      */
     @Nullable
     public static CaptureRequest buildWithoutTarget(@NonNull CaptureConfig captureConfig,
-            @Nullable CameraDevice device)
+            @Nullable CameraDevice device, @NonNull TemplateParamsOverride templateParamsOverride)
             throws CameraAccessException {
         if (device == null) {
             return null;
@@ -219,6 +236,9 @@
         CaptureRequest.Builder builder = device.createCaptureRequest(
                 captureConfig.getTemplateType());
 
+        applyTemplateParamsOverrideWorkaround(builder, captureConfig.getTemplateType(),
+                templateParamsOverride);
+
         applyImplementationOptionToCaptureBuilder(builder,
                 captureConfig.getImplementationOptions());
 
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
index 9c5cb20..e785b48 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
@@ -40,6 +40,7 @@
 import androidx.camera.camera2.internal.compat.quirk.CaptureNoResponseQuirk;
 import androidx.camera.camera2.internal.compat.workaround.RequestMonitor;
 import androidx.camera.camera2.internal.compat.workaround.StillCaptureFlow;
+import androidx.camera.camera2.internal.compat.workaround.TemplateParamsOverride;
 import androidx.camera.camera2.internal.compat.workaround.TorchStateReset;
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.DynamicRange;
@@ -121,6 +122,7 @@
     private final TorchStateReset mTorchStateReset = new TorchStateReset();
     private final RequestMonitor mRequestMonitor;
     private final DynamicRangesCompat mDynamicRangesCompat;
+    private final TemplateParamsOverride mTemplateParamsOverride;
 
     /**
      * Constructor for CaptureSession without CameraQuirk.
@@ -139,6 +141,7 @@
         mCaptureSessionStateCallback = new StateCallback();
         mRequestMonitor = new RequestMonitor(
                 cameraQuirks != null && cameraQuirks.contains(CaptureNoResponseQuirk.class));
+        mTemplateParamsOverride = new TemplateParamsOverride(cameraQuirks);
     }
 
     @Override
@@ -336,7 +339,8 @@
                     try {
                         CaptureRequest captureRequest =
                                 Camera2CaptureRequestBuilder.buildWithoutTarget(
-                                        sessionParameterConfigBuilder.build(), cameraDevice);
+                                        sessionParameterConfigBuilder.build(), cameraDevice,
+                                        mTemplateParamsOverride);
                         if (captureRequest != null) {
                             sessionConfigCompat.setSessionParameters(captureRequest);
                         }
@@ -647,7 +651,7 @@
                 Logger.d(TAG, "Issuing request for session.");
                 CaptureRequest captureRequest = Camera2CaptureRequestBuilder.build(
                         captureConfig, mSynchronizedCaptureSession.getDevice(),
-                        mConfiguredSurfaceMap, true);
+                        mConfiguredSurfaceMap, true, mTemplateParamsOverride);
                 if (captureRequest == null) {
                     Logger.d(TAG, "Skipping issuing empty request for session.");
                     return -1;
@@ -757,7 +761,7 @@
 
                     CaptureRequest captureRequest = Camera2CaptureRequestBuilder.build(
                             captureConfigBuilder.build(), mSynchronizedCaptureSession.getDevice(),
-                            mConfiguredSurfaceMap, false);
+                            mConfiguredSurfaceMap, false, mTemplateParamsOverride);
                     if (captureRequest == null) {
                         Logger.d(TAG, "Skipping issuing request without surface.");
                         return -1;
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CaptureIntentPreviewQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CaptureIntentPreviewQuirk.java
new file mode 100644
index 0000000..a8865c3
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CaptureIntentPreviewQuirk.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 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.compat.quirk;
+
+import android.hardware.camera2.CaptureRequest;
+
+import androidx.annotation.NonNull;
+import androidx.camera.core.impl.Quirk;
+import androidx.camera.core.impl.Quirks;
+
+/**
+ * A Quirk interface denotes devices have specific issue and can be workaround by using
+ * {@link CaptureRequest#CONTROL_CAPTURE_INTENT_PREVIEW} to replace
+ * {@link CaptureRequest#CONTROL_CAPTURE_INTENT_VIDEO_RECORD}.
+ *
+ * <p>Subclasses of this quirk may contain device specific information.
+ */
+public interface CaptureIntentPreviewQuirk extends Quirk {
+    /**
+     * Returns if the device specific issue can be workaround by using
+     * {@link CaptureRequest#CONTROL_CAPTURE_INTENT_PREVIEW} to replace
+     * {@link CaptureRequest#CONTROL_CAPTURE_INTENT_VIDEO_RECORD}.
+     */
+    default boolean workaroundByCaptureIntentPreview() {
+        return true;
+    }
+
+    /**
+     * Returns if input quirks contains at least one {@link CaptureIntentPreviewQuirk} which
+     * {@link CaptureIntentPreviewQuirk#workaroundByCaptureIntentPreview()} is true.
+     */
+    static boolean workaroundByCaptureIntentPreview(@NonNull Quirks quirks) {
+        for (CaptureIntentPreviewQuirk quirk : quirks.getAll(CaptureIntentPreviewQuirk.class)) {
+            if (quirk.workaroundByCaptureIntentPreview()) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/TemplateParamsOverride.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/TemplateParamsOverride.java
new file mode 100644
index 0000000..ce64101
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/TemplateParamsOverride.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2024 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.compat.workaround;
+
+import static android.hardware.camera2.CameraDevice.TEMPLATE_RECORD;
+import static android.hardware.camera2.CameraMetadata.CONTROL_CAPTURE_INTENT_PREVIEW;
+import static android.hardware.camera2.CaptureRequest.CONTROL_CAPTURE_INTENT;
+
+import static androidx.camera.camera2.internal.compat.quirk.CaptureIntentPreviewQuirk.workaroundByCaptureIntentPreview;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.unmodifiableMap;
+
+import android.hardware.camera2.CaptureRequest;
+
+import androidx.annotation.NonNull;
+import androidx.camera.core.impl.Quirks;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Workaround to get those capture parameters used to override the template default parameters.
+ *
+ * <p>This workaround should only be applied on repeating request but not on single request.
+ */
+public class TemplateParamsOverride {
+    private final Quirks mQuirks;
+
+    public TemplateParamsOverride(@NonNull Quirks quirks) {
+        mQuirks = quirks;
+    }
+
+    /**
+     * Returns capture parameters used to override the default parameters of the input template.
+     */
+    @NonNull
+    public Map<CaptureRequest.Key<?>, Object> getOverrideParams(int template) {
+        if (template == TEMPLATE_RECORD && workaroundByCaptureIntentPreview(mQuirks)) {
+            Map<CaptureRequest.Key<?>, Object> params = new HashMap<>();
+            params.put(CONTROL_CAPTURE_INTENT, CONTROL_CAPTURE_INTENT_PREVIEW);
+            return unmodifiableMap(params);
+        }
+        return emptyMap();
+    }
+}
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilderTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilderTest.java
index 6f7d947..5fb8fd8 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilderTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilderTest.java
@@ -18,14 +18,16 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static java.util.Collections.emptyList;
+
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
 import android.os.Build;
-import android.view.Surface;
 
+import androidx.camera.camera2.internal.compat.workaround.TemplateParamsOverride;
 import androidx.camera.core.impl.CaptureConfig;
-import androidx.camera.core.impl.DeferrableSurface;
+import androidx.camera.core.impl.Quirks;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -44,9 +46,11 @@
     public void buildCaptureRequestWithNullCameraDevice() throws CameraAccessException {
         CameraDevice cameraDevice = null;
         CaptureConfig captureConfig = new CaptureConfig.Builder().build();
+        TemplateParamsOverride noOpTemplateParamsOverride = new TemplateParamsOverride(
+                new Quirks(emptyList()));
 
         CaptureRequest captureRequest = Camera2CaptureRequestBuilder.build(captureConfig,
-                cameraDevice, new HashMap<DeferrableSurface, Surface>(), true);
+                cameraDevice, new HashMap<>(), true, noOpTemplateParamsOverride);
 
         assertThat(captureRequest).isNull();
     }
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/TemplateParamsOverrideTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/TemplateParamsOverrideTest.kt
new file mode 100644
index 0000000..0c42de6
--- /dev/null
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/TemplateParamsOverrideTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2024 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.compat.workaround
+
+import android.hardware.camera2.CameraDevice.TEMPLATE_MANUAL
+import android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW
+import android.hardware.camera2.CameraDevice.TEMPLATE_RECORD
+import android.hardware.camera2.CameraDevice.TEMPLATE_STILL_CAPTURE
+import android.hardware.camera2.CameraDevice.TEMPLATE_VIDEO_SNAPSHOT
+import android.hardware.camera2.CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG
+import android.hardware.camera2.CameraMetadata.CONTROL_CAPTURE_INTENT_PREVIEW
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureRequest.CONTROL_CAPTURE_INTENT
+import androidx.camera.camera2.internal.compat.quirk.CaptureIntentPreviewQuirk
+import androidx.camera.core.impl.Quirk
+import androidx.camera.core.impl.Quirks
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.ParameterizedRobolectricTestRunner
+import org.robolectric.annotation.internal.DoNotInstrument
+
+/** Unit test for [TemplateParamsOverride]. */
+@RunWith(ParameterizedRobolectricTestRunner::class)
+@DoNotInstrument
+class TemplateParamsOverrideTest(
+    private val testName: String,
+    quirk: Quirk?,
+    private val expectedParamsMap: Map<Int, Map<CaptureRequest.Key<*>, Any>>,
+) {
+    companion object {
+        private val emptyParamsMap = emptyMap<CaptureRequest.Key<*>, Any>()
+        private val paramsMapForCaptureIntentPreviewQuirk =
+            mapOf(
+                TEMPLATE_RECORD to mapOf(CONTROL_CAPTURE_INTENT to CONTROL_CAPTURE_INTENT_PREVIEW)
+            )
+
+        @JvmStatic
+        @ParameterizedRobolectricTestRunner.Parameters(name = "testName={0}")
+        fun data() =
+            mutableListOf<Array<Any?>>().apply {
+                add(
+                    arrayOf(
+                        "no quirk",
+                        null,
+                        emptyParamsMap,
+                    )
+                )
+                add(
+                    arrayOf(
+                        "CaptureIntentPreviewQuirk with false workaround flag",
+                        object : CaptureIntentPreviewQuirk {
+                            override fun workaroundByCaptureIntentPreview() = false
+                        },
+                        emptyParamsMap,
+                    )
+                )
+                add(
+                    arrayOf(
+                        "CaptureIntentPreviewQuirk with true workaround flag",
+                        object : CaptureIntentPreviewQuirk {
+                            override fun workaroundByCaptureIntentPreview() = true
+                        },
+                        paramsMapForCaptureIntentPreviewQuirk,
+                    )
+                )
+            }
+    }
+
+    private val quirks = Quirks(if (quirk != null) listOf(quirk) else emptyList())
+
+    @Test
+    fun getOverrideParams() {
+        for (template in
+            listOf(
+                TEMPLATE_PREVIEW,
+                TEMPLATE_RECORD,
+                TEMPLATE_STILL_CAPTURE,
+                TEMPLATE_MANUAL,
+                TEMPLATE_VIDEO_SNAPSHOT,
+                TEMPLATE_ZERO_SHUTTER_LAG
+            )) {
+            val params = TemplateParamsOverride(quirks).getOverrideParams(template)
+            val expectedParams = expectedParamsMap[template] ?: emptyParamsMap
+            assertWithMessage("getOverrideParams with template: $template")
+                .that(params)
+                .isEqualTo(expectedParams)
+        }
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/Quirks.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/Quirks.java
index 81bafdb..baedf54 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/Quirks.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/Quirks.java
@@ -18,6 +18,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -97,4 +98,10 @@
 
         return false;
     }
+
+    /** Adds an extra quirk. */
+    @VisibleForTesting
+    public void addQuirkForTesting(@NonNull Quirk quirk) {
+        mQuirks.add(quirk);
+    }
 }