blob: 3133d8843ffb2f081ad5416f785d185ce7386cec [file] [log] [blame]
/*
* Copyright 2020 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.impl
import android.hardware.camera2.CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START
import android.hardware.camera2.CaptureRequest.CONTROL_AF_TRIGGER_CANCEL
import android.hardware.camera2.CaptureRequest.CONTROL_AF_TRIGGER_START
import android.hardware.camera2.CaptureRequest
import android.hardware.camera2.CaptureRequest.CONTROL_AE_LOCK
import android.hardware.camera2.CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER
import android.hardware.camera2.CaptureRequest.CONTROL_AF_TRIGGER
import android.hardware.camera2.CaptureResult
import android.hardware.camera2.params.MeteringRectangle
import android.os.Build
import androidx.annotation.GuardedBy
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.AeMode
import androidx.camera.camera2.pipe.AfMode
import androidx.camera.camera2.pipe.AwbMode
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CameraGraph.Constants3A.FRAME_NUMBER_INVALID
import androidx.camera.camera2.pipe.FlashMode
import androidx.camera.camera2.pipe.Lock3ABehavior
import androidx.camera.camera2.pipe.Result3A
import androidx.camera.camera2.pipe.Status3A
import androidx.camera.camera2.pipe.TorchState
import androidx.camera.camera2.pipe.core.Log.debug
import androidx.camera.camera2.pipe.shouldUnlockAe
import androidx.camera.camera2.pipe.shouldUnlockAf
import androidx.camera.camera2.pipe.shouldUnlockAwb
import androidx.camera.camera2.pipe.shouldWaitForAeToConverge
import androidx.camera.camera2.pipe.shouldWaitForAfToConverge
import androidx.camera.camera2.pipe.shouldWaitForAwbToConverge
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.cancel
/**
* This class implements the 3A methods of [CameraGraphSessionImpl].
*/
internal class Controller3A(
private val graphProcessor: GraphProcessor,
private val graphState3A: GraphState3A,
private val graphListener3A: Listener3A
) {
companion object {
private val aeConvergedStateList = listOf(
CaptureResult.CONTROL_AE_STATE_CONVERGED,
CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED,
CaptureResult.CONTROL_AE_STATE_LOCKED
)
private val awbConvergedStateList = listOf(
CaptureResult.CONTROL_AWB_STATE_CONVERGED,
CaptureResult.CONTROL_AWB_STATE_LOCKED
)
private val afConvergedStateList = listOf(
CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED,
CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED,
CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED,
CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED
)
private val aeLockedStateList = listOf(CaptureResult.CONTROL_AE_STATE_LOCKED)
private val awbLockedStateList = listOf(CaptureResult.CONTROL_AWB_STATE_LOCKED)
private val afLockedStateList = listOf(
CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED,
CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED
)
private val aePostPrecaptureStateList = listOf(
CaptureResult.CONTROL_AE_STATE_CONVERGED,
CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED,
CaptureResult.CONTROL_AE_STATE_LOCKED
)
val parameterForAfTriggerStart = mapOf<CaptureRequest.Key<*>, Any>(
CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_START
)
val parameterForAfTriggerCancel = mapOf<CaptureRequest.Key<*>, Any>(
CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_CANCEL
)
private val parametersForAePrecaptureAndAfTrigger = mapOf<CaptureRequest.Key<*>, Any>(
CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_START,
CONTROL_AE_PRECAPTURE_TRIGGER to CONTROL_AE_PRECAPTURE_TRIGGER_START
)
private val result3ASubmitFailed = Result3A(FRAME_NUMBER_INVALID, Status3A.SUBMIT_FAILED)
private val aeUnlockedStateList = listOf(
CaptureResult.CONTROL_AE_STATE_INACTIVE,
CaptureResult.CONTROL_AE_STATE_SEARCHING,
CaptureResult.CONTROL_AE_STATE_CONVERGED,
CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED
)
private val afUnlockedStateList = listOf(
CaptureResult.CONTROL_AF_STATE_INACTIVE,
CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN,
CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN,
CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED,
CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED
)
private val awbUnlockedStateList = listOf(
CaptureResult.CONTROL_AWB_STATE_INACTIVE,
CaptureResult.CONTROL_AWB_STATE_SEARCHING,
CaptureResult.CONTROL_AWB_STATE_CONVERGED
)
}
// Keep track of the result associated with latest call to update3A. If update3A is called again
// and the current result is not complete, we will cancel the current result.
@GuardedBy("this")
private var lastUpdate3AResult: Deferred<Result3A>? = null
fun update3A(
aeMode: AeMode? = null,
afMode: AfMode? = null,
awbMode: AwbMode? = null,
flashMode: FlashMode? = null,
aeRegions: List<MeteringRectangle>? = null,
afRegions: List<MeteringRectangle>? = null,
awbRegions: List<MeteringRectangle>? = null
): Deferred<Result3A> {
// Add the listener to a global pool of 3A listeners to monitor the state change to the
// desired one.
val listener = createListenerFor3AParams(aeMode, afMode, awbMode, flashMode)
graphListener3A.addListener(listener)
// Update the 3A state of the graph. This will make sure then when GraphProcessor builds
// the next request it will apply the 3A parameters corresponding to the updated 3A state
// to the request.
graphState3A.update(aeMode, afMode, awbMode, flashMode, aeRegions, afRegions, awbRegions)
// Try submitting a new repeating request with the 3A parameters corresponding to the new
// 3A state and corresponding listeners.
graphProcessor.invalidate()
val result = listener.getDeferredResult()
synchronized(this) {
lastUpdate3AResult?.cancel("A newer call for 3A state update initiated.")
lastUpdate3AResult = result
}
return result
}
suspend fun submit3A(
aeMode: AeMode? = null,
afMode: AfMode? = null,
awbMode: AwbMode? = null,
aeRegions: List<MeteringRectangle>? = null,
afRegions: List<MeteringRectangle>? = null,
awbRegions: List<MeteringRectangle>? = null
): Deferred<Result3A> {
// Add the listener to a global pool of 3A listeners to monitor the state change to the
// desired one.
val listener = createListenerFor3AParams(aeMode, afMode, awbMode)
graphListener3A.addListener(listener)
val extra3AParams = mutableMapOf<CaptureRequest.Key<*>, Any>()
aeMode?.let { extra3AParams.put(CaptureRequest.CONTROL_AE_MODE, it.value) }
afMode?.let { extra3AParams.put(CaptureRequest.CONTROL_AF_MODE, it.value) }
awbMode?.let { extra3AParams.put(CaptureRequest.CONTROL_AWB_MODE, it.value) }
aeRegions?.let {
extra3AParams.put(
CaptureRequest.CONTROL_AE_REGIONS,
it.toTypedArray()
)
}
afRegions?.let {
extra3AParams.put(
CaptureRequest.CONTROL_AF_REGIONS,
it.toTypedArray()
)
}
awbRegions?.let {
extra3AParams.put(
CaptureRequest.CONTROL_AWB_REGIONS,
it.toTypedArray()
)
}
if (!graphProcessor.submit(extra3AParams)) {
graphListener3A.removeListener(listener)
return CompletableDeferred(result3ASubmitFailed)
}
return listener.getDeferredResult()
}
/**
* Given the desired lock behaviors for ae, af and awb, this method, (a) first unlocks them and
* wait for them to converge, and then (b) locks them.
*
* (a) In this step, as needed, we first send a single request with 'af trigger = cancel' to
* unlock af, and then a repeating request to unlock ae and awb. We suspend till we receive a
* response from the camera that each of the ae, af awb are converged.
* (b) In this step, as needed, we submit a repeating request to lock ae and awb, and then a
* single request to lock af by setting 'af trigger = start'. Once these requests are submitted
* we don't wait further and immediately return a Deferred<Result3A> which gets completed when
* the capture result with correct lock states for ae, af and awb is received.
*
* If we received an error when submitting any of the above requests or if waiting for the
* desired 3A state times out then we return early with the appropriate status code.
*
* Note: the frameLimit and timeLimitNs applies to each of the above steps (a) and (b) and not
* as a whole for the whole lock3A method. Thus, in the worst case this method including the
* completion of returned Deferred<Result3A> can take 2 * min(time equivalent of frameLimit,
* timeLimit) to complete
*/
suspend fun lock3A(
aeLockBehavior: Lock3ABehavior? = null,
afLockBehavior: Lock3ABehavior? = null,
awbLockBehavior: Lock3ABehavior? = null,
frameLimit: Int = CameraGraph.DEFAULT_FRAME_LIMIT,
timeLimitNs: Long? = CameraGraph.DEFAULT_TIME_LIMIT_NS
): Deferred<Result3A> {
// If we explicitly need to unlock af first before proceeding to lock it, we need to send
// a single request with TRIGGER = TRIGGER_CANCEL so that af can start a fresh scan.
if (afLockBehavior.shouldUnlockAf()) {
debug { "lock3A - sending a request to unlock af first." }
if (!graphProcessor.submit(parameterForAfTriggerCancel)) {
return CompletableDeferred(result3ASubmitFailed)
}
}
// As needed unlock ae, awb and wait for ae, af and awb to converge.
if (aeLockBehavior.shouldWaitForAeToConverge() ||
afLockBehavior.shouldWaitForAfToConverge() ||
awbLockBehavior.shouldWaitForAwbToConverge()
) {
val converged3AExitConditions = createConverged3AExitConditions(
aeLockBehavior.shouldWaitForAeToConverge(),
afLockBehavior.shouldWaitForAfToConverge(),
awbLockBehavior.shouldWaitForAwbToConverge()
)
val listener = Result3AStateListenerImpl(
converged3AExitConditions,
frameLimit,
timeLimitNs
)
graphListener3A.addListener(listener)
// If we have to explicitly unlock ae, awb, then update the 3A state of the camera
// graph. This is because ae, awb lock values should stay as part of repeating
// request to the camera device. For af we need only one single request to trigger it,
// leaving it unset in the subsequent requests to the camera device will not affect the
// previously sent af trigger.
val aeLockValue = if (aeLockBehavior.shouldUnlockAe()) false else null
val awbLockValue = if (awbLockBehavior.shouldUnlockAwb()) false else null
if (aeLockValue != null || awbLockValue != null) {
debug { "lock3A - setting aeLock=$aeLockValue, awbLock=$awbLockValue" }
graphState3A.update(
aeLock = aeLockValue,
awbLock = awbLockValue
)
}
graphProcessor.invalidate()
debug {
"lock3A - waiting for" +
(if (aeLockBehavior.shouldWaitForAeToConverge()) " ae" else "") +
(if (afLockBehavior.shouldWaitForAfToConverge()) " af" else "") +
(if (awbLockBehavior.shouldWaitForAwbToConverge()) " awb" else "") +
" to converge before locking them."
}
val result = listener.getDeferredResult().await()
debug {
"lock3A - converged at frame number=${result.frameNumber.value}, status=${result
.status}"
}
// Return immediately if we encounter an error when unlocking and waiting for
// convergence.
if (result.status != Status3A.OK) {
return CompletableDeferred(result)
}
}
return lock3ANow(aeLockBehavior, afLockBehavior, awbLockBehavior, frameLimit, timeLimitNs)
}
/**
* This method unlocks ae, af and awb, as specified by setting the corresponding parameter to
* true.
*
* There are two requests involved in this operation, (a) a single request with af trigger =
* cancel, to unlock af, and then (a) a repeating request to unlock ae, awb.
*/
suspend fun unlock3A(
ae: Boolean? = null,
af: Boolean? = null,
awb: Boolean? = null
): Deferred<Result3A> {
check(ae == true || af == true || awb == true) { "No parameter has value as true" }
// If we explicitly need to unlock af first before proceeding to lock it, we need to send
// a single request with TRIGGER = TRIGGER_CANCEL so that af can start a fresh scan.
if (af == true) {
debug { "unlock3A - sending a request to unlock af first." }
if (!graphProcessor.submit(parameterForAfTriggerCancel)) {
debug { "unlock3A - request to unlock af failed, returning early." }
return CompletableDeferred(result3ASubmitFailed)
}
}
// As needed unlock ae, awb and wait for ae, af and awb to converge.
val unlocked3AExitConditions = createUnLocked3AExitConditions(
ae == true,
af == true,
awb == true
)
val listener = Result3AStateListenerImpl(unlocked3AExitConditions)
graphListener3A.addListener(listener)
// Update the 3A state of the camera graph and invalidate the repeating request with the
// new state.
val aeLockValue = if (ae == true) false else null
val awbLockValue = if (awb == true) false else null
if (aeLockValue != null || awbLockValue != null) {
debug { "unlock3A - updating graph state, aeLock=$aeLockValue, awbLock=$awbLockValue" }
graphState3A.update(
aeLock = aeLockValue,
awbLock = awbLockValue
)
}
graphProcessor.invalidate()
return listener.getDeferredResult()
}
suspend fun lock3AForCapture(
frameLimit: Int = CameraGraph.DEFAULT_FRAME_LIMIT,
timeLimitNs: Long = CameraGraph.DEFAULT_TIME_LIMIT_NS
): Deferred<Result3A> {
val listener = Result3AStateListenerImpl(
mapOf<CaptureResult.Key<*>, List<Any>>(
CaptureResult.CONTROL_AE_STATE to aePostPrecaptureStateList,
CaptureResult.CONTROL_AF_STATE to afLockedStateList
),
frameLimit,
timeLimitNs
)
graphListener3A.addListener(listener)
debug { "lock3AForCapture - sending a request to trigger ae precapture metering and af." }
if (!graphProcessor.submit(parametersForAePrecaptureAndAfTrigger)) {
debug {
"lock3AForCapture - request to trigger ae precapture metering and af failed, " +
"returning early."
}
graphListener3A.removeListener(listener)
return CompletableDeferred(result3ASubmitFailed)
}
graphProcessor.invalidate()
return listener.getDeferredResult()
}
suspend fun unlock3APostCapture(): Deferred<Result3A> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return unlock3APostCaptureAndroidMAndAbove()
}
return unlock3APostCaptureAndroidLAndBelow()
}
/**
* For api level below 23, to resume the normal scan of ae after precapture metering
* sequence, we have to first send a request with ae lock = true and then a request with ae
* lock = false. REF :
* https://developer.android.com/reference/android/hardware/camera2/CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
*/
private suspend fun unlock3APostCaptureAndroidLAndBelow(): Deferred<Result3A> {
debug { "unlock3AForCapture - sending a request to cancel af and turn on ae." }
if (!graphProcessor.submit(
mapOf(
CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_CANCEL,
CONTROL_AE_LOCK to true
)
)
) {
debug { "unlock3AForCapture - request to cancel af and lock ae as failed." }
return CompletableDeferred(result3ASubmitFailed)
}
// Listener to monitor when we receive the capture result corresponding to the request
// below.
val listener = Result3AStateListenerImpl(mapOf())
graphListener3A.addListener(listener)
debug { "unlock3AForCapture - sending a request to turn off ae." }
if (!graphProcessor.submit(mapOf<CaptureRequest.Key<*>, Any>(CONTROL_AE_LOCK to false))) {
debug { "unlock3AForCapture - request to unlock ae failed." }
graphListener3A.removeListener(listener)
return CompletableDeferred(result3ASubmitFailed)
}
return listener.getDeferredResult()
}
/**
* For API level 23 or newer versions, the sending a request with
* CONTROL_AE_PRECAPTURE_TRIGGER = CANCEL can be used to unlock the camera device's
* internally locked AE. REF :
* https://developer.android.com/reference/android/hardware/camera2/CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
*/
@RequiresApi(23)
private suspend fun unlock3APostCaptureAndroidMAndAbove(): Deferred<Result3A> {
debug { "unlock3APostCapture - sending a request to reset af and ae precapture metering." }
val parametersForAePrecaptureAndAfCancel = mapOf<CaptureRequest.Key<*>, Any>(
CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_CANCEL,
CONTROL_AE_PRECAPTURE_TRIGGER to
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL
)
if (!graphProcessor.submit(parametersForAePrecaptureAndAfCancel)) {
debug {
"unlock3APostCapture - request to reset af and ae precapture metering failed, " +
"returning early."
}
return CompletableDeferred(result3ASubmitFailed)
}
// Sending a request with ae precapture trigger = cancel does not have any specific affect
// on the ae state, so we don't need to listen for a specific state. As long as the request
// successfully reaches the camera device and the capture request corresponding to that
// request arrives back, it should suffice.
val listener = Result3AStateListenerImpl(
mapOf<CaptureResult.Key<*>, List<Any>>(
CaptureResult.CONTROL_AF_STATE to afUnlockedStateList
)
)
graphListener3A.addListener(listener)
graphProcessor.invalidate()
return listener.getDeferredResult()
}
fun setTorch(torchState: TorchState): Deferred<Result3A> {
// Determine the flash mode based on the torch state.
val flashMode = if (torchState == TorchState.ON) FlashMode.TORCH else FlashMode.OFF
// To use the flash control, AE mode must be set to ON or OFF.
val currAeMode = graphState3A.aeMode
val desiredAeMode = if (currAeMode == AeMode.ON || currAeMode == AeMode.OFF) null else
AeMode.ON
return update3A(aeMode = desiredAeMode, flashMode = flashMode)
}
private suspend fun lock3ANow(
aeLockBehavior: Lock3ABehavior?,
afLockBehavior: Lock3ABehavior?,
awbLockBehavior: Lock3ABehavior?,
frameLimit: Int?,
timeLimitNs: Long?
): Deferred<Result3A> {
val finalAeLockValue = if (aeLockBehavior == null) null else true
val finalAwbLockValue = if (awbLockBehavior == null) null else true
val locked3AExitConditions = createLocked3AExitConditions(
finalAeLockValue != null,
afLockBehavior != null,
finalAwbLockValue != null
)
var resultForLocked: Deferred<Result3A>? = null
if (locked3AExitConditions.isNotEmpty()) {
val listener = Result3AStateListenerImpl(
locked3AExitConditions,
frameLimit,
timeLimitNs
)
graphListener3A.addListener(listener)
graphState3A.update(aeLock = finalAeLockValue, awbLock = finalAwbLockValue)
debug {
"lock3A - submitting request with aeLock=$finalAeLockValue , " +
"awbLock=$finalAwbLockValue"
}
graphProcessor.invalidate()
resultForLocked = listener.getDeferredResult()
}
if (afLockBehavior == null) {
return resultForLocked!!
}
debug { "lock3A - submitting a request to lock af." }
if (!graphProcessor.submit(parameterForAfTriggerStart)) {
// TODO(sushilnath@): Change the error code to a more specific code so it's clear
// that one of the request in sequence of requests failed and the caller should
// unlock 3A to bring the 3A system to an initial state and then try again if they
// want to. The other option is to reset or restore the 3A state here.
return CompletableDeferred(result3ASubmitFailed)
}
return resultForLocked!!
}
private fun createConverged3AExitConditions(
waitForAeToConverge: Boolean,
waitForAfToConverge: Boolean,
waitForAwbToConverge: Boolean
): Map<CaptureResult.Key<*>, List<Any>> {
if (
!waitForAeToConverge && !waitForAfToConverge && !waitForAwbToConverge
) {
return mapOf()
}
val exitConditionMapForConverged = mutableMapOf<CaptureResult.Key<*>, List<Any>>()
if (waitForAeToConverge) {
exitConditionMapForConverged[CaptureResult.CONTROL_AE_STATE] = aeConvergedStateList
}
if (waitForAwbToConverge) {
exitConditionMapForConverged[CaptureResult.CONTROL_AWB_STATE] = awbConvergedStateList
}
if (waitForAfToConverge) {
exitConditionMapForConverged[CaptureResult.CONTROL_AF_STATE] = afConvergedStateList
}
return exitConditionMapForConverged
}
private fun createLocked3AExitConditions(
waitForAeToLock: Boolean,
waitForAfToLock: Boolean,
waitForAwbToLock: Boolean
): Map<CaptureResult.Key<*>, List<Any>> {
if (!waitForAeToLock && !waitForAfToLock && !waitForAwbToLock) {
return mapOf()
}
val exitConditionMapForLocked = mutableMapOf<CaptureResult.Key<*>, List<Any>>()
if (waitForAeToLock) {
exitConditionMapForLocked[CaptureResult.CONTROL_AE_STATE] = aeLockedStateList
}
if (waitForAfToLock) {
exitConditionMapForLocked[CaptureResult.CONTROL_AF_STATE] = afLockedStateList
}
if (waitForAwbToLock) {
exitConditionMapForLocked[CaptureResult.CONTROL_AWB_STATE] = awbLockedStateList
}
return exitConditionMapForLocked
}
private fun createUnLocked3AExitConditions(
ae: Boolean,
af: Boolean,
awb: Boolean
): Map<CaptureResult.Key<*>, List<Any>> {
if (!ae && !af && !awb) {
return mapOf()
}
val exitConditionMapForUnLocked = mutableMapOf<CaptureResult.Key<*>, List<Any>>()
if (ae) {
exitConditionMapForUnLocked[CaptureResult.CONTROL_AE_STATE] = aeUnlockedStateList
}
if (af) {
exitConditionMapForUnLocked[CaptureResult.CONTROL_AF_STATE] = afUnlockedStateList
}
if (awb) {
exitConditionMapForUnLocked[CaptureResult.CONTROL_AWB_STATE] = awbUnlockedStateList
}
return exitConditionMapForUnLocked
}
// We create a map for the 3A modes and the desired values and leave out the keys
// corresponding to the metering regions. The reason being the camera framework can chose to
// crop or modify the metering regions as per its constraints. So when we receive at least
// one capture result corresponding to this request it is assumed that the framework has
// applied the desired metering regions to the best of its judgement, and we don't need an
// exact match between the metering regions sent in the capture request and the metering
// regions received from the camera device.
private fun createListenerFor3AParams(
aeMode: AeMode? = null,
afMode: AfMode? = null,
awbMode: AwbMode? = null,
flashMode: FlashMode? = null,
): Result3AStateListenerImpl {
val resultModesMap = mutableMapOf<CaptureResult.Key<*>, List<Any>>()
aeMode?.let { resultModesMap.put(CaptureResult.CONTROL_AE_MODE, listOf(it.value)) }
afMode?.let { resultModesMap.put(CaptureResult.CONTROL_AF_MODE, listOf(it.value)) }
awbMode?.let { resultModesMap.put(CaptureResult.CONTROL_AWB_MODE, listOf(it.value)) }
flashMode?.let { resultModesMap.put(CaptureResult.FLASH_MODE, listOf(it.value)) }
return Result3AStateListenerImpl(resultModesMap.toMap())
}
}