Merge changes from topic "State3AControl" into androidx-main

* changes:
  Update AF mode according to the Template type
  Move AE related settings to State3AControl
diff --git a/camera/camera-camera2-pipe-integration/build.gradle b/camera/camera-camera2-pipe-integration/build.gradle
index 0161db3..5ba8cb5 100644
--- a/camera/camera-camera2-pipe-integration/build.gradle
+++ b/camera/camera-camera2-pipe-integration/build.gradle
@@ -91,6 +91,7 @@
     androidTestImplementation(project(":annotation:annotation-experimental"))
     androidTestImplementation(project(":camera:camera-lifecycle"))
     androidTestImplementation(project(":camera:camera-testing"))
+    androidTestImplementation(project(":camera:camera-video"))
     androidTestImplementation(project(":concurrent:concurrent-futures-ktx"))
     androidTestImplementation(project(":internal-testutils-truth"))
     androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0")
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt
index e5bccd9..a9e2aeb 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt
@@ -17,16 +17,25 @@
 package androidx.camera.camera2.pipe.integration
 
 import android.content.Context
+import android.graphics.SurfaceTexture
 import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES
+import android.hardware.camera2.CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES
 import android.hardware.camera2.CameraCharacteristics.CONTROL_MAX_REGIONS_AE
 import android.hardware.camera2.CameraCharacteristics.CONTROL_MAX_REGIONS_AF
 import android.hardware.camera2.CameraCharacteristics.CONTROL_MAX_REGIONS_AWB
+import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_OFF
 import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON
 import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH
 import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH
+import android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_AUTO
+import android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE
+import android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO
+import android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_OFF
 import android.hardware.camera2.CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION
 import android.hardware.camera2.CaptureRequest.CONTROL_AE_MODE
 import android.hardware.camera2.CaptureRequest.CONTROL_AE_REGIONS
+import android.hardware.camera2.CaptureRequest.CONTROL_AF_MODE
 import android.hardware.camera2.CaptureRequest.CONTROL_AF_REGIONS
 import android.hardware.camera2.CaptureRequest.CONTROL_AWB_REGIONS
 import android.hardware.camera2.CaptureRequest.CONTROL_CAPTURE_INTENT
@@ -36,6 +45,7 @@
 import android.hardware.camera2.CaptureRequest.FLASH_MODE_TORCH
 import android.hardware.camera2.CaptureRequest.SCALER_CROP_REGION
 import android.os.Build
+import android.util.Size
 import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.FrameInfo
 import androidx.camera.camera2.pipe.RequestMetadata
@@ -49,11 +59,15 @@
 import androidx.camera.core.FocusMeteringAction
 import androidx.camera.core.ImageAnalysis
 import androidx.camera.core.ImageCapture
+import androidx.camera.core.Preview
 import androidx.camera.core.SurfaceOrientedMeteringPointFactory
 import androidx.camera.core.UseCase
 import androidx.camera.core.internal.CameraUseCaseAdapter
 import androidx.camera.testing.CameraUtil
 import androidx.camera.testing.CameraXUtil
+import androidx.camera.testing.SurfaceTextureProvider
+import androidx.camera.video.Recorder
+import androidx.camera.video.VideoCapture
 import androidx.concurrent.futures.await
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -188,7 +202,7 @@
 
         waitForResult(captureCount = 60).verify(
             { requestMeta: RequestMetadata, _ ->
-                requestMeta.request[CONTROL_AE_MODE] == CONTROL_AE_MODE_ON_AUTO_FLASH
+                requestMeta.isAeMode(CONTROL_AE_MODE_ON_AUTO_FLASH)
             },
             TIMEOUT
         )
@@ -203,7 +217,7 @@
 
         waitForResult(captureCount = 60).verify(
             { requestMeta: RequestMetadata, _ ->
-                requestMeta[CONTROL_AE_MODE] == CONTROL_AE_MODE_ON
+                requestMeta.isAeMode(CONTROL_AE_MODE_ON)
             },
             TIMEOUT
         )
@@ -218,7 +232,7 @@
 
         waitForResult(captureCount = 60).verify(
             { requestMeta: RequestMetadata, _ ->
-                requestMeta[CONTROL_AE_MODE] == CONTROL_AE_MODE_ON_ALWAYS_FLASH
+                requestMeta.isAeMode(CONTROL_AE_MODE_ON_ALWAYS_FLASH)
             },
             TIMEOUT
         )
