| /* |
| * Copyright 2021 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. |
| */ |
| |
| @file:RequiresApi(21) |
| |
| 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 android.util.Size |
| import androidx.annotation.RequiresApi |
| import androidx.camera.camera2.pipe.CameraGraph |
| import androidx.camera.camera2.pipe.CameraId |
| import androidx.camera.camera2.pipe.Lock3ABehavior |
| import androidx.camera.camera2.pipe.Result3A |
| import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat |
| import androidx.camera.camera2.pipe.integration.compat.ZoomCompat |
| import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks |
| import androidx.camera.camera2.pipe.integration.compat.workaround.MeteringRegionCorrection |
| import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpMeteringRegionCorrection |
| import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector |
| 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 |
| import androidx.camera.camera2.pipe.integration.testing.FakeCameraProperties |
| import androidx.camera.camera2.pipe.integration.testing.FakeState3AControlCreator |
| import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCameraRequestControl |
| import androidx.camera.camera2.pipe.integration.testing.FakeZoomCompat |
| 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.Preview |
| import androidx.camera.core.SurfaceOrientedMeteringPointFactory |
| import androidx.camera.core.UseCase |
| import androidx.camera.core.impl.StreamSpec |
| import androidx.camera.core.impl.utils.executor.CameraXExecutors |
| import androidx.camera.testing.fakes.FakeCamera |
| import androidx.camera.testing.impl.SurfaceTextureProvider |
| import androidx.camera.testing.impl.fakes.FakeUseCase |
| import androidx.test.filters.MediumTest |
| import androidx.testutils.MainDispatcherRule |
| 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 kotlinx.coroutines.CompletableDeferred |
| import kotlinx.coroutines.Deferred |
| import kotlinx.coroutines.Dispatchers |
| import kotlinx.coroutines.ExperimentalCoroutinesApi |
| import kotlinx.coroutines.Job |
| import kotlinx.coroutines.asExecutor |
| import kotlinx.coroutines.async |
| import kotlinx.coroutines.cancel |
| import kotlinx.coroutines.delay |
| import kotlinx.coroutines.test.StandardTestDispatcher |
| import kotlinx.coroutines.test.TestScope |
| import kotlinx.coroutines.test.advanceTimeBy |
| import kotlinx.coroutines.test.advanceUntilIdle |
| import kotlinx.coroutines.test.runTest |
| import kotlinx.coroutines.withContext |
| import org.junit.After |
| import org.junit.Assert.assertThrows |
| import org.junit.Before |
| import org.junit.Rule |
| import org.junit.Test |
| import org.junit.runner.RunWith |
| import org.robolectric.annotation.Config |
| import org.robolectric.annotation.internal.DoNotInstrument |
| import org.robolectric.shadows.StreamConfigurationMapBuilder |
| import org.robolectric.util.ReflectionHelpers |
| |
| 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 CAMERA_ID_4 = "4" // camera 0 with LENS_FACING_FRONT |
| private const val CAMERA_ID_5 = "5" // camera 0 supporting AF regions only |
| |
| 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 AREA_WIDTH_2 = (MeteringPointFactory.getDefaultPointSize() * SENSOR_WIDTH2).toInt() |
| private val AREA_HEIGHT_2 = (MeteringPointFactory.getDefaultPointSize() * SENSOR_HEIGHT2).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 |
| ) |
| |
| // the following rectangles are for metering point (0, 0) |
| private val M_RECT_PVIEW_RATIO_16x9_SENSOR_640x480 = Rect( |
| 0, 60 - AREA_HEIGHT / 2, |
| AREA_WIDTH / 2, 60 + AREA_HEIGHT / 2 |
| ) |
| private val M_RECT_PVIEW_RATIO_4x3_SENSOR_1920x1080 = Rect( |
| 240 - AREA_WIDTH_2 / 2, 0, |
| 240 + AREA_WIDTH_2 / 2, AREA_HEIGHT_2 / 2 |
| ) |
| |
| @OptIn(ExperimentalCoroutinesApi::class) |
| @RunWith(RobolectricCameraPipeTestRunner::class) |
| @Config(minSdk = Build.VERSION_CODES.LOLLIPOP) |
| @DoNotInstrument |
| class FocusMeteringControlTest { |
| private val testScope = TestScope() |
| private val testDispatcher = StandardTestDispatcher(testScope.testScheduler) |
| |
| @get:Rule |
| val mainDispatcherRule = MainDispatcherRule(testDispatcher) |
| |
| private val fakeUseCaseThreads by lazy { |
| UseCaseThreads( |
| testScope, |
| testDispatcher.asExecutor(), |
| testDispatcher |
| ) |
| } |
| |
| 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() |
| |
| @Before |
| fun setUp() { |
| loadCameraProperties() |
| fakeRequestControl.focusMeteringResult = |
| CompletableDeferred(Result3A(status = Result3A.Status.OK)) |
| focusMeteringControl = initFocusMeteringControl(CAMERA_ID_0) |
| } |
| |
| @After |
| fun tearDown() { |
| // CoroutineScope#cancel can throw exception if the scope has no job left |
| try { |
| fakeUseCaseCamera.runningUseCases.forEach { |
| it.onStateDetached() |
| it.onUnbind() |
| } |
| // fakeUseCaseThreads may still be using Main dispatcher which sometimes |
| // causes Dispatchers.resetMain() to throw an exception: |
| // "IllegalStateException: Dispatchers.Main is used concurrently with setting it" |
| fakeUseCaseThreads.scope.cancel() |
| fakeUseCaseThreads.sequentialScope.cancel() |
| } catch (_: Exception) { |
| } |
| } |
| |
| @Test |
| fun meteringRegionsFromMeteringPoint_fovAspectRatioEqualToCropAspectRatio() { |
| val meteringPoint = FakeMeteringPointFactory().createPoint(0.0f, 0.0f) |
| val meteringRectangles = FocusMeteringControl.meteringRegionsFromMeteringPoints( |
| listOf(meteringPoint), |
| 1, |
| Rect(0, 0, 800, 600), |
| Rational(4, 3), |
| FocusMeteringAction.FLAG_AF, |
| NoOpMeteringRegionCorrection, |
| ) |
| assertThat(meteringRectangles.size).isEqualTo(1) |
| // Aspect ratio of crop region is same as default aspect ratio. So no padding is needed |
| // along width or height. However only the bottom right quadrant of the metering rectangle |
| // will fit inside the crop region. |
| val expectedMeteringRectangle = MeteringRectangle( |
| 0, 0, 60, 45, FocusMeteringControl.METERING_WEIGHT_DEFAULT |
| ) |
| assertThat(meteringRectangles[0]).isEqualTo(expectedMeteringRectangle) |
| |
| val meteringPoint1 = FakeMeteringPointFactory().createPoint(0.5f, 0.5f) |
| val meteringRectangles1 = FocusMeteringControl.meteringRegionsFromMeteringPoints( |
| listOf(meteringPoint1), |
| 1, |
| Rect(0, 0, 800, 600), |
| Rational(4, 3), |
| FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE |
| or FocusMeteringAction.FLAG_AWB, |
| NoOpMeteringRegionCorrection, |
| ) |
| assertThat(meteringRectangles1.size).isEqualTo(1) |
| // Aspect ratio of crop region is same as default aspect ratio. So no padding is needed |
| // along width or height. The metering region will completely fit inside the crop region. |
| val expectedMeteringRectangle1 = MeteringRectangle( |
| 340, 255, 120, 90, FocusMeteringControl.METERING_WEIGHT_DEFAULT |
| ) |
| assertThat(meteringRectangles1[0]).isEqualTo(expectedMeteringRectangle1) |
| |
| val meteringPoint2 = FakeMeteringPointFactory().createPoint(1f, 1f) |
| val meteringRectangles2 = FocusMeteringControl.meteringRegionsFromMeteringPoints( |
| listOf(meteringPoint2), |
| 1, |
| Rect(0, 0, 800, 600), |
| Rational(4, 3), |
| FocusMeteringAction.FLAG_AF, |
| NoOpMeteringRegionCorrection, |
| ) |
| assertThat(meteringRectangles2.size).isEqualTo(1) |
| // Aspect ratio of crop region is same as default aspect ratio. So no padding is needed |
| // along width or height. However only the top left quadrant of the metering rectangle |
| // will fit inside the crop region. |
| val expectedMeteringRectangle2 = MeteringRectangle( |
| 740, 555, 60, 45, FocusMeteringControl.METERING_WEIGHT_DEFAULT |
| ) |
| assertThat(meteringRectangles2[0]).isEqualTo(expectedMeteringRectangle2) |
| } |
| |
| @Test |
| fun meteringRegionsFromMeteringPoint_fovAspectRatioGreaterThanCropAspectRatio() { |
| val meteringPoint = FakeMeteringPointFactory().createPoint(0.0f, 0.0f) |
| val meteringRectangles = FocusMeteringControl.meteringRegionsFromMeteringPoints( |
| listOf(meteringPoint), |
| 1, |
| Rect(0, 0, 400, 400), |
| Rational(4, 3), |
| FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE |
| or FocusMeteringAction.FLAG_AWB, |
| NoOpMeteringRegionCorrection, |
| ) |
| assertThat(meteringRectangles.size).isEqualTo(1) |
| // Default aspect ratio is greater than the aspect ratio of the crop region. So we need |
| // to add some padding at the top. |
| val expectedMeteringRectangle = MeteringRectangle( |
| 0, 20, 30, 60, FocusMeteringControl.METERING_WEIGHT_DEFAULT |
| ) |
| assertThat(meteringRectangles[0]).isEqualTo(expectedMeteringRectangle) |
| |
| val meteringPoint1 = FakeMeteringPointFactory().createPoint(0.5f, 0.5f) |
| val meteringRectangles1 = FocusMeteringControl.meteringRegionsFromMeteringPoints( |
| listOf(meteringPoint1), |
| 1, |
| Rect(0, 0, 400, 400), |
| Rational(4, 3), |
| FocusMeteringAction.FLAG_AF, |
| NoOpMeteringRegionCorrection, |
| ) |
| assertThat(meteringRectangles1.size).isEqualTo(1) |
| val expectedMeteringRectangle1 = MeteringRectangle( |
| 170, 170, 60, 60, FocusMeteringControl.METERING_WEIGHT_DEFAULT |
| ) |
| assertThat(meteringRectangles1[0]).isEqualTo(expectedMeteringRectangle1) |
| |
| val meteringPoint2 = FakeMeteringPointFactory().createPoint(1f, 1f) |
| val meteringRectangles2 = FocusMeteringControl.meteringRegionsFromMeteringPoints( |
| listOf(meteringPoint2), |
| 1, |
| Rect(0, 0, 400, 400), |
| Rational(4, 3), |
| FocusMeteringAction.FLAG_AF, |
| NoOpMeteringRegionCorrection, |
| ) |
| assertThat(meteringRectangles2.size).isEqualTo(1) |
| // Default aspect ratio is greater than the aspect ratio of the crop region. So we need |
| // to add some padding at the bottom. |
| val expectedMeteringRectangle2 = MeteringRectangle( |
| 370, 320, 30, 60, FocusMeteringControl.METERING_WEIGHT_DEFAULT |
| ) |
| assertThat(meteringRectangles2[0]).isEqualTo(expectedMeteringRectangle2) |
| } |
| |
| @Test |
| fun meteringRegionsFromMeteringPoint_fovAspectRatioLessThanCropAspectRatio() { |
| val meteringPoint = FakeMeteringPointFactory().createPoint(0.0f, 0.0f) |
| val meteringRectangles = FocusMeteringControl.meteringRegionsFromMeteringPoints( |
| listOf(meteringPoint), |
| 1, |
| Rect(0, 0, 400, 400), |
| Rational(3, 4), |
| FocusMeteringAction.FLAG_AF, |
| NoOpMeteringRegionCorrection, |
| ) |
| assertThat(meteringRectangles.size).isEqualTo(1) |
| val expectedMeteringRectangle = MeteringRectangle( |
| 20, 0, 60, 30, FocusMeteringControl.METERING_WEIGHT_DEFAULT |
| ) |
| assertThat(meteringRectangles[0]).isEqualTo(expectedMeteringRectangle) |
| |
| val meteringPoint1 = FakeMeteringPointFactory().createPoint(0.5f, 0.5f) |
| val meteringRectangles1 = FocusMeteringControl.meteringRegionsFromMeteringPoints( |
| listOf(meteringPoint1), |
| 1, |
| Rect(0, 0, 400, 400), |
| Rational(3, 4), |
| FocusMeteringAction.FLAG_AF, |
| NoOpMeteringRegionCorrection, |
| ) |
| assertThat(meteringRectangles1.size).isEqualTo(1) |
| val expectedMeteringRectangle1 = MeteringRectangle( |
| 170, 170, 60, 60, FocusMeteringControl.METERING_WEIGHT_DEFAULT |
| ) |
| assertThat(meteringRectangles1[0]).isEqualTo(expectedMeteringRectangle1) |
| |
| val meteringPoint2 = FakeMeteringPointFactory().createPoint(1f, 1f) |
| val meteringRectangles2 = FocusMeteringControl.meteringRegionsFromMeteringPoints( |
| listOf(meteringPoint2), |
| 1, |
| Rect(0, 0, 400, 400), |
| Rational(3, 4), |
| FocusMeteringAction.FLAG_AF, |
| NoOpMeteringRegionCorrection, |
| ) |
| assertThat(meteringRectangles2.size).isEqualTo(1) |
| val expectedMeteringRectangle2 = MeteringRectangle( |
| 320, 370, 60, 30, FocusMeteringControl.METERING_WEIGHT_DEFAULT |
| ) |
| assertThat(meteringRectangles2[0]).isEqualTo(expectedMeteringRectangle2) |
| } |
| |
| @Test |
| fun startFocusAndMetering_invalidPoint() = runTest { |
| val invalidPoint = pointFactory.createPoint(1f, 1.1f) |
| |
| val future = focusMeteringControl.startFocusAndMetering( |
| FocusMeteringAction.Builder(invalidPoint).build() |
| ) |
| |
| assertFutureFailedWithIllegalArgumentException(future) |
| } |
| |
| @Test |
| fun startFocusAndMetering_defaultPoint_3ARectsAreCorrect() = runTest { |
| 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?.get(0)?.rect).isEqualTo(M_RECT_1) |
| |
| assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1) |
| assertWithMessage("Wrong AF region").that(afRegions?.get(0)?.rect).isEqualTo(M_RECT_1) |
| |
| assertWithMessage("Wrong number of AWB regions").that(awbRegions?.size).isEqualTo(1) |
| assertWithMessage("Wrong AWB region").that(awbRegions?.get(0)?.rect).isEqualTo(M_RECT_1) |
| } |
| } |
| |
| @Test |
| fun startFocusAndMetering_defaultPoint_3ALocksAreCorrect() = runTest { |
| startFocusMeteringAndAwait(FocusMeteringAction.Builder(point1).build()) |
| |
| with(fakeRequestControl.focusMeteringCalls.last()) { |
| assertWithMessage("Wrong lock behavior for AE") |
| .that(aeLockBehavior).isNull() |
| assertWithMessage("Wrong lock behavior for AF") |
| .that(afLockBehavior).isEqualTo(Lock3ABehavior.IMMEDIATE) |
| assertWithMessage("Wrong lock behavior for AWB") |
| .that(awbLockBehavior).isNull() |
| } |
| } |
| |
| @Test |
| fun startFocusAndMetering_multiplePoints_3ARectsAreCorrect() = runTest { |
| // 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?.get(0)?.rect).isEqualTo(M_RECT_1) |
| assertWithMessage("Wrong AE region").that(aeRegions?.get(1)?.rect).isEqualTo(M_RECT_2) |
| assertWithMessage("Wrong AE region").that(aeRegions?.get(2)?.rect).isEqualTo(M_RECT_3) |
| |
| assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(3) |
| assertWithMessage("Wrong AF region").that(afRegions?.get(0)?.rect).isEqualTo(M_RECT_1) |
| assertWithMessage("Wrong AF region").that(afRegions?.get(1)?.rect).isEqualTo(M_RECT_2) |
| assertWithMessage("Wrong AF region").that(afRegions?.get(2)?.rect).isEqualTo(M_RECT_3) |
| |
| assertWithMessage("Wrong number of AWB regions").that(awbRegions?.size).isEqualTo(1) |
| assertWithMessage("Wrong AWB region").that(awbRegions?.get(0)?.rect).isEqualTo(M_RECT_1) |
| } |
| } |
| |
| @Test |
| @Config(maxSdk = 32) |
| fun startFocusAndMetering_AfRegionCorrectedByQuirk() { |
| ReflectionHelpers.setStaticField(Build::class.java, "BRAND", "Samsung") |
| |
| focusMeteringControl = initFocusMeteringControl(cameraId = CAMERA_ID_4) |
| |
| startFocusMeteringAndAwait( |
| FocusMeteringAction.Builder(point1) |
| .addPoint(point2) |
| .addPoint(point3) |
| .build() |
| ) |
| |
| // after flipping horizontally, left / right will be swapped. |
| val flippedRect1 = Rect( |
| SENSOR_WIDTH - M_RECT_1.right, M_RECT_1.top, |
| SENSOR_WIDTH - M_RECT_1.left, M_RECT_1.bottom |
| ) |
| val flippedRect2 = Rect( |
| SENSOR_WIDTH - M_RECT_2.right, M_RECT_2.top, |
| SENSOR_WIDTH - M_RECT_2.left, M_RECT_2.bottom |
| ) |
| val flippedRect3 = Rect( |
| SENSOR_WIDTH - M_RECT_3.right, M_RECT_3.top, |
| SENSOR_WIDTH - M_RECT_3.left, M_RECT_3.bottom |
| ) |
| |
| with(fakeRequestControl.focusMeteringCalls.last()) { |
| assertWithMessage("Wrong number of AE regions").that(aeRegions?.size).isEqualTo(3) |
| assertWithMessage("Wrong AE region").that(aeRegions?.get(0)?.rect).isEqualTo(M_RECT_1) |
| assertWithMessage("Wrong AE region").that(aeRegions?.get(1)?.rect).isEqualTo(M_RECT_2) |
| assertWithMessage("Wrong AE region").that(aeRegions?.get(2)?.rect).isEqualTo(M_RECT_3) |
| |
| assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(3) |
| assertWithMessage("Wrong AF region") |
| .that(afRegions?.get(0)?.rect).isEqualTo(flippedRect1) |
| assertWithMessage("Wrong AF region") |
| .that(afRegions?.get(1)?.rect).isEqualTo(flippedRect2) |
| assertWithMessage("Wrong AF region") |
| .that(afRegions?.get(2)?.rect).isEqualTo(flippedRect3) |
| |
| assertWithMessage("Wrong number of AWB regions") |
| .that(awbRegions?.size).isEqualTo(1) |
| assertWithMessage("Wrong AWB region").that(awbRegions?.get(0)?.rect).isEqualTo(M_RECT_1) |
| } |
| } |
| |
| @Test |
| fun startFocusAndMetering_multiplePointsVariousModes() = runTest { |
| // 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?.get(0)?.rect).isEqualTo(M_RECT_2) |
| assertWithMessage("Wrong AE region").that(aeRegions?.get(1)?.rect).isEqualTo(M_RECT_3) |
| |
| assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(2) |
| assertWithMessage("Wrong AF region").that(afRegions?.get(0)?.rect).isEqualTo(M_RECT_2) |
| assertWithMessage("Wrong AF region").that(afRegions?.get(1)?.rect).isEqualTo(M_RECT_3) |
| |
| assertWithMessage("Wrong number of AWB regions").that(awbRegions?.size).isEqualTo(1) |
| assertWithMessage("Wrong AWB region").that(awbRegions?.get(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?.get(0)?.rect).isEqualTo(M_RECT_3) |
| |
| assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1) |
| assertWithMessage("Wrong AF region").that(afRegions?.get(0)?.rect).isEqualTo(M_RECT_1) |
| |
| assertWithMessage("Wrong number of AWB regions").that(awbRegions?.size).isEqualTo(1) |
| assertWithMessage("Wrong AWB region").that(awbRegions?.get(0)?.rect).isEqualTo(M_RECT_2) |
| } |
| } |
| |
| @Test |
| fun cropRegionIsSet_resultBasedOnCropRegion() { |
| val cropWidth = 480 |
| val cropHeight = 360 |
| val cropRect = Rect( |
| SENSOR_WIDTH / 2 - cropWidth / 2, |
| SENSOR_HEIGHT / 2 - cropHeight / 2, |
| SENSOR_WIDTH / 2 + cropWidth / 2, SENSOR_HEIGHT / 2 + cropHeight / 2 |
| ) |
| |
| focusMeteringControl = initFocusMeteringControl( |
| cameraId = CAMERA_ID_0, |
| zoomCompat = FakeZoomCompat(croppedSensorArea = cropRect), |
| ) |
| |
| val centerPt = pointFactory.createPoint(0.5f, 0.5f) |
| startFocusMeteringAndAwait( |
| FocusMeteringAction.Builder(centerPt).build() |
| ) |
| |
| val areaWidth = (MeteringPointFactory.getDefaultPointSize() * cropRect.width()).toInt() |
| val areaHeight = (MeteringPointFactory.getDefaultPointSize() * cropRect.height()).toInt() |
| val adjustedRect = Rect( |
| cropRect.centerX() - areaWidth / 2, |
| cropRect.centerY() - areaHeight / 2, |
| cropRect.centerX() + areaWidth / 2, |
| cropRect.centerY() + areaHeight / 2 |
| ) |
| with(fakeRequestControl.focusMeteringCalls.last()) { |
| assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1) |
| assertWithMessage("Wrong AF region") |
| .that(afRegions?.get(0)?.rect).isEqualTo(adjustedRect) |
| } |
| } |
| |
| @Test |
| fun cropRegionIsSetTwice_resultAlwaysBasedOnCurrentCropRegion() { |
| val cropWidth = 480 |
| val cropHeight = 360 |
| val cropRect = Rect( |
| SENSOR_WIDTH / 2 - cropWidth / 2, |
| SENSOR_HEIGHT / 2 - cropHeight / 2, |
| SENSOR_WIDTH / 2 + cropWidth / 2, SENSOR_HEIGHT / 2 + cropHeight / 2 |
| ) |
| |
| val zoomCompat = FakeZoomCompat(croppedSensorArea = Rect(0, 0, 640, 480)) |
| focusMeteringControl = initFocusMeteringControl( |
| cameraId = CAMERA_ID_0, |
| zoomCompat = zoomCompat, |
| ) |
| |
| val centerPt = pointFactory.createPoint(0.5f, 0.5f) |
| startFocusMeteringAndAwait( |
| FocusMeteringAction.Builder(centerPt).build() |
| ) |
| |
| zoomCompat.croppedSensorArea = cropRect |
| startFocusMeteringAndAwait( |
| FocusMeteringAction.Builder(centerPt).build() |
| ) |
| |
| val areaWidth = (MeteringPointFactory.getDefaultPointSize() * cropRect.width()).toInt() |
| val areaHeight = (MeteringPointFactory.getDefaultPointSize() * cropRect.height()).toInt() |
| val adjustedRect = Rect( |
| cropRect.centerX() - areaWidth / 2, |
| cropRect.centerY() - areaHeight / 2, |
| cropRect.centerX() + areaWidth / 2, |
| cropRect.centerY() + areaHeight / 2 |
| ) |
| with(fakeRequestControl.focusMeteringCalls.last()) { |
| assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1) |
| assertWithMessage("Wrong AF region") |
| .that(afRegions?.get(0)?.rect).isEqualTo(adjustedRect) |
| } |
| } |
| |
| @Test |
| fun previewFovAdjusted_16by9_to_4by3() { |
| // use 16:9 preview aspect ratio with sensor region of 4:3 (camera 0) |
| focusMeteringControl = initFocusMeteringControl( |
| cameraId = CAMERA_ID_0, |
| useCases = setOf(createPreview(Size(1920, 1080))), |
| ) |
| |
| startFocusMeteringAndAwait( |
| FocusMeteringAction.Builder(point1).build() |
| ) |
| |
| with(fakeRequestControl.focusMeteringCalls.last()) { |
| assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1) |
| assertWithMessage("Wrong AF region") |
| .that(afRegions?.get(0)?.rect).isEqualTo(M_RECT_PVIEW_RATIO_16x9_SENSOR_640x480) |
| } |
| } |
| |
| @Test |
| fun previewFovAdjusted_4by3_to_16by9() { |
| // use 4:3 preview aspect ratio with sensor region of 16:9 (camera 1) |
| focusMeteringControl = initFocusMeteringControl( |
| cameraId = CAMERA_ID_1, |
| useCases = setOf(createPreview(Size(640, 480))), |
| zoomCompat = FakeZoomCompat(croppedSensorArea = Rect(0, 0, 1920, 1080)) |
| ) |
| |
| startFocusMeteringAndAwait( |
| FocusMeteringAction.Builder(point1).build() |
| ) |
| |
| with(fakeRequestControl) { |
| assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1) |
| assertWithMessage("Wrong AF region") |
| .that(afRegions?.get(0)?.rect).isEqualTo(M_RECT_PVIEW_RATIO_4x3_SENSOR_1920x1080) |
| } |
| } |
| |
| @Test |
| fun customFovAdjusted() { |
| // 16:9 to 4:3 |
| val useCase = FakeUseCase() |
| useCase.updateSuggestedStreamSpec(StreamSpec.builder(Size(1920, 1080)).build()) |
| |
| val factory = SurfaceOrientedMeteringPointFactory(1.0f, 1.0f, useCase) |
| val point = factory.createPoint(0f, 0f) |
| |
| focusMeteringControl = initFocusMeteringControl( |
| cameraId = CAMERA_ID_0, |
| useCases = setOf(createPreview(Size(640, 480))), |
| ) |
| |
| startFocusMeteringAndAwait( |
| FocusMeteringAction.Builder(point).build() |
| ) |
| |
| with(fakeRequestControl.focusMeteringCalls.last()) { |
| assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1) |
| assertWithMessage("Wrong AF region") |
| .that(afRegions?.get(0)?.rect).isEqualTo(M_RECT_PVIEW_RATIO_16x9_SENSOR_640x480) |
| } |
| } |
| |
| @Test |
| fun previewRatioNotUsed_whenPreviewUseCaseIsRemoved() { |
| // add 16:9 aspect ratio Preview with sensor region of 4:3 (camera 0), then remove Preview |
| focusMeteringControl = initFocusMeteringControl( |
| CAMERA_ID_0, |
| useCases = setOf(createPreview(Size(1920, 1080))), |
| ) |
| fakeUseCaseCamera.runningUseCases = emptySet() |
| focusMeteringControl.onRunningUseCasesChanged() |
| |
| startFocusMeteringAndAwait( |
| FocusMeteringAction.Builder(point1).build() |
| ) |
| |
| // point1 = (0, 0) is considered as center point of metering rectangle. |
| // Since previewAspectRatio is not set, it will be same as cropRegionAspectRatio |
| // which is the size of SENSOR_1 in this test. So the point is not adjusted, |
| // and simply M_RECT_1 (metering rectangle of point1 with SENSOR_1) should be used. |
| with(fakeRequestControl.focusMeteringCalls.last()) { |
| assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1) |
| assertWithMessage("Wrong AF region").that(afRegions?.get(0)?.rect).isEqualTo(M_RECT_1) |
| } |
| } |
| |
| @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?.get(0)?.rect?.width()).isEqualTo((SENSOR_WIDTH * 1.0f).toInt()) |
| assertWithMessage("Wrong AF region height") |
| .that(afRegions?.get(0)?.rect?.height()).isEqualTo((SENSOR_HEIGHT * 1.0f).toInt()) |
| |
| assertWithMessage("Wrong AF region width") |
| .that(afRegions?.get(1)?.rect?.width()).isEqualTo((SENSOR_WIDTH * 0.5f).toInt()) |
| assertWithMessage("Wrong AF region height") |
| .that(afRegions?.get(1)?.rect?.height()).isEqualTo((SENSOR_HEIGHT * 0.5f).toInt()) |
| |
| assertWithMessage("Wrong AF region width") |
| .that(afRegions?.get(2)?.rect?.width()).isEqualTo((SENSOR_WIDTH * 0.1f).toInt()) |
| assertWithMessage("Wrong AF region height") |
| .that(afRegions?.get(2)?.rect?.height()).isEqualTo((SENSOR_HEIGHT * 0.1f).toInt()) |
| } |
| } |
| |
| @Test |
| fun startFocusMetering_AfLocked_completesWithFocusTrue() = runTest { |
| fakeRequestControl.focusMeteringResult = CompletableDeferred( |
| 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() = runTest { |
| fakeRequestControl.focusMeteringResult = CompletableDeferred( |
| 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 |
| fun startFocusMetering_AfStateIsNull_completesWithFocusTrue() = runTest { |
| fakeRequestControl.focusMeteringResult = CompletableDeferred( |
| 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 |
| fun startFocusMeteringAfRequested_CameraNotSupportAfAuto_CompletesWithTrue() = runTest { |
| // 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) |
| } |
| |
| @MediumTest |
| @Test |
| fun startFocusMetering_cancelledBeforeCompletion_failsWithOperationCanceledOperation() = |
| runTest { |
| // Arrange. Set a delay CompletableDeferred |
| fakeRequestControl.focusMeteringResult = CompletableDeferred<Result3A>().apply { |
| async(Dispatchers.Default) { |
| delay(500) |
| complete( |
| 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) |
| |
| // Act. |
| focusMeteringControl.cancelFocusAndMeteringAsync() |
| |
| // Assert. |
| assertFutureFailedWithOperationCancellation(future) |
| } |
| |
| @Test |
| fun startThenCancelThenStart_previous2FuturesFailsWithOperationCanceled() = runTest { |
| // Arrange. Set a never complete CompletableDeferred |
| fakeRequestControl.focusMeteringResult = CompletableDeferred() |
| fakeRequestControl.cancelFocusMeteringResult = CompletableDeferred() |
| val action = FocusMeteringAction.Builder(point1).build() |
| |
| // Act. |
| val result1 = focusMeteringControl.startFocusAndMetering(action) |
| val result2 = focusMeteringControl.cancelFocusAndMeteringAsync().asListenableFuture() |
| focusMeteringControl.startFocusAndMetering(action) |
| |
| // Assert. |
| assertFutureFailedWithOperationCancellation(result1) |
| assertFutureFailedWithOperationCancellation(result2) |
| } |
| |
| @MediumTest |
| @Test |
| fun startMultipleActions_allExceptLatestAreCancelled() = runTest { |
| // Arrange. |
| // Set a CompletableDeferred that is completed later |
| fakeRequestControl.focusMeteringResult = CompletableDeferred() |
| |
| val action = FocusMeteringAction.Builder(point1).build() |
| val result1 = focusMeteringControl.startFocusAndMetering(action) |
| val result2 = focusMeteringControl.startFocusAndMetering(action) |
| val result3 = focusMeteringControl.startFocusAndMetering(action) |
| |
| // Simulate CompletableDeferred completion is delayed and all tasks completing before then |
| advanceUntilIdle() |
| fakeRequestControl.focusMeteringResult.complete( |
| Result3A( |
| status = Result3A.Status.OK, |
| frameMetadata = FakeFrameMetadata( |
| extraMetadata = mapOf( |
| CONTROL_AF_STATE to CONTROL_AF_STATE_FOCUSED_LOCKED |
| ) |
| ) |
| ) |
| ) |
| |
| assertFutureFailedWithOperationCancellation(result1) |
| assertFutureFailedWithOperationCancellation(result2) |
| assertFutureFocusCompleted(result3, true) |
| } |
| |
| @Test |
| fun startFocusMetering_focusedThenCancel_futureStillCompletes() = runTest { |
| // Arrange. |
| fakeRequestControl.focusMeteringResult = CompletableDeferred( |
| 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).apply { |
| advanceUntilIdle() |
| get(3, TimeUnit.SECONDS) |
| } |
| |
| // Act. Cancel it and then ensure the returned ListenableFuture still completes. |
| focusMeteringControl.cancelFocusAndMeteringAsync().join() |
| |
| // Assert. |
| assertFutureFocusCompleted(result, true) |
| } |
| |
| @Test |
| fun startFocusMeteringAFAEAWB_noPointsAreSupported_failFuture() = runTest { |
| 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) |
| |
| assertFutureFailedWithIllegalArgumentException(future) |
| } |
| |
| @Test |
| fun startFocusMeteringAEAWB_noPointsAreSupported_failFuture() = runTest { |
| val focusMeteringControl = initFocusMeteringControl(CAMERA_ID_3) |
| val action = FocusMeteringAction.Builder( |
| point1, |
| FocusMeteringAction.FLAG_AE or FocusMeteringAction.FLAG_AWB |
| ).build() |
| val future = focusMeteringControl.startFocusAndMetering(action) |
| |
| assertFutureFailedWithIllegalArgumentException(future) |
| } |
| |
| @Test |
| fun startFocusMeteringAFAWB_noPointsAreSupported_failFuture() = runTest { |
| val focusMeteringControl = initFocusMeteringControl(CAMERA_ID_3) |
| val action = FocusMeteringAction.Builder( |
| point1, |
| FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AWB |
| ).build() |
| val future = focusMeteringControl.startFocusAndMetering(action) |
| |
| assertFutureFailedWithIllegalArgumentException(future) |
| } |
| |
| @Test |
| fun startFocusMetering_morePointsThanSupported_futureCompletes() = runTest { |
| // 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, even though focus is not locked. |
| fakeRequestControl.focusMeteringResult = CompletableDeferred( |
| Result3A( |
| status = Result3A.Status.OK, |
| frameMetadata = FakeFrameMetadata( |
| extraMetadata = mapOf( |
| CONTROL_AF_STATE to CONTROL_AF_STATE_NOT_FOCUSED_LOCKED |
| ) |
| ) |
| ) |
| ) |
| |
| 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 |
| fun startFocusMetering_noPointsAreValid_failFuture() = runTest { |
| 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) |
| |
| assertFutureFailedWithIllegalArgumentException(future) |
| } |
| |
| @Test |
| fun isFocusMeteringSupported_allSupportedPoints_shouldReturnTrue() = runTest { |
| 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() = runTest { |
| // 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() = runTest { |
| 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() = runTest { |
| 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() |
| } |
| |
| @Test |
| fun cancelFocusMetering_actionIsCanceledAndFutureCompletes() = runTest { |
| // Arrange. Set a never complete CompletableDeferred |
| fakeRequestControl.focusMeteringResult = CompletableDeferred() |
| val action = FocusMeteringAction.Builder(point1).build() |
| |
| // Act. |
| val actionResult = focusMeteringControl.startFocusAndMetering(action) |
| val cancelResult = focusMeteringControl.cancelFocusAndMeteringAsync().asListenableFuture() |
| |
| // Assert. |
| assertFutureFailedWithOperationCancellation(actionResult) |
| assertThat(cancelResult[3, TimeUnit.SECONDS]?.status).isEqualTo(Result3A.Status.OK) |
| } |
| |
| @MediumTest |
| @Test |
| fun cancelFocusAndMetering_autoCancelIsDisabled(): Unit = runTest { |
| // Arrange. Set a never complete CompletableDeferred |
| fakeRequestControl.focusMeteringResult = CompletableDeferred() |
| val autoCancelDuration: Long = 500 |
| val action = FocusMeteringAction.Builder(point1) |
| .setAutoCancelDuration(autoCancelDuration, TimeUnit.MILLISECONDS) |
| .build() |
| val autoFocusTimeoutDuration: Long = 1000 |
| focusMeteringControl.startFocusAndMetering(action, autoFocusTimeoutDuration) |
| |
| // Act. Call cancel before the auto cancel occur. |
| focusMeteringControl.cancelFocusAndMeteringAsync().await() |
| advanceUntilIdle() |
| assertThat(fakeRequestControl.cancelFocusMeteringCallCount).isEqualTo(1) |
| |
| // Assert. cancelFocusMetering only be invoked once. |
| delay(autoFocusTimeoutDuration) |
| advanceUntilIdle() |
| assertThat(fakeRequestControl.cancelFocusMeteringCallCount).isEqualTo(1) |
| } |
| |
| @MediumTest |
| @Test |
| fun autoCancelDuration_completeWithIsFocusSuccessfulFalse() = runTest { |
| // Arrange. |
| // Set an incomplete CompletableDeferred |
| fakeRequestControl.focusMeteringResult = CompletableDeferred() |
| |
| val autoCancelTimeOutDuration: Long = 500 |
| val action = FocusMeteringAction.Builder(point1) |
| .setAutoCancelDuration(autoCancelTimeOutDuration, TimeUnit.MILLISECONDS) |
| .build() |
| |
| // Act. |
| val future = focusMeteringControl.startFocusAndMetering( |
| action, |
| autoCancelTimeOutDuration |
| ) |
| |
| // simulate UseCaseCamera timing out during auto focus |
| fakeRequestControl.focusMeteringResult.complete( |
| Result3A(status = Result3A.Status.TIME_LIMIT_REACHED) |
| ) |
| |
| // Assert. |
| assertFutureFocusCompleted(future, false) |
| } |
| |
| @MediumTest |
| @Test |
| fun shorterAutoCancelDuration_cancelIsCalled_completeActionFutureIsNotCalled(): Unit = |
| runTest { |
| // Arrange. |
| // Set an incomplete CompletableDeferred |
| fakeRequestControl.focusMeteringResult = CompletableDeferred() |
| fakeRequestControl.awaitFocusMetering = false // simulates async |
| val autoCancelDuration: Long = 500 |
| val action = FocusMeteringAction.Builder(point1) |
| .setAutoCancelDuration(autoCancelDuration, TimeUnit.MILLISECONDS) |
| .build() |
| val autoFocusTimeoutDuration: Long = 1000 |
| |
| // Act. |
| val future = focusMeteringControl.startFocusAndMetering( |
| action, |
| autoFocusTimeoutDuration |
| ) |
| |
| // simulate UseCaseCamera timing out during auto focus |
| advanceUntilIdle() // ensures all operations are triggered already |
| delay(autoFocusTimeoutDuration) |
| fakeRequestControl.focusMeteringResult.complete( |
| Result3A(status = Result3A.Status.TIME_LIMIT_REACHED) |
| ) |
| |
| // Assert. |
| assertFutureFailedWithOperationCancellation(future) |
| } |
| |
| @MediumTest |
| @Test |
| fun longerAutoCancelDuration_completeWithIsFocusSuccessfulFalse() = runTest { |
| // Arrange. |
| // Set an incomplete CompletableDeferred |
| fakeRequestControl.focusMeteringResult = CompletableDeferred() |
| |
| val autoCancelDuration: Long = 1000 |
| val action = FocusMeteringAction.Builder(point1) |
| .setAutoCancelDuration(autoCancelDuration, TimeUnit.MILLISECONDS) |
| .build() |
| val autoFocusTimeoutDuration: Long = 500 |
| |
| // Act. |
| val future = focusMeteringControl.startFocusAndMetering(action, autoFocusTimeoutDuration) |
| |
| // simulate UseCaseCamera timing out during auto focus |
| fakeRequestControl.focusMeteringResult.complete( |
| Result3A(status = Result3A.Status.TIME_LIMIT_REACHED) |
| ) |
| |
| // Assert. |
| assertFutureFocusCompleted(future, false) |
| } |
| |
| @MediumTest |
| @Test |
| fun autoCancelDurationDisabled_completeAfterAutoFocusTimeoutDuration(): Unit = runTest { |
| // Arrange. |
| // Set an incomplete CompletableDeferred |
| fakeRequestControl.focusMeteringResult = CompletableDeferred() |
| |
| val autoCancelDuration: Long = 500 |
| val action = FocusMeteringAction.Builder(point1) |
| .setAutoCancelDuration(autoCancelDuration, TimeUnit.MILLISECONDS) |
| .disableAutoCancel() |
| .build() |
| val autoFocusTimeoutTestDuration: Long = 1000 |
| |
| // Act. |
| val future = focusMeteringControl.startFocusAndMetering( |
| action, autoFocusTimeoutTestDuration |
| ) |
| |
| // simulate UseCaseCamera timing out during auto focus |
| fakeRequestControl.focusMeteringResult.complete( |
| Result3A(status = Result3A.Status.TIME_LIMIT_REACHED) |
| ) |
| |
| // Assert. |
| assertFutureFocusCompleted(future, false) |
| } |
| |
| @Test |
| fun startFocusMetering_afAutoModeIsSet() = runTest { |
| // Arrange. |
| val action = FocusMeteringAction |
| .Builder(point1, FocusMeteringAction.FLAG_AF) |
| .setAutoCancelDuration(8, TimeUnit.SECONDS) |
| .build() |
| val state3AControl = createState3AControl(CAMERA_ID_0) |
| focusMeteringControl = initFocusMeteringControl( |
| cameraId = CAMERA_ID_0, |
| useCases = setOf(createPreview(Size(1920, 1080))), |
| useCaseThreads = fakeUseCaseThreads, |
| state3AControl = state3AControl, |
| ) |
| |
| // Act. |
| focusMeteringControl.startFocusAndMeteringAndAdvanceTestScope( |
| this, |
| action, |
| testScopeAdvanceTimeMillis = 6000, // not cancelled yet |
| )[5, TimeUnit.SECONDS] |
| |
| // Assert. |
| assertThat( |
| state3AControl.preferredFocusMode |
| ).isEqualTo(CaptureRequest.CONTROL_AF_MODE_AUTO) |
| } |
| |
| @Test |
| fun startFocusMetering_afModeResetAfterAutoCancel() = runTest { |
| // Arrange. |
| val action = FocusMeteringAction |
| .Builder(point1, FocusMeteringAction.FLAG_AF) |
| .build() |
| val state3AControl = createState3AControl(CAMERA_ID_0) |
| focusMeteringControl = initFocusMeteringControl( |
| cameraId = CAMERA_ID_0, |
| useCases = setOf(createPreview(Size(1920, 1080))), |
| useCaseThreads = fakeUseCaseThreads, |
| state3AControl = state3AControl, |
| ) |
| |
| // Act. |
| focusMeteringControl.startFocusAndMeteringAndAdvanceTestScope( |
| this, |
| action, |
| )[5, TimeUnit.SECONDS] |
| |
| // Assert. |
| assertThat( |
| state3AControl.preferredFocusMode |
| ).isNull() |
| } |
| |
| @Test |
| fun startFocusMetering_AfNotInvolved_afAutoModeNotSet() = runTest { |
| // Arrange. |
| val action = FocusMeteringAction.Builder( |
| point1, |
| FocusMeteringAction.FLAG_AE or FocusMeteringAction.FLAG_AWB |
| ).build() |
| val state3AControl = createState3AControl(CAMERA_ID_0) |
| focusMeteringControl = initFocusMeteringControl( |
| cameraId = CAMERA_ID_0, |
| useCases = setOf(createPreview(Size(1920, 1080))), |
| useCaseThreads = fakeUseCaseThreads, |
| state3AControl = state3AControl, |
| ) |
| |
| // Act. |
| focusMeteringControl.startFocusAndMeteringAndAdvanceTestScope( |
| this, |
| action |
| )[5, TimeUnit.SECONDS] |
| |
| // Assert. |
| assertThat( |
| state3AControl.preferredFocusMode |
| ).isEqualTo(null) |
| } |
| |
| @Test |
| fun startAndThenCancel_afAutoModeNotSet(): Unit = runTest { |
| // Arrange. |
| val action = FocusMeteringAction.Builder(point1, FocusMeteringAction.FLAG_AF).build() |
| val state3AControl = createState3AControl(CAMERA_ID_0) |
| focusMeteringControl = initFocusMeteringControl( |
| cameraId = CAMERA_ID_0, |
| useCases = setOf(createPreview(Size(1920, 1080))), |
| useCaseThreads = fakeUseCaseThreads, |
| state3AControl = state3AControl, |
| ) |
| |
| // Act. |
| focusMeteringControl.startFocusAndMeteringAndAdvanceTestScope( |
| this, |
| action, |
| )[5, TimeUnit.SECONDS] |
| focusMeteringControl.cancelFocusAndMeteringAsync().join() |
| |
| // Assert. |
| advanceUntilIdle() |
| assertThat( |
| state3AControl.preferredFocusMode |
| ).isEqualTo(null) |
| } |
| |
| @Test |
| fun startFocusMetering_submitFailed_failsWithOperationCanceledOperation() = runTest { |
| fakeRequestControl.focusMeteringResult = CompletableDeferred( |
| Result3A( |
| status = Result3A.Status.SUBMIT_FAILED, |
| frameMetadata = null, |
| ) |
| ) |
| |
| val result = focusMeteringControl.startFocusAndMetering( |
| FocusMeteringAction.Builder(point1).build() |
| ) |
| |
| assertFutureFailedWithOperationCancellation(result) |
| } |
| |
| @Test |
| fun startFocusMetering_noAfPoint_futureCompletesWithFocusUnsuccessful() = runTest { |
| val focusMeteringControl = initFocusMeteringControl(CAMERA_ID_1) |
| val action = FocusMeteringAction.Builder( |
| point1, |
| FocusMeteringAction.FLAG_AE or FocusMeteringAction.FLAG_AWB |
| ).build() |
| val future = focusMeteringControl.startFocusAndMetering(action) |
| |
| assertFutureFocusCompleted(future, false) |
| } |
| |
| @Test |
| fun startFocusMetering_frameMetadataNullWithOkStatus_futureCompletesWithFocusSuccessful() = |
| runTest { |
| /** |
| * According to [androidx.camera.camera2.pipe.graph.Controller3A.lock3A] method |
| * documentation, if the operation is not supported by the camera device, then this |
| * method returns early with Result3A made of 'OK' status and 'null' metadata. |
| */ |
| fakeRequestControl.focusMeteringResult = CompletableDeferred( |
| Result3A( |
| status = Result3A.Status.OK, |
| frameMetadata = null, |
| ) |
| ) |
| |
| val focusMeteringControl = initFocusMeteringControl(CAMERA_ID_0) |
| val future = focusMeteringControl.startFocusAndMetering( |
| FocusMeteringAction.Builder( |
| point1 |
| ).build() |
| ) |
| |
| assertFutureFocusCompleted(future, false) |
| } |
| |
| @Test |
| fun startFocusMetering_noAePoint_aeRegionsSetToDefault() = runTest { |
| startFocusMeteringAndAwait( |
| FocusMeteringAction.Builder( |
| point1, FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AWB |
| ).build() |
| ) |
| |
| with(fakeRequestControl.focusMeteringCalls.last()) { |
| assertWithMessage("Wrong AE regions").that(aeRegions) |
| .isEqualTo(CameraGraph.Constants3A.METERING_REGIONS_DEFAULT.toList()) |
| } |
| } |
| |
| @Test |
| fun startFocusMetering_noAfPoint_noFocusMeteringStart() = runTest { |
| startFocusMeteringAndAwait( |
| FocusMeteringAction.Builder( |
| point1, FocusMeteringAction.FLAG_AE or FocusMeteringAction.FLAG_AWB |
| ).build() |
| ) |
| |
| assertWithMessage("Focus metering started despite no AF point") |
| .that(fakeRequestControl.focusMeteringCalls) |
| .isEmpty() |
| } |
| |
| @Test |
| fun startFocusMetering_noAfPoint_aeAwbRegionsUpdated() = runTest { |
| startFocusMeteringAndAwait( |
| FocusMeteringAction.Builder( |
| point1, FocusMeteringAction.FLAG_AE or FocusMeteringAction.FLAG_AWB |
| ).build() |
| ) |
| |
| with(fakeRequestControl) { |
| assertWithMessage("Wrong AF regions").that(afRegions) |
| .isEqualTo(CameraGraph.Constants3A.METERING_REGIONS_DEFAULT.toList()) |
| |
| // ensuring not default, exact value checked in other tests |
| assertWithMessage("Wrong AE regions").that(aeRegions) |
| .isNotEqualTo(CameraGraph.Constants3A.METERING_REGIONS_DEFAULT.toList()) |
| assertWithMessage("Wrong AWB regions").that(awbRegions) |
| .isNotEqualTo(CameraGraph.Constants3A.METERING_REGIONS_DEFAULT.toList()) |
| } |
| } |
| |
| @Test |
| fun startFocusMetering_noAwbPoint_awbRegionsSetToDefault() = runTest { |
| startFocusMeteringAndAwait( |
| FocusMeteringAction.Builder( |
| point1, FocusMeteringAction.FLAG_AE or FocusMeteringAction.FLAG_AF |
| ).build() |
| ) |
| |
| with(fakeRequestControl.focusMeteringCalls.last()) { |
| assertWithMessage("Wrong AWB regions").that(awbRegions) |
| .isEqualTo(CameraGraph.Constants3A.METERING_REGIONS_DEFAULT.toList()) |
| } |
| } |
| |
| @Test |
| fun startFocusMetering_onlyAfSupported_unsupportedRegionsNotConfigured() = runTest { |
| // camera 5 supports 1 AF and 0 AE/AWB regions |
| focusMeteringControl = initFocusMeteringControl(cameraId = CAMERA_ID_5) |
| |
| startFocusMeteringAndAwait( |
| FocusMeteringAction.Builder( |
| point1, |
| FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE or |
| FocusMeteringAction.FLAG_AWB |
| ).build() |
| ) |
| |
| with(fakeRequestControl.focusMeteringCalls.last()) { |
| assertWithMessage("Wrong number of AE regions").that(aeRegions).isNull() |
| assertWithMessage("Wrong lock behavior for AE").that(aeLockBehavior).isNull() |
| |
| assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1) |
| assertWithMessage("Wrong lock behavior for AE") |
| .that(afLockBehavior).isEqualTo(Lock3ABehavior.IMMEDIATE) |
| |
| assertWithMessage("Wrong number of AWB regions").that(awbRegions).isNull() |
| assertWithMessage("Wrong lock behavior for AWB").that(awbLockBehavior).isNull() |
| } |
| } |
| |
| private fun TestScope.assertFutureFocusCompleted( |
| future: ListenableFuture<FocusMeteringResult>, |
| isFocused: Boolean |
| ) { |
| advanceUntilIdle() |
| val focusMeteringResult = future[3, TimeUnit.SECONDS] |
| assertThat(focusMeteringResult.isFocusSuccessful).isEqualTo(isFocused) |
| } |
| |
| private fun <T> TestScope.assertFutureFailedWithIllegalArgumentException( |
| future: ListenableFuture<T> |
| ) { |
| advanceUntilIdle() |
| assertThrows(ExecutionException::class.java) { |
| future[3, TimeUnit.SECONDS] |
| }.apply { |
| assertThat(cause).isInstanceOf(IllegalArgumentException::class.java) |
| } |
| } |
| |
| private fun <T> assertFutureFailedWithOperationCancellation( |
| future: ListenableFuture<T> |
| ) { |
| assertThrows(ExecutionException::class.java) { |
| future[3, TimeUnit.SECONDS] |
| }.apply { |
| assertThat(cause).isInstanceOf(CameraControl.OperationCanceledException::class.java) |
| } |
| } |
| |
| private fun <T> TestScope.assertFutureFailedWithOperationCancellation( |
| future: ListenableFuture<T> |
| ) { |
| advanceUntilIdle() |
| this@FocusMeteringControlTest.assertFutureFailedWithOperationCancellation(future) |
| } |
| |
| 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) = runTest { |
| startFocusMetering(action) |
| focusMeteringResultCallback.await() |
| } |
| |
| private val fakeUseCaseCamera = object : UseCaseCamera { |
| override var runningUseCases = setOf<UseCase>() |
| |
| 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 initFocusMeteringControl( |
| cameraId: String, |
| useCases: Set<UseCase> = emptySet(), |
| useCaseThreads: UseCaseThreads = fakeUseCaseThreads, |
| state3AControl: State3AControl = createState3AControl(cameraId), |
| zoomCompat: ZoomCompat = FakeZoomCompat() |
| ) = FocusMeteringControl( |
| cameraPropertiesMap[cameraId]!!, |
| MeteringRegionCorrection.Bindings.provideMeteringRegionCorrection( |
| CameraQuirks( |
| cameraPropertiesMap[cameraId]!!.metadata, |
| StreamConfigurationMapCompat( |
| StreamConfigurationMapBuilder.newBuilder().build(), |
| OutputSizesCorrector( |
| cameraPropertiesMap[cameraId]!!.metadata, |
| StreamConfigurationMapBuilder.newBuilder().build() |
| ), |
| ) |
| ) |
| ), |
| state3AControl, |
| useCaseThreads, |
| zoomCompat |
| ).apply { |
| fakeUseCaseCamera.runningUseCases = useCases |
| useCaseCamera = fakeUseCaseCamera |
| onRunningUseCasesChanged() |
| } |
| |
| 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 |
| ) |
| |
| // **** Camera 4 characteristics (same as Camera 0, but includes LENS_FACING_FRONT) **** // |
| val characteristics4 = characteristics0 + mapOf( |
| CameraCharacteristics.LENS_FACING to CameraCharacteristics.LENS_FACING_FRONT |
| ) |
| |
| cameraPropertiesMap[CAMERA_ID_4] = initCameraProperties( |
| CAMERA_ID_4, |
| characteristics4 |
| ) |
| |
| // **** Camera 5 characteristics (same as Camera 0, but supports AF regions only) **** // |
| val characteristics5 = characteristics0 + mapOf( |
| CameraCharacteristics.CONTROL_MAX_REGIONS_AF to 3, |
| CameraCharacteristics.CONTROL_MAX_REGIONS_AE to 0, |
| CameraCharacteristics.CONTROL_MAX_REGIONS_AWB to 0 |
| ) |
| |
| cameraPropertiesMap[CAMERA_ID_5] = initCameraProperties( |
| CAMERA_ID_5, |
| characteristics5 |
| ) |
| } |
| |
| private fun createPreview(suggestedStreamSpecResolution: Size) = |
| Preview.Builder() |
| .setCaptureOptionUnpacker { _, _ -> } |
| .setSessionOptionUnpacker { _, _, _ -> } |
| .build().apply { |
| setSurfaceProvider( |
| CameraXExecutors.mainThreadExecutor(), |
| SurfaceTextureProvider.createSurfaceTextureProvider() |
| ) |
| }.also { |
| it.bindToCamera(FakeCamera("0"), null, null) |
| it.updateSuggestedStreamSpec( |
| StreamSpec.builder(suggestedStreamSpecResolution).build() |
| ) |
| } |
| |
| private fun createState3AControl( |
| cameraId: String = CAMERA_ID_0, |
| properties: CameraProperties = cameraPropertiesMap[cameraId]!!, |
| useCaseCamera: UseCaseCamera = fakeUseCaseCamera, |
| ) = FakeState3AControlCreator.createState3AControl( |
| properties, |
| useCaseCamera |
| ) |
| |
| private fun FocusMeteringControl.startFocusAndMeteringAndAdvanceTestScope( |
| testScope: TestScope, |
| action: FocusMeteringAction, |
| autoFocusTimeoutMs: Long? = null, |
| testScopeAdvanceTimeMillis: Long? = null, |
| ): ListenableFuture<FocusMeteringResult> { |
| val future = autoFocusTimeoutMs?.let { |
| startFocusAndMetering(action, it) |
| } ?: run { |
| startFocusAndMetering(action) |
| } |
| if (testScopeAdvanceTimeMillis == null) { |
| testScope.advanceUntilIdle() |
| } else { |
| testScope.advanceTimeBy(testScopeAdvanceTimeMillis) |
| } |
| return future |
| } |
| } |