Merge "[CameraPipe] Port FocusMeteringControlTest from camera-camera2" into androidx-main
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 b9cc262..2b0ba9b 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
@@ -114,9 +114,6 @@
return signal.asListenableFuture()
}
- // TODO(b/254790453): Add proper tests for the method, might be better to do it after adding the
- // logic to check the number of metering regions supported by device,
- // in this#meteringRegionsFromMeteringPoints()
fun isFocusMeteringSupported(
action: FocusMeteringAction
): Boolean {
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 dd5a033..b12e900 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
@@ -17,22 +17,114 @@
package androidx.camera.camera2.pipe.integration.adapter
import android.graphics.Rect
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.CaptureResult.CONTROL_AF_STATE
+import android.hardware.camera2.CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
+import android.hardware.camera2.CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED
import android.hardware.camera2.params.MeteringRectangle
import android.os.Build
import android.util.Rational
+import androidx.camera.camera2.pipe.CameraId
+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.UseCaseCamera
+import androidx.camera.camera2.pipe.integration.impl.UseCaseCameraRequestControl
+import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
+import androidx.camera.camera2.pipe.integration.testing.FakeCameraProperties
+import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCameraRequestControl
+import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
+import androidx.camera.camera2.pipe.testing.FakeFrameMetadata
+import androidx.camera.core.CameraControl
+import androidx.camera.core.FocusMeteringAction
+import androidx.camera.core.FocusMeteringResult
+import androidx.camera.core.MeteringPointFactory
+import androidx.camera.core.SurfaceOrientedMeteringPointFactory
+import androidx.camera.core.UseCase
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import com.google.common.util.concurrent.FutureCallback
+import com.google.common.util.concurrent.Futures
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+import junit.framework.TestCase
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
+private const val CAMERA_ID_0 = "0" // 640x480 sensor size
+private const val CAMERA_ID_1 = "1" // 1920x1080 sensor size
+private const val CAMERA_ID_2 = "2" // 640x480 sensor size, not support AF_AUTO.
+private const val CAMERA_ID_3 = "3" // camera that does not support 3A regions.
+
+private const val SENSOR_WIDTH = 640
+private const val SENSOR_HEIGHT = 480
+private const val SENSOR_WIDTH2 = 1920
+private const val SENSOR_HEIGHT2 = 1080
+
+private val AREA_WIDTH = (MeteringPointFactory.getDefaultPointSize() * SENSOR_WIDTH).toInt()
+private val AREA_HEIGHT = (MeteringPointFactory.getDefaultPointSize() * SENSOR_HEIGHT).toInt()
+
+private val M_RECT_1 = Rect(0, 0, AREA_WIDTH / 2, AREA_HEIGHT / 2)
+private val M_RECT_2 = Rect(0, SENSOR_HEIGHT - AREA_HEIGHT / 2, AREA_WIDTH / 2, SENSOR_HEIGHT)
+private val M_RECT_3 = Rect(
+ SENSOR_WIDTH - AREA_WIDTH / 2,
+ SENSOR_HEIGHT - AREA_HEIGHT / 2,
+ SENSOR_WIDTH,
+ SENSOR_HEIGHT
+)
+
@RunWith(RobolectricCameraPipeTestRunner::class)
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
@DoNotInstrument
-public class FocusMeteringControlTest {
+class FocusMeteringControlTest {
+ private val pointFactory = SurfaceOrientedMeteringPointFactory(1f, 1f)
+ private lateinit var focusMeteringControl: FocusMeteringControl
+
+ private val point1 = pointFactory.createPoint(0f, 0f)
+ private val point2 = pointFactory.createPoint(0.0f, 1.0f)
+ private val point3 = pointFactory.createPoint(1.0f, 1.0f)
+
+ private val cameraPropertiesMap = mutableMapOf<String, CameraProperties>()
+
+ private val fakeRequestControl = FakeUseCaseCameraRequestControl()
+ private val fakeUseCaseThreads by lazy {
+ val executor = Executors.newSingleThreadExecutor()
+ val dispatcher = executor.asCoroutineDispatcher()
+ val cameraScope = CoroutineScope(Job() + dispatcher)
+
+ UseCaseThreads(
+ cameraScope,
+ executor,
+ dispatcher,
+ )
+ }
+
+ @Before
+ fun setUp() {
+ loadCameraProperties()
+ fakeRequestControl.focusMeteringResult3A = Result3A(status = Result3A.Status.OK)
+ focusMeteringControl = initFocusMeteringControl(CAMERA_ID_0)
+ }
+
@Test
- public fun meteringRegionsFromMeteringPoint_fovAspectRatioEqualToCropAspectRatio() {
+ fun meteringRegionsFromMeteringPoint_fovAspectRatioEqualToCropAspectRatio() {
val meteringPoint = FakeMeteringPointFactory().createPoint(0.0f, 0.0f)
val meteringRectangles = FocusMeteringControl.meteringRegionsFromMeteringPoints(
listOf(meteringPoint),
@@ -82,7 +174,7 @@
}
@Test
- public fun meteringRegionsFromMeteringPoint_fovAspectRatioGreaterThanCropAspectRatio() {
+ fun meteringRegionsFromMeteringPoint_fovAspectRatioGreaterThanCropAspectRatio() {
val meteringPoint = FakeMeteringPointFactory().createPoint(0.0f, 0.0f)
val meteringRectangles = FocusMeteringControl.meteringRegionsFromMeteringPoints(
listOf(meteringPoint),
@@ -128,7 +220,7 @@
}
@Test
- public fun meteringRegionsFromMeteringPoint_fovAspectRatioLessThanCropAspectRatio() {
+ fun meteringRegionsFromMeteringPoint_fovAspectRatioLessThanCropAspectRatio() {
val meteringPoint = FakeMeteringPointFactory().createPoint(0.0f, 0.0f)
val meteringRectangles = FocusMeteringControl.meteringRegionsFromMeteringPoints(
listOf(meteringPoint),
@@ -168,4 +260,647 @@
)
assertThat(meteringRectangles2[0]).isEqualTo(expectedMeteringRectangle2)
}
-}
\ No newline at end of file
+
+ @Test
+ fun startFocusAndMetering_invalidPoint() = runBlocking {
+ val invalidPoint = pointFactory.createPoint(1f, 1.1f)
+
+ startFocusMeteringAndAwait(FocusMeteringAction.Builder(invalidPoint).build())
+
+ // TODO: This will probably throw an invalid argument exception in future instead of
+ // passing the parameters to request control, better to assert the exception then.
+
+ with(fakeRequestControl.focusMeteringCalls.last()) {
+ assertWithMessage("Wrong number of AE regions").that(aeRegions.size).isEqualTo(0)
+ assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(0)
+ assertWithMessage("Wrong number of AWB regions").that(awbRegions.size).isEqualTo(0)
+ }
+ }
+
+ @Test
+ fun startFocusAndMetering_defaultPoint_3ARectsAreCorrect() = runBlocking {
+ startFocusMeteringAndAwait(FocusMeteringAction.Builder(point1).build())
+
+ with(fakeRequestControl.focusMeteringCalls.last()) {
+ assertWithMessage("Wrong number of AE regions").that(aeRegions.size).isEqualTo(1)
+ assertWithMessage("Wrong AE region").that(aeRegions[0].rect).isEqualTo(M_RECT_1)
+
+ assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(1)
+ assertWithMessage("Wrong AF region").that(afRegions[0].rect).isEqualTo(M_RECT_1)
+
+ assertWithMessage("Wrong number of AWB regions").that(awbRegions.size).isEqualTo(1)
+ assertWithMessage("Wrong AWB region").that(awbRegions[0].rect).isEqualTo(M_RECT_1)
+ }
+ }
+
+ @Test
+ fun startFocusAndMetering_multiplePoints_3ARectsAreCorrect() = runBlocking {
+ // Camera 0 i.e. Max AF count = 3, Max AE count = 3, Max AWB count = 1
+ startFocusMeteringAndAwait(
+ FocusMeteringAction.Builder(point1)
+ .addPoint(point2)
+ .addPoint(point3)
+ .build()
+ )
+
+ with(fakeRequestControl.focusMeteringCalls.last()) {
+ assertWithMessage("Wrong number of AE regions").that(aeRegions.size).isEqualTo(3)
+ assertWithMessage("Wrong AE region").that(aeRegions[0].rect).isEqualTo(M_RECT_1)
+ assertWithMessage("Wrong AE region").that(aeRegions[1].rect).isEqualTo(M_RECT_2)
+ assertWithMessage("Wrong AE region").that(aeRegions[2].rect).isEqualTo(M_RECT_3)
+
+ assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(3)
+ assertWithMessage("Wrong AF region").that(afRegions[0].rect).isEqualTo(M_RECT_1)
+ assertWithMessage("Wrong AF region").that(afRegions[1].rect).isEqualTo(M_RECT_2)
+ assertWithMessage("Wrong AF region").that(afRegions[2].rect).isEqualTo(M_RECT_3)
+
+ assertWithMessage("Wrong number of AWB regions").that(awbRegions.size).isEqualTo(1)
+ assertWithMessage("Wrong AWB region").that(awbRegions[0].rect).isEqualTo(M_RECT_1)
+ }
+ }
+
+ @Test
+ fun startFocusAndMetering_multiplePointsVariousModes() = runBlocking {
+ // Camera 0 i.e. Max AF count = 3, Max AE count = 3, Max AWB count = 1
+ startFocusMeteringAndAwait(
+ FocusMeteringAction.Builder(point1, FocusMeteringAction.FLAG_AWB)
+ .addPoint(point2, FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE)
+ .addPoint(
+ point3,
+ FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE or
+ FocusMeteringAction.FLAG_AWB
+ )
+ .build()
+ )
+
+ with(fakeRequestControl.focusMeteringCalls.last()) {
+ assertWithMessage("Wrong number of AE regions").that(aeRegions.size).isEqualTo(2)
+ assertWithMessage("Wrong AE region").that(aeRegions[0].rect).isEqualTo(M_RECT_2)
+ assertWithMessage("Wrong AE region").that(aeRegions[1].rect).isEqualTo(M_RECT_3)
+
+ assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(2)
+ assertWithMessage("Wrong AF region").that(afRegions[0].rect).isEqualTo(M_RECT_2)
+ assertWithMessage("Wrong AF region").that(afRegions[1].rect).isEqualTo(M_RECT_3)
+
+ assertWithMessage("Wrong number of AWB regions").that(awbRegions.size).isEqualTo(1)
+ assertWithMessage("Wrong AWB region").that(awbRegions[0].rect).isEqualTo(M_RECT_1)
+ }
+ }
+
+ @Test
+ fun startFocusAndMetering_multiplePointsDistinctModes() {
+ // Camera 0 i.e. Max AF count = 3, Max AE count = 3, Max AWB count = 1
+ startFocusMeteringAndAwait(
+ FocusMeteringAction.Builder(point1, FocusMeteringAction.FLAG_AF)
+ .addPoint(point2, FocusMeteringAction.FLAG_AWB)
+ .addPoint(point3, FocusMeteringAction.FLAG_AE)
+ .build()
+ )
+
+ with(fakeRequestControl.focusMeteringCalls.last()) {
+ assertWithMessage("Wrong number of AE regions").that(aeRegions.size).isEqualTo(1)
+ assertWithMessage("Wrong AE region").that(aeRegions[0].rect).isEqualTo(M_RECT_3)
+
+ assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(1)
+ assertWithMessage("Wrong AF region").that(afRegions[0].rect).isEqualTo(M_RECT_1)
+
+ assertWithMessage("Wrong number of AWB regions").that(awbRegions.size).isEqualTo(1)
+ assertWithMessage("Wrong AWB region").that(awbRegions[0].rect).isEqualTo(M_RECT_2)
+ }
+ }
+
+ @Test
+ fun meteringPointsWithSize_convertedCorrectly() {
+ val point1 = pointFactory.createPoint(0.5f, 0.5f, 1.0f)
+ val point2 = pointFactory.createPoint(0.5f, 0.5f, 0.5f)
+ val point3 = pointFactory.createPoint(0.5f, 0.5f, 0.1f)
+
+ startFocusMeteringAndAwait(
+ FocusMeteringAction.Builder(point1)
+ .addPoint(point2)
+ .addPoint(point3).build()
+ )
+
+ with(fakeRequestControl.focusMeteringCalls.last()) {
+ assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(3)
+
+ assertWithMessage("Wrong AF region width")
+ .that(afRegions[0].rect.width()).isEqualTo((SENSOR_WIDTH * 1.0f).toInt())
+ assertWithMessage("Wrong AF region height")
+ .that(afRegions[0].rect.height()).isEqualTo((SENSOR_HEIGHT * 1.0f).toInt())
+
+ assertWithMessage("Wrong AF region width")
+ .that(afRegions[1].rect.width()).isEqualTo((SENSOR_WIDTH * 0.5f).toInt())
+ assertWithMessage("Wrong AF region height")
+ .that(afRegions[1].rect.height()).isEqualTo((SENSOR_HEIGHT * 0.5f).toInt())
+
+ assertWithMessage("Wrong AF region width")
+ .that(afRegions[2].rect.width()).isEqualTo((SENSOR_WIDTH * 0.1f).toInt())
+ assertWithMessage("Wrong AF region height")
+ .that(afRegions[2].rect.height()).isEqualTo((SENSOR_HEIGHT * 0.1f).toInt())
+ }
+ }
+
+ @Test
+ fun startFocusMetering_AfLocked_completesWithFocusFalse() {
+ fakeRequestControl.focusMeteringResult3A = Result3A(
+ status = Result3A.Status.OK,
+ frameMetadata = FakeFrameMetadata(
+ extraMetadata = mapOf(
+ CONTROL_AF_STATE to CONTROL_AF_STATE_FOCUSED_LOCKED
+ )
+ )
+ )
+ val action = FocusMeteringAction.Builder(point1).build()
+
+ val future = focusMeteringControl.startFocusAndMetering(action)
+
+ assertFutureFocusCompleted(future, true)
+ }
+
+ @Test
+ fun startFocusMetering_AfNotLocked_completesWithFocusFalse() {
+ fakeRequestControl.focusMeteringResult3A = Result3A(
+ status = Result3A.Status.OK,
+ frameMetadata = FakeFrameMetadata(
+ extraMetadata = mapOf(
+ CONTROL_AF_STATE to CONTROL_AF_STATE_NOT_FOCUSED_LOCKED
+ )
+ )
+ )
+ val action = FocusMeteringAction.Builder(point1).build()
+
+ val future = focusMeteringControl.startFocusAndMetering(action)
+
+ assertFutureFocusCompleted(future, false)
+ }
+
+ @Test
+ @Ignore("When AfState is null, it means AF is not supported")
+ fun startFocusMetering_AfStateIsNull_completesWithFocusFalse() {
+ fakeRequestControl.focusMeteringResult3A = Result3A(
+ status = Result3A.Status.OK,
+ frameMetadata = FakeFrameMetadata(
+ extraMetadata = mapOf(
+ CONTROL_AF_STATE to null
+ )
+ )
+ )
+ val action = FocusMeteringAction.Builder(point1)
+ .build()
+
+ val result = focusMeteringControl.startFocusAndMetering(
+ action
+ )
+
+ assertFutureFocusCompleted(result, true)
+ }
+
+ @Test
+ @Ignore("When AF is not supported, focus should be reported as successful")
+ fun startFocusMeteringAfRequested_CameraNotSupportAfAuto_CompletesWithTrue() {
+ // Use camera which does not support AF_AUTO
+ focusMeteringControl = initFocusMeteringControl(CAMERA_ID_2)
+ val action = FocusMeteringAction.Builder(point1)
+ .build()
+
+ val result = focusMeteringControl.startFocusAndMetering(
+ action
+ )
+
+ assertFutureFocusCompleted(result, true)
+ }
+
+ @Test
+ @Ignore("b/205662153")
+ fun startFocusMetering_cancelledBeforeCompletion_failsWithOperationCanceledOperation() {
+ val action = FocusMeteringAction.Builder(point1).build()
+ val future = focusMeteringControl.startFocusAndMetering(
+ action
+ )
+
+ // TODO: Check if the following is the correct method to call while enabling this test
+ focusMeteringControl.cancelFocusAndMeteringAsync()
+
+ try {
+ future.get()
+ TestCase.fail("The future should fail.")
+ } catch (e: ExecutionException) {
+ assertThat(e.cause)
+ .isInstanceOf(CameraControl.OperationCanceledException::class.java)
+ } catch (e: InterruptedException) {
+ assertThat(e.cause)
+ .isInstanceOf(CameraControl.OperationCanceledException::class.java)
+ }
+ }
+
+ @Test
+ @Ignore("b/205662153: Enable when cancelFocusAndMetering implementation is completed")
+ fun startThenCancelThenStart_previous2FuturesFailsWithOperationCanceled() {
+ val action = FocusMeteringAction.Builder(point1)
+ .build()
+
+ val result1 = focusMeteringControl.startFocusAndMetering(action)
+ // TODO: b/205662153
+// val result2 = focusMeteringControl.cancelFocusAndMetering()
+ focusMeteringControl.startFocusAndMetering(action)
+
+ assertFutureFailedWithOperationCancellation(result1)
+ // TODO: b/205662153
+// assertFutureFailedWithOperationCancellation(result2)
+ }
+
+ @Test
+ @Ignore("b/205662153: Enable when cancelFocusAndMetering implementation is completed")
+ fun startMultipleActions_allExceptLatestAreCancelled() {
+ val action = FocusMeteringAction.Builder(point1)
+ .build()
+ val result1 = focusMeteringControl.startFocusAndMetering(action)
+ val result2 = focusMeteringControl.startFocusAndMetering(action)
+ val result3 = focusMeteringControl.startFocusAndMetering(action)
+ assertFutureFailedWithOperationCancellation(result1)
+ assertFutureFailedWithOperationCancellation(result2)
+ assertFutureFocusCompleted(result3, true)
+ }
+
+ @Test
+ @Ignore("b/205662153: Enable when cancelFocusAndMetering implementation is completed")
+ fun startFocusMetering_focusedThenCancel_futureStillCompletes() {
+ fakeRequestControl.focusMeteringResult3A = Result3A(
+ status = Result3A.Status.OK,
+ frameMetadata = FakeFrameMetadata(
+ extraMetadata = mapOf(
+ CONTROL_AF_STATE to CONTROL_AF_STATE_FOCUSED_LOCKED
+ )
+ )
+ )
+ val action = FocusMeteringAction.Builder(point1).build()
+
+ val result = focusMeteringControl.startFocusAndMetering(action)
+
+ // cancel it and then ensure the returned ListenableFuture still completes;
+ // TODO: Check if the following is the correct method to call while enabling this test
+ focusMeteringControl.cancelFocusAndMeteringAsync()
+ assertFutureFocusCompleted(result, true)
+ }
+
+ @Test
+ @Ignore("aosp/2369189")
+ fun startFocusMeteringAFAEAWB_noPointsAreSupported_failFuture() {
+ val focusMeteringControl = initFocusMeteringControl(CAMERA_ID_3)
+ val action = FocusMeteringAction.Builder(
+ point1,
+ FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE or
+ FocusMeteringAction.FLAG_AWB
+ ).build()
+ val future = focusMeteringControl.startFocusAndMetering(action)
+
+ assertThrows(ExecutionException::class.java) {
+ future[500, TimeUnit.MILLISECONDS]
+ }.also {
+ assertThat(it.cause).isInstanceOf(IllegalArgumentException::class.java)
+ }
+ }
+
+ @Test
+ @Ignore("aosp/2369189")
+ fun startFocusMeteringAEAWB_noPointsAreSupported_failFuture() {
+ val focusMeteringControl = initFocusMeteringControl(CAMERA_ID_3)
+ val action = FocusMeteringAction.Builder(
+ point1,
+ FocusMeteringAction.FLAG_AE or FocusMeteringAction.FLAG_AWB
+ ).build()
+ val future = focusMeteringControl.startFocusAndMetering(action)
+
+ assertThrows(ExecutionException::class.java) {
+ future[500, TimeUnit.MILLISECONDS]
+ }.also {
+ assertThat(it.cause).isInstanceOf(IllegalArgumentException::class.java)
+ }
+ }
+
+ @Test
+ @Ignore("aosp/2369189")
+ fun startFocusMeteringAFAWB_noPointsAreSupported_failFuture() {
+ val focusMeteringControl = initFocusMeteringControl(CAMERA_ID_3)
+ val action = FocusMeteringAction.Builder(
+ point1,
+ FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AWB
+ ).build()
+ val future = focusMeteringControl.startFocusAndMetering(action)
+
+ assertThrows(ExecutionException::class.java) {
+ future[500, TimeUnit.MILLISECONDS]
+ }.also {
+ assertThat(it.cause).isInstanceOf(IllegalArgumentException::class.java)
+ }
+ }
+
+ @Test
+ fun startFocusMetering_morePointsThanSupported_futureCompletes() {
+ // Camera 0 supports only 3 AF, 3 AE, 1 AWB regions, here we try to have 1 AE region, 2 AWB
+ // regions. It should still complete the future.
+ val action = FocusMeteringAction.Builder(
+ point1,
+ FocusMeteringAction.FLAG_AE or FocusMeteringAction.FLAG_AWB
+ ).addPoint(point2, FocusMeteringAction.FLAG_AWB)
+ .build()
+
+ val future = focusMeteringControl.startFocusAndMetering(action)
+
+ // isFocused should be false since AF shouldn't trigger for lack of AF region.
+ assertFutureFocusCompleted(future, false)
+ }
+
+ @Test
+ @Ignore("aosp/2369189")
+ fun startFocusMetering_noPointsAreValid_failFuture() {
+ val focusMeteringControl = initFocusMeteringControl(CAMERA_ID_0)
+
+ // These will generate MeteringRectangles (width == 0 or height ==0)
+ val invalidPt1 = pointFactory.createPoint(2.0f, 2.0f)
+ val invalidPt2 = pointFactory.createPoint(2.0f, 0.5f)
+ val invalidPt3 = pointFactory.createPoint(-1.0f, -1.0f)
+ val action = FocusMeteringAction.Builder(invalidPt1, FocusMeteringAction.FLAG_AF)
+ .addPoint(invalidPt2, FocusMeteringAction.FLAG_AE)
+ .addPoint(invalidPt3, FocusMeteringAction.FLAG_AWB).build()
+ val future = focusMeteringControl.startFocusAndMetering(action)
+
+ assertThrows(ExecutionException::class.java) {
+ future[500, TimeUnit.MILLISECONDS]
+ }.also {
+ assertThat(it.cause).isInstanceOf(IllegalArgumentException::class.java)
+ }
+ }
+
+ @Test
+ fun isFocusMeteringSupported_allSupportedPoints_shouldReturnTrue() {
+ val action = FocusMeteringAction.Builder(
+ point1,
+ FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE or
+ FocusMeteringAction.FLAG_AWB
+ ).addPoint(point2, FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE)
+ .addPoint(point2, FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE)
+ .build()
+ assertThat(focusMeteringControl.isFocusMeteringSupported(action)).isTrue()
+ }
+
+ @Test
+ fun isFocusMeteringSupported_morePointsThanSupported_shouldReturnTrue() {
+ // Camera 0 supports 3 AF, 3 AE, 1 AWB regions, here we try to have 1 AE region, 2 AWB
+ // regions. But it should still be supported.
+ val action = FocusMeteringAction.Builder(
+ point1,
+ FocusMeteringAction.FLAG_AE or FocusMeteringAction.FLAG_AWB
+ )
+ .addPoint(point2, FocusMeteringAction.FLAG_AWB)
+ .build()
+ assertThat(focusMeteringControl.isFocusMeteringSupported(action)).isTrue()
+ }
+
+ @Test
+ fun isFocusMeteringSupported_noSupport3ARegion_shouldReturnFalse() {
+ val action = FocusMeteringAction.Builder(point1).build()
+
+ // No 3A regions are supported on Camera3
+ val focusMeteringControl = initFocusMeteringControl(CAMERA_ID_3)
+ assertThat(focusMeteringControl.isFocusMeteringSupported(action)).isFalse()
+ }
+
+ @Test
+ fun isFocusMeteringSupported_allInvalidPoints_shouldReturnFalse() {
+ val invalidPoint1 = pointFactory.createPoint(1.1f, 0f)
+ val invalidPoint2 = pointFactory.createPoint(0f, 1.1f)
+ val invalidPoint3 = pointFactory.createPoint(-0.1f, 0f)
+ val invalidPoint4 = pointFactory.createPoint(0f, -0.1f)
+ val action = FocusMeteringAction.Builder(invalidPoint1)
+ .addPoint(invalidPoint2)
+ .addPoint(invalidPoint3)
+ .addPoint(invalidPoint4).build()
+ assertThat(focusMeteringControl.isFocusMeteringSupported(action)).isFalse()
+ }
+
+ // TODO: Port the following tests once their corresponding logics have been implemented.
+ // - triggerAfWithTemplate, triggerAePrecaptureWithTemplate, cancelAfAeTriggerWithTemplate
+ // - startFocusAndMetering_AfRegionCorrectedByQuirk
+ // - cropRegionIsSet_resultBasedOnCropRegion
+ // - previewFovAdjusted_16by9_to_4by3, previewFovAdjusted_4by3_to_16by9, customFovAdjusted
+ // - withAFPoints_AFIsTriggered, withoutAFPoints_AFIsNotTriggered (ref: TapToFocusDeviceTest)
+ // - autoCancelDuration_completeWithIsFocusSuccessfulFalse
+ // - shorterAutoCancelDuration_cancelIsCalled_completeActionFutureIsNotCalled
+ // - longerAutoCancelDuration_cancelIsCalled_afterCompleteWithIsFocusSuccessfulFalse
+ // - autoCancelDurationDisabled_completeAfterAutoFocusTimeoutDuration
+ // The following ones should probably be tested with actual camera (shadow or device) since
+ // these tests guess what camera would return for provided parameters,
+ // I think it will be better to test these in actual devices (b/263322280). Additionally, we
+ // should also add some tests here to check if toFocusMeteringResult is working properly.
+ // - startFocusMeteringAEAWB_sessionUpdated_completesWithFocusFalse
+ // - startFocusMeteringAE_sessionUpdated_completesWithFocusFalse
+ // - startFocusMeteringAFOnly_sessionUpdated_completesWithFocusTrue
+ // The following ones will depend on how exactly they will be implemented.
+ // - cancelFocusAndMetering_* (probably many of these tests will no longer be applicable in
+ // this level since Controller3A handles things a bit differently)
+ // - 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>,
+ isFocused: Boolean
+ ) {
+ val focusMeteringResult = future[3, TimeUnit.SECONDS]
+ assertThat(focusMeteringResult.isFocusSuccessful).isEqualTo(isFocused)
+ }
+
+ private fun <T> assertFutureFailedWithOperationCancellation(future: ListenableFuture<T>) {
+ assertThrows(ExecutionException::class.java) {
+ future[3, TimeUnit.SECONDS]
+ }.apply {
+ assertThat(cause).isInstanceOf(CameraControl.OperationCanceledException::class.java)
+ }
+ }
+
+ private val focusMeteringResultCallback = object : FutureCallback<FocusMeteringResult?> {
+ private var latch = CountDownLatch(1)
+
+ @Volatile
+ var successResult: FocusMeteringResult? = null
+
+ @Volatile
+ var failureThrowable: Throwable? = null
+
+ override fun onSuccess(result: FocusMeteringResult?) {
+ successResult = result
+ latch.countDown()
+ }
+
+ override fun onFailure(t: Throwable) {
+ failureThrowable = t
+ latch.countDown()
+ }
+
+ fun reset() {
+ latch = CountDownLatch(1)
+ }
+
+ suspend fun await(timeoutMs: Long = 10000) {
+ withContext(Dispatchers.IO) {
+ latch.await(timeoutMs, TimeUnit.MILLISECONDS)
+ }
+ }
+ }
+
+ private fun startFocusMetering(action: FocusMeteringAction) {
+ focusMeteringResultCallback.reset()
+
+ val result = focusMeteringControl.startFocusAndMetering(action)
+ Futures.addCallback<FocusMeteringResult>(
+ result,
+ focusMeteringResultCallback,
+ Executors.newSingleThreadExecutor()
+ )
+ }
+
+ private fun startFocusMeteringAndAwait(action: FocusMeteringAction) = runBlocking {
+ startFocusMetering(action)
+ focusMeteringResultCallback.await()
+ }
+
+ private fun initFocusMeteringControl(cameraId: String) =
+ FocusMeteringControl(
+ cameraPropertiesMap[cameraId]!!, fakeUseCaseThreads
+ ).apply {
+ useCaseCamera = object : UseCaseCamera {
+ override var runningUseCases: Set<UseCase>
+ get() = TODO("Not yet implemented")
+ set(@Suppress("UNUSED_PARAMETER") value) {}
+
+ override val requestControl: UseCaseCameraRequestControl
+ get() = fakeRequestControl
+
+ override fun <T> setParameterAsync(
+ key: CaptureRequest.Key<T>,
+ value: T,
+ priority: androidx.camera.core.impl.Config.OptionPriority
+ ): Deferred<Unit> {
+ TODO("Not yet implemented")
+ }
+
+ override fun setParametersAsync(
+ values: Map<CaptureRequest.Key<*>, Any>,
+ priority: androidx.camera.core.impl.Config.OptionPriority
+ ): Deferred<Unit> {
+ TODO("Not yet implemented")
+ }
+
+ override fun close(): Job {
+ TODO("Not yet implemented")
+ }
+ }
+ }
+
+ private fun initCameraProperties(
+ cameraIdStr: String,
+ characteristics: Map<CameraCharacteristics.Key<*>, Any?>
+ ): FakeCameraProperties {
+ val cameraId = CameraId(cameraIdStr)
+ return FakeCameraProperties(
+ FakeCameraMetadata(
+ cameraId = cameraId,
+ characteristics = characteristics
+ ),
+ cameraId
+ )
+ }
+
+ private fun loadCameraProperties() {
+ // **** Camera 0 characteristics (640X480 sensor size)****//
+ val characteristics0 = mapOf(
+ CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE to
+ Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT),
+ CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES to
+ intArrayOf(
+ CaptureResult.CONTROL_AF_MODE_CONTINUOUS_VIDEO,
+ CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE,
+ CaptureResult.CONTROL_AF_MODE_AUTO,
+ CaptureResult.CONTROL_AF_MODE_OFF
+ ),
+ CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES to
+ intArrayOf(
+ CaptureResult.CONTROL_AE_MODE_ON,
+ CaptureResult.CONTROL_AE_MODE_ON_ALWAYS_FLASH,
+ CaptureResult.CONTROL_AE_MODE_ON_AUTO_FLASH,
+ CaptureResult.CONTROL_AE_MODE_OFF
+ ),
+ CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES to
+ intArrayOf(
+ CaptureResult.CONTROL_AWB_MODE_AUTO,
+ CaptureResult.CONTROL_AWB_MODE_OFF
+ ),
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL to
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
+ CameraCharacteristics.CONTROL_MAX_REGIONS_AF to 3,
+ CameraCharacteristics.CONTROL_MAX_REGIONS_AE to 3,
+ CameraCharacteristics.CONTROL_MAX_REGIONS_AWB to 1
+ )
+
+ cameraPropertiesMap[CAMERA_ID_0] = initCameraProperties(
+ CAMERA_ID_0,
+ characteristics0
+ )
+
+ // **** Camera 1 characteristics (1920x1080 sensor size) ****//
+ val characteristics1 = mapOf(
+ CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE to
+ Rect(0, 0, SENSOR_WIDTH2, SENSOR_HEIGHT2),
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL to
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3,
+ CameraCharacteristics.CONTROL_MAX_REGIONS_AF to 1,
+ CameraCharacteristics.CONTROL_MAX_REGIONS_AE to 1,
+ CameraCharacteristics.CONTROL_MAX_REGIONS_AWB to 1
+ )
+
+ cameraPropertiesMap[CAMERA_ID_1] = initCameraProperties(
+ CAMERA_ID_1,
+ characteristics1
+ )
+
+ // **** Camera 2 characteristics (640x480 sensor size, does not support AF_AUTO ****//
+ val characteristics2 = mapOf(
+ CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE to
+ Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT),
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL to
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3,
+ CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES to
+ intArrayOf(
+ CaptureResult.CONTROL_AF_MODE_OFF
+ ),
+ CameraCharacteristics.CONTROL_MAX_REGIONS_AF to 1,
+ CameraCharacteristics.CONTROL_MAX_REGIONS_AE to 1,
+ CameraCharacteristics.CONTROL_MAX_REGIONS_AWB to 1
+ )
+
+ cameraPropertiesMap[CAMERA_ID_2] = initCameraProperties(
+ CAMERA_ID_2,
+ characteristics2
+ )
+
+ // ** Camera 3 characteristics (640x480 sensor size, does not support any 3A regions //
+ val characteristics3 = mapOf(
+ CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE to
+ Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT),
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL to
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3,
+ CameraCharacteristics.CONTROL_MAX_REGIONS_AF to 0,
+ CameraCharacteristics.CONTROL_MAX_REGIONS_AE to 0,
+ CameraCharacteristics.CONTROL_MAX_REGIONS_AWB to 0
+ )
+
+ cameraPropertiesMap[CAMERA_ID_3] = initCameraProperties(
+ CAMERA_ID_3,
+ characteristics3
+ )
+ }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
index fd6f7a7..d550bdb 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
@@ -90,13 +90,19 @@
return CompletableDeferred(Result3A(status = Result3A.Status.OK))
}
+ val focusMeteringCalls = mutableListOf<FocusMeteringParams>()
+ var focusMeteringResult3A = Result3A(status = Result3A.Status.OK)
+
override suspend fun startFocusAndMeteringAsync(
aeRegions: List<MeteringRectangle>,
afRegions: List<MeteringRectangle>,
awbRegions: List<MeteringRectangle>,
afTriggerStartAeMode: AeMode?
): Deferred<Result3A> {
- return CompletableDeferred(Result3A(status = Result3A.Status.OK))
+ focusMeteringCalls.add(
+ FocusMeteringParams(aeRegions, afRegions, awbRegions, afTriggerStartAeMode)
+ )
+ return CompletableDeferred(focusMeteringResult3A)
}
override suspend fun cancelFocusAndMeteringAsync(): Deferred<Result3A> {
@@ -111,6 +117,13 @@
): List<Deferred<Void?>> {
return listOf(CompletableDeferred(null))
}
+
+ data class FocusMeteringParams(
+ val aeRegions: List<MeteringRectangle>,
+ val afRegions: List<MeteringRectangle>,
+ val awbRegions: List<MeteringRectangle>,
+ val afTriggerStartAeMode: AeMode?
+ )
}
// TODO: Further implement the methods in this class as needed