@@ -234,7 +248,7 @@
         waitForResult(captureCount = 30).verify(
             { requestMeta: RequestMetadata, frameInfo: FrameInfo ->
                 frameInfo.requestMetadata[FLASH_MODE] == FLASH_MODE_TORCH &&
-                    requestMeta[CONTROL_AE_MODE] == CONTROL_AE_MODE_ON
+                    requestMeta.isAeMode(CONTROL_AE_MODE_ON)
             },
             TIMEOUT
         )
@@ -250,7 +264,7 @@
         waitForResult(captureCount = 30).verify(
             { requestMeta: RequestMetadata, frameInfo: FrameInfo ->
                 frameInfo.requestMetadata[FLASH_MODE] != FLASH_MODE_TORCH &&
-                    requestMeta[CONTROL_AE_MODE] == CONTROL_AE_MODE_ON_AUTO_FLASH
+                    requestMeta.isAeMode(CONTROL_AE_MODE_ON_AUTO_FLASH)
             },
             TIMEOUT
         )
@@ -341,6 +355,32 @@
     }
 
     @Test
+    fun setTemplatePreview_afModeToContinuousPicture() = runBlocking {
+        bindUseCase(createPreview())
+
+        // Assert. Verify the afMode.
+        waitForResult(captureCount = 60).verify(
+            { requestMeta: RequestMetadata, _ ->
+                requestMeta.isAfMode(CONTROL_AF_MODE_CONTINUOUS_PICTURE)
+            },
+            TIMEOUT
+        )
+    }
+
+    @Test
+    fun setTemplateRecord_afModeToContinuousVideo() = runBlocking {
+        bindUseCase(createVideoCapture())
+
+        // Assert. Verify the afMode.
+        waitForResult(captureCount = 60).verify(
+            { requestMeta: RequestMetadata, _ ->
+                requestMeta.isAfMode(CONTROL_AF_MODE_CONTINUOUS_VIDEO)
+            },
+            TIMEOUT
+        )
+    }
+
+    @Test
     fun setZoomRatio_operationCanceledExceptionIfNoUseCase() {
         assertFutureFailedWithOperationCancellation(cameraControl.setZoomRatio(1.5f))
     }
@@ -436,4 +476,70 @@
         )
         cameraControl = camera.cameraControl as CameraControlAdapter
     }
+
+    private fun createVideoCapture(): VideoCapture<Recorder> {
+        return VideoCapture.withOutput(Recorder.Builder().build())
+    }
+
+    private suspend fun createPreview(): Preview =
+        Preview.Builder().build().also { preview ->
+            withContext(Dispatchers.Main) {
+                preview.setSurfaceProvider(getSurfaceProvider())
+            }
+        }
+
+    private fun getSurfaceProvider(): Preview.SurfaceProvider {
+        return SurfaceTextureProvider.createSurfaceTextureProvider(
+            object : SurfaceTextureProvider.SurfaceTextureCallback {
+                override fun onSurfaceTextureReady(
+                    surfaceTexture: SurfaceTexture,
+                    resolution: Size
+                ) {
+                    // No-op
+                }
+
+                override fun onSafeToRelease(surfaceTexture: SurfaceTexture) {
+                    surfaceTexture.release()
+                }
+            }
+        )
+    }
+
+    private fun RequestMetadata.isAfMode(afMode: Int): Boolean {
+        return if (characteristics.isAfModeSupported(afMode)) {
+            getOrDefault(CONTROL_AF_MODE, null) == afMode
+        } else {
+            val fallbackMode =
+                if (characteristics.isAfModeSupported(CONTROL_AF_MODE_CONTINUOUS_PICTURE)) {
+                    CONTROL_AF_MODE_CONTINUOUS_PICTURE
+                } else if (characteristics.isAfModeSupported(CONTROL_AF_MODE_AUTO)) {
+                    CONTROL_AF_MODE_AUTO
+                } else {
+                    CONTROL_AF_MODE_OFF
+                }
+            getOrDefault(CONTROL_AF_MODE, null) == fallbackMode
+        }
+    }
+
+    private fun RequestMetadata.isAeMode(aeMode: Int): Boolean {
+        return if (characteristics.isAeModeSupported(aeMode)) {
+            getOrDefault(CONTROL_AE_MODE, null) == aeMode
+        } else {
+            val fallbackMode =
+                if (characteristics.isAeModeSupported(CONTROL_AE_MODE_ON)) {
+                    CONTROL_AE_MODE_ON
+                } else {
+                    CONTROL_AE_MODE_OFF
+                }
+            getOrDefault(CONTROL_AE_MODE, null) == fallbackMode
+        }
+    }
+
+    private fun CameraCharacteristics.isAfModeSupported(
+        afMode: Int
+    ) = (get(CONTROL_AF_AVAILABLE_MODES) ?: intArrayOf(-1)).contains(afMode)
+
+    private fun CameraCharacteristics.isAeModeSupported(
+        aeMode: Int
+    ) = (get(CONTROL_AE_AVAILABLE_MODES) ?: intArrayOf(-1)).contains(aeMode)
 }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraConfig.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraConfig.kt
