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
+ }
+ )
+ }
}