index 4c1ef19..de3e23f 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraConfig.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraConfig.kt
@@ -35,6 +35,7 @@
 import androidx.camera.camera2.pipe.integration.impl.EvCompControl
 import androidx.camera.camera2.pipe.integration.impl.FlashControl
 import androidx.camera.camera2.pipe.integration.impl.FocusMeteringControl
+import androidx.camera.camera2.pipe.integration.impl.State3AControl
 import androidx.camera.camera2.pipe.integration.impl.TorchControl
 import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
 import androidx.camera.camera2.pipe.integration.impl.ZoomControl
@@ -67,6 +68,7 @@
         EvCompControl.Bindings::class,
         FlashControl.Bindings::class,
         FocusMeteringControl.Bindings::class,
+        State3AControl.Bindings::class,
         TorchControl.Bindings::class,
         Camera2CameraControlCompat.Bindings::class,
     ],
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FlashControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FlashControl.kt
index 0a00d07..21fe187 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FlashControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FlashControl.kt
@@ -16,7 +16,6 @@
 
 package androidx.camera.camera2.pipe.integration.impl
 
-import android.hardware.camera2.CaptureRequest
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.integration.config.CameraScope
 import androidx.camera.core.CameraControl
@@ -25,12 +24,12 @@
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
+import javax.inject.Inject
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.launch
-import javax.inject.Inject
 
-private const val DEFAULT_FLASH_MODE = ImageCapture.FLASH_MODE_OFF
+internal const val DEFAULT_FLASH_MODE = ImageCapture.FLASH_MODE_OFF
 
 /**
  * Implementation of Flash control exposed by [CameraControlInternal].
@@ -38,6 +37,7 @@
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 @CameraScope
 class FlashControl @Inject constructor(
+    private val state3AControl: State3AControl,
     private val threads: UseCaseThreads,
 ) : UseCaseCameraControl {
     private var _useCaseCamera: UseCaseCamera? = null
@@ -74,7 +74,7 @@
     fun setFlashAsync(flashMode: Int): Deferred<Unit> {
         val signal = CompletableDeferred<Unit>()
 
-        useCaseCamera?.let { useCaseCamera ->
+        useCaseCamera?.let {
 
             // Update _flashMode immediately so that CameraControlInternal#getFlashMode()
             // returns correct value.
@@ -84,24 +84,8 @@
                 stopRunningTask()
 
                 _updateSignal = signal
-                when (flashMode) {
-                    ImageCapture.FLASH_MODE_OFF -> CaptureRequest.CONTROL_AE_MODE_ON
-                    ImageCapture.FLASH_MODE_ON -> CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH
-                    ImageCapture.FLASH_MODE_AUTO -> CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH
-                    // TODO(b/209383160): porting the Quirk for AEModeDisabler
-                    //      mAutoFlashAEModeDisabler.getCorrectedAeMode(
-                    //      CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH
-                    //    )
-                    else -> CaptureRequest.CONTROL_AE_MODE_ON
-                }.let { aeMode ->
-                    // TODO: check the AE mode is supported before set it.
-                    useCaseCamera.requestControl.addParametersAsync(
-                        type = UseCaseCameraRequestControl.Type.FLASH,
-                        values = mapOf(
-                            CaptureRequest.CONTROL_AE_MODE to aeMode,
-                        )
-                    )
-                }.join()
+                state3AControl.flashMode = flashMode
+                state3AControl.updateSignal?.join()
 
                 signal.complete(Unit)
             }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FocusMeteringControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FocusMeteringControl.kt
index 43b6750..08fd80a 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FocusMeteringControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FocusMeteringControl.kt
@@ -19,6 +19,7 @@
 import android.graphics.PointF
 import android.graphics.Rect
 import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CaptureRequest
 import android.hardware.camera2.CaptureResult
 import android.hardware.camera2.params.MeteringRectangle
 import android.util.Rational
@@ -54,8 +55,9 @@
 @CameraScope
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 class FocusMeteringControl @Inject constructor(
-    val cameraProperties: CameraProperties,
-    val threads: UseCaseThreads,
+    private val cameraProperties: CameraProperties,
+    private val state3AControl: State3AControl,
+    private val threads: UseCaseThreads,
 ) : UseCaseCameraControl {
     private var _useCaseCamera: UseCaseCamera? = null
 
@@ -155,6 +157,9 @@
                     )
                     return@launch
                 }
+                if (afRectangles.isNotEmpty()) {
+                    state3AControl.preferredFocusMode = CaptureRequest.CONTROL_AF_MODE_AUTO
+                }
                 val (isCancelEnabled, timeout) = if (action.isAutoCancelEnabled &&
                     action.autoCancelDurationInMillis < autoFocusTimeoutMs
                 ) {
@@ -259,6 +264,7 @@
         signalToCancel: CompletableDeferred<FocusMeteringResult>?,
     ): Result3A {
         signalToCancel?.setCancelException("Cancelled by cancelFocusAndMetering()")
+        state3AControl.preferredFocusMode = null
         return useCaseCamera.requestControl.cancelFocusAndMeteringAsync().await()
     }
 
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/State3AControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/State3AControl.kt
new file mode 100644
index 0000000..740438c
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/State3AControl.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2023 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.impl
+
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CaptureRequest
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
+import androidx.camera.camera2.pipe.integration.config.CameraScope
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.UseCase
+import androidx.camera.core.impl.CaptureConfig
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.lifecycle.Observer
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import javax.inject.Inject
+import kotlin.properties.ObservableProperty
+import kotlin.reflect.KProperty
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Deferred
+
+@CameraScope
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+class State3AControl @Inject constructor(
+    val cameraProperties: CameraProperties,
+) : UseCaseCameraControl {
+    private var _useCaseCamera: UseCaseCamera? = null
+    override var useCaseCamera: UseCaseCamera?
+        get() = _useCaseCamera
+        set(value) {
+            val previousUseCaseCamera = _useCaseCamera
+            _useCaseCamera = value
+            CameraXExecutors.mainThreadExecutor().execute {
+                previousUseCaseCamera?.runningUseCasesLiveData?.removeObserver(
+                    useCaseChangeObserver
+                )
+                value?.let {
+                    it.runningUseCasesLiveData.observeForever(useCaseChangeObserver)
+                    invalidate() // Always apply the settings to the camera.
+                }
+            }
+        }
+
+    private val useCaseChangeObserver =
+        Observer<Set<UseCase>> { useCases -> useCases.updateTemplate() }
+    private val afModes = cameraProperties.metadata.getOrDefault(
+        CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES,
+        intArrayOf(CaptureRequest.CONTROL_AF_MODE_OFF)
+    ).asList()
+    private val aeModes = cameraProperties.metadata.getOrDefault(
+        CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES,
+        intArrayOf(CaptureRequest.CONTROL_AE_MODE_OFF)
+    ).asList()
+    private val awbModes = cameraProperties.metadata.getOrDefault(
+        CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES,
+        intArrayOf(CaptureRequest.CONTROL_AWB_MODE_OFF)
+    ).asList()
+
+    var updateSignal: Deferred<Unit>? = null
+        private set
+    var flashMode by updateOnPropertyChange(DEFAULT_FLASH_MODE)
+    var template by updateOnPropertyChange(DEFAULT_REQUEST_TEMPLATE)
+    var preferredAeMode: Int? by updateOnPropertyChange(null)
+    var preferredFocusMode: Int? by updateOnPropertyChange(null)
+
+    override fun reset() {
+        preferredAeMode = null
+        preferredFocusMode = null
+        flashMode = DEFAULT_FLASH_MODE
+        template = DEFAULT_REQUEST_TEMPLATE
+    }
+
+    private fun <T> updateOnPropertyChange(
+        initialValue: T
+    ) = object : ObservableProperty<T>(initialValue) {
+        override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) {
+            if (newValue != oldValue) {
+                invalidate()
+            }
+        }
+    }
+
+    fun invalidate() {
+        val preferAeMode = preferredAeMode ?: when (flashMode) {
+            ImageCapture.FLASH_MODE_OFF -> CaptureRequest.CONTROL_AE_MODE_ON
+            ImageCapture.FLASH_MODE_ON -> CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH
+            ImageCapture.FLASH_MODE_AUTO -> CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH
+            // TODO(b/209383160): porting the Quirk for AEModeDisabler
+            //      mAutoFlashAEModeDisabler.getCorrectedAeMode(
+            //      CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH
+            //    )
+            else -> CaptureRequest.CONTROL_AE_MODE_ON
+        }
+
+        val preferAfMode = preferredFocusMode ?: getDefaultAfMode()
+
+        updateSignal = useCaseCamera?.requestControl?.addParametersAsync(
+            values = mapOf(
+                CaptureRequest.CONTROL_AE_MODE to getSupportedAeMode(preferAeMode),
+                CaptureRequest.CONTROL_AF_MODE to getSupportedAfMode(preferAfMode),
+                CaptureRequest.CONTROL_AWB_MODE to getSupportedAwbMode(
+                    CaptureRequest.CONTROL_AWB_MODE_AUTO
+                ),
+            )
+        ) ?: CompletableDeferred(null)
+    }
+
+    private fun getDefaultAfMode(): Int = when (template) {
+        CameraDevice.TEMPLATE_RECORD -> CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO
+        CameraDevice.TEMPLATE_PREVIEW -> CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
+        else -> CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
+    }
+
+    /**
+     * If preferredMode not available, priority is CONTINUOUS_PICTURE > AUTO > OFF
+     */
+    private fun getSupportedAfMode(preferredMode: Int) = when {
+        afModes.contains(preferredMode) -> {
+            preferredMode
+        }
+
+        afModes.contains(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) -> {
+            CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
+        }
+
+        afModes.contains(CaptureRequest.CONTROL_AF_MODE_AUTO) -> {
+            CaptureRequest.CONTROL_AF_MODE_AUTO
+        }
+
+        else -> {
+            CaptureRequest.CONTROL_AF_MODE_OFF
+        }
+    }
+
+    /**
+     * If preferredMode not available, priority is AE_ON > AE_OFF
+     */
+    private fun getSupportedAeMode(preferredMode: Int) = when {
+        aeModes.contains(preferredMode) -> {
+            preferredMode
+        }
+
+        aeModes.contains(CaptureRequest.CONTROL_AE_MODE_ON) -> {
+            CaptureRequest.CONTROL_AE_MODE_ON
+        }
+
+        else -> {
+            CaptureRequest.CONTROL_AE_MODE_OFF
+        }
+    }
+
+    /**
+     * If preferredMode not available, priority is AWB_AUTO > AWB_OFF
+     */
+    private fun getSupportedAwbMode(preferredMode: Int) = when {
+        awbModes.contains(preferredMode) -> {
+            preferredMode
+        }
+
+        awbModes.contains(CaptureRequest.CONTROL_AWB_MODE_AUTO) -> {
+            CaptureRequest.CONTROL_AWB_MODE_AUTO
+        }
+
+        else -> {
+            CaptureRequest.CONTROL_AWB_MODE_OFF
+        }
+    }
+
+    private fun Collection<UseCase>.updateTemplate() {
+        SessionConfigAdapter(this).getValidSessionConfigOrNull()?.let {
+            val templateType = it.repeatingCaptureConfig.templateType
+            template = if (templateType != CaptureConfig.TEMPLATE_TYPE_NONE) {
+                templateType
+            } else {
+                DEFAULT_REQUEST_TEMPLATE
+            }
+        }
+    }
+
+    @Module
+    abstract class Bindings {
+        @Binds
+        @IntoSet
+        abstract fun provideControls(state3AControl: State3AControl): UseCaseCameraControl
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/TorchControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/TorchControl.kt
index ffce1ecb..7981dc9 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/TorchControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/TorchControl.kt
@@ -29,10 +29,10 @@
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
+import javax.inject.Inject
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.launch
-import javax.inject.Inject
 
 /**
  * Implementation of Torch control exposed by [CameraControlInternal].
@@ -41,6 +41,7 @@
 @CameraScope
 class TorchControl @Inject constructor(
     cameraProperties: CameraProperties,
+    private val state3AControl: State3AControl,
     private val threads: UseCaseThreads,
 ) : UseCaseCameraControl {
 
@@ -94,20 +95,10 @@
                 // TODO(b/209757083), handle the failed result of the setTorchAsync().
                 useCaseCamera.requestControl.setTorchAsync(torch).join()
 
-                if (torch) {
-                    // Hold the internal AE mode to ON while the torch is turned ON.
-                    useCaseCamera.requestControl.addParametersAsync(
-                        type = UseCaseCameraRequestControl.Type.TORCH,
-                        values = mapOf(
-                            CaptureRequest.CONTROL_AE_MODE to CaptureRequest.CONTROL_AE_MODE_ON,
-                        )
-                    )
-                } else {
-                    // Restore the AE mode after the torch control has been used.
-                    useCaseCamera.requestControl.setConfigAsync(
-                        type = UseCaseCameraRequestControl.Type.TORCH,
-                    )
-                }.join()
+                // Hold the internal AE mode to ON while the torch is turned ON.
+                state3AControl.preferredAeMode =
+                    if (torch) CaptureRequest.CONTROL_AE_MODE_ON else null
+                state3AControl.updateSignal?.join()
 
                 signal.complete(Unit)
             }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
index 3d9377d..852b4f4 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
@@ -48,7 +48,7 @@
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.async
 
-private const val DEFAULT_REQUEST_TEMPLATE = CameraDevice.TEMPLATE_PREVIEW
+internal const val DEFAULT_REQUEST_TEMPLATE = CameraDevice.TEMPLATE_PREVIEW
 
 /**
  * The RequestControl provides a couple of APIs to update the config of the camera, it also stores
@@ -66,8 +66,6 @@
     enum class Type {
         SESSION_CONFIG,
         DEFAULT,
-        FLASH,
-        TORCH,
         CAMERA2_CAMERA_CONTROL,
     }
 
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/FocusMeteringControlTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/FocusMeteringControlTest.kt
index 8ca5d39..5c5af8b 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/FocusMeteringControlTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/FocusMeteringControlTest.kt
@@ -31,6 +31,7 @@
 import androidx.camera.camera2.pipe.Result3A
 import androidx.camera.camera2.pipe.integration.impl.CameraProperties
 import androidx.camera.camera2.pipe.integration.impl.FocusMeteringControl
+import androidx.camera.camera2.pipe.integration.impl.State3AControl
 import androidx.camera.camera2.pipe.integration.impl.UseCaseCamera
 import androidx.camera.camera2.pipe.integration.impl.UseCaseCameraRequestControl
 import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
@@ -1028,19 +1029,84 @@
         assertFutureFocusCompleted(future, false)
     }
 
+    @Test
+    fun startFocusMetering_afAutoModeIsSet() {
+        // Arrange.
+        val action = FocusMeteringAction.Builder(point1, FocusMeteringAction.FLAG_AF).build()
+        val state3AControl = createState3AControl(CAMERA_ID_0)
+        focusMeteringControl = initFocusMeteringControl(
+            CAMERA_ID_0,
+            setOf(createPreview(Size(1920, 1080))),
+            fakeUseCaseThreads,
+            state3AControl,
+        )
+
+        // Act.
+        focusMeteringControl.startFocusAndMetering(
+            action
+        )[5, TimeUnit.SECONDS]
+
+        // Assert.
+        assertThat(
+            state3AControl.preferredFocusMode
+        ).isEqualTo(CaptureRequest.CONTROL_AF_MODE_AUTO)
+    }
+
+    @Test
+    fun startFocusMetering_AfNotInvolved_afAutoModeNotSet() {
+        // Arrange.
+        val action = FocusMeteringAction.Builder(
+            point1,
+            FocusMeteringAction.FLAG_AE or FocusMeteringAction.FLAG_AWB
+        ).build()
+        val state3AControl = createState3AControl(CAMERA_ID_0)
+        focusMeteringControl = initFocusMeteringControl(
+            CAMERA_ID_0,
+            setOf(createPreview(Size(1920, 1080))),
+            fakeUseCaseThreads,
+            state3AControl,
+        )
+
+        // Act.
+        focusMeteringControl.startFocusAndMetering(
+            action
+        )[5, TimeUnit.SECONDS]
+
+        // Assert.
+        assertThat(
+            state3AControl.preferredFocusMode
+        ).isEqualTo(null)
+    }
+
+    @Test
+    fun startAndThenCancel_afAutoModeNotSet(): Unit = runBlocking {
+        // Arrange.
+        val action = FocusMeteringAction.Builder(point1, FocusMeteringAction.FLAG_AF).build()
+        val state3AControl = createState3AControl(CAMERA_ID_0)
+        focusMeteringControl = initFocusMeteringControl(
+            CAMERA_ID_0,
+            setOf(createPreview(Size(1920, 1080))),
+            fakeUseCaseThreads,
+            state3AControl,
+        )
+
+        // Act.
+        focusMeteringControl.startFocusAndMetering(
+            action
+        )[5, TimeUnit.SECONDS]
+        focusMeteringControl.cancelFocusAndMeteringAsync().join()
+
+        // Assert.
+        assertThat(
+            state3AControl.preferredFocusMode
+        ).isEqualTo(null)
+    }
+
     // TODO: Port the following tests once their corresponding logics have been implemented.
     //  - [b/255679866] triggerAfWithTemplate, triggerAePrecaptureWithTemplate,
     //          cancelAfAeTriggerWithTemplate
     //  - startFocusAndMetering_AfRegionCorrectedByQuirk
     //  - [b/262225455] cropRegionIsSet_resultBasedOnCropRegion
-    //  The following ones will depend on how exactly they will be implemented.
-    //  - [b/264018162] addFocusMeteringOptions_hasCorrectAfMode,
-    //                  startFocusMetering_isAfAutoModeIsTrue,
-    //                  startFocusMetering_AfNotInvolved_isAfAutoModeIsSet,
-    //                  startAndThenCancel_isAfAutoModeIsFalse
-    //      (an alternative way can be checking the AF mode
-    //      at the frame with AF_TRIGGER_START request in capture callback, but this requires
-    //      invoking actual camera operations, ref: TapToFocusDeviceTest)
 
     private fun assertFutureFocusCompleted(
         future: ListenableFuture<FocusMeteringResult>,
@@ -1135,8 +1201,11 @@
         cameraId: String,
         useCases: Set<UseCase> = emptySet(),
         useCaseThreads: UseCaseThreads = fakeUseCaseThreads,
+        state3AControl: State3AControl = createState3AControl(cameraId),
     ) = FocusMeteringControl(
-            cameraPropertiesMap[cameraId]!!, useCaseThreads
+            cameraPropertiesMap[cameraId]!!,
+            state3AControl,
+            useCaseThreads
         ).apply {
             fakeUseCaseCamera.runningUseCasesLiveData.value = useCases
             useCaseCamera = fakeUseCaseCamera
@@ -1260,4 +1329,12 @@
                     StreamSpec.builder(suggestedStreamSpecResolution).build()
                 )
             }
+
+    private fun createState3AControl(
+        cameraId: String = CAMERA_ID_0,
+        properties: CameraProperties = cameraPropertiesMap[cameraId]!!,
+        useCaseCamera: UseCaseCamera = fakeUseCaseCamera,
+    ) = State3AControl(properties).apply {
+        this.useCaseCamera = useCaseCamera
+    }
 }
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 6337c98..f6a563e 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
@@ -177,15 +177,21 @@
 
     @Before
     fun setUp() {
+        val fakeUseCaseCamera = FakeUseCaseCamera(requestControl = fakeRequestControl)
+        val fakeCameraProperties = FakeCameraProperties(
+            FakeCameraMetadata(
+                mapOf(CameraCharacteristics.FLASH_INFO_AVAILABLE to true),
+            )
+        )
+
         torchControl = TorchControl(
-            FakeCameraProperties(
-                FakeCameraMetadata(
-                    mapOf(CameraCharacteristics.FLASH_INFO_AVAILABLE to true),
-                )
-            ),
+            fakeCameraProperties,
+            State3AControl(fakeCameraProperties).apply {
+                useCaseCamera = fakeUseCaseCamera
+            },
             fakeUseCaseThreads,
         ).also {
-            it.useCaseCamera = FakeUseCaseCamera(requestControl = fakeRequestControl)
+            it.useCaseCamera = fakeUseCaseCamera
 
             // Ensure the control is updated after the UseCaseCamera been set.
             assertThat(
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/TorchControlTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/TorchControlTest.kt
index be46710..6b24bc2 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/TorchControlTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/TorchControlTest.kt
@@ -147,33 +147,50 @@
 
     @Before
     fun setUp() {
+        val fakeUseCaseCamera = FakeUseCaseCamera()
+        val fakeCameraProperties = FakeCameraProperties(metadata)
         torchControl = TorchControl(
-            FakeCameraProperties(metadata),
+            fakeCameraProperties,
+            State3AControl(fakeCameraProperties).apply {
+                useCaseCamera = fakeUseCaseCamera
+            },
             fakeUseCaseThreads,
         )
-        torchControl.useCaseCamera = FakeUseCaseCamera()
+        torchControl.useCaseCamera = fakeUseCaseCamera
     }
 
     @Test
     fun enableTorch_whenNoFlashUnit(): Unit = runBlocking {
         assertThrows<IllegalStateException> {
+            val fakeUseCaseCamera = FakeUseCaseCamera()
+            val fakeCameraProperties = FakeCameraProperties()
+
             // Without a flash unit, this Job will complete immediately with a IllegalStateException
             TorchControl(
-                FakeCameraProperties(),
+                fakeCameraProperties,
+                State3AControl(fakeCameraProperties).apply {
+                    useCaseCamera = fakeUseCaseCamera
+                },
                 fakeUseCaseThreads,
             ).also {
-                it.useCaseCamera = FakeUseCaseCamera()
+                it.useCaseCamera = fakeUseCaseCamera
             }.setTorchAsync(true).await()
         }
     }
 
     @Test
     fun getTorchState_whenNoFlashUnit() {
+        val fakeUseCaseCamera = FakeUseCaseCamera()
+        val fakeCameraProperties = FakeCameraProperties()
+
         val torchState = TorchControl(
-            FakeCameraProperties(),
+            fakeCameraProperties,
+            State3AControl(fakeCameraProperties).apply {
+                useCaseCamera = fakeUseCaseCamera
+            },
             fakeUseCaseThreads,
         ).also {
-            it.useCaseCamera = FakeUseCaseCamera()
+            it.useCaseCamera = fakeUseCaseCamera
         }.torchStateLiveData.value
 
         Truth.assertThat(torchState).isEqualTo(TorchState.OFF)
@@ -182,8 +199,14 @@
     @Test
     fun enableTorch_whenInactive(): Unit = runBlocking {
         assertThrows<CameraControl.OperationCanceledException> {
+            val fakeUseCaseCamera = FakeUseCaseCamera()
+            val fakeCameraProperties = FakeCameraProperties(metadata)
+
             TorchControl(
-                FakeCameraProperties(metadata),
+                fakeCameraProperties,
+                State3AControl(fakeCameraProperties).apply {
+                    useCaseCamera = fakeUseCaseCamera
+                },
                 fakeUseCaseThreads,
             ).setTorchAsync(true).await()
         }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraInfoAdapterCreator.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraInfoAdapterCreator.kt
index 5c6e224..e98ffc8 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraInfoAdapterCreator.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraInfoAdapterCreator.kt
@@ -29,6 +29,7 @@
 import androidx.camera.camera2.pipe.integration.impl.CameraProperties
 import androidx.camera.camera2.pipe.integration.impl.EvCompControl
 import androidx.camera.camera2.pipe.integration.impl.FocusMeteringControl
+import androidx.camera.camera2.pipe.integration.impl.State3AControl
 import androidx.camera.camera2.pipe.integration.impl.TorchControl
 import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
 import androidx.camera.camera2.pipe.integration.impl.ZoomControl
@@ -79,21 +80,28 @@
             cameraId
         ),
         zoomControl: ZoomControl = this.zoomControl,
-    ) = CameraInfoAdapter(
-        cameraProperties,
-        CameraConfig(cameraId),
-        CameraStateAdapter(),
-        CameraControlStateAdapter(
-            zoomControl,
-            EvCompControl(FakeEvCompCompat()),
-            TorchControl(cameraProperties, useCaseThreads),
-        ),
-        CameraCallbackMap(),
-        FocusMeteringControl(
-            cameraProperties,
-            useCaseThreads
-        ).apply {
-            useCaseCamera = FakeUseCaseCamera()
+    ): CameraInfoAdapter {
+        val fakeUseCaseCamera = FakeUseCaseCamera()
+        val state3AControl = State3AControl(cameraProperties).apply {
+            useCaseCamera = fakeUseCaseCamera
         }
-    )
+        return CameraInfoAdapter(
+            cameraProperties,
+            CameraConfig(cameraId),
+            CameraStateAdapter(),
+            CameraControlStateAdapter(
+                zoomControl,
+                EvCompControl(FakeEvCompCompat()),
+                TorchControl(cameraProperties, state3AControl, useCaseThreads),
+            ),
+            CameraCallbackMap(),
+            FocusMeteringControl(
+                cameraProperties,
+                state3AControl,
+                useCaseThreads
+            ).apply {
+                useCaseCamera = fakeUseCaseCamera
+            }
+        )
+    }
 }