blob: 1666ff247b7b6b7f2466fcc3bcb2bf15a9e1967e [file] [log] [blame]
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.camera.camera2.pipe.integration.impl
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CaptureRequest
import android.os.Build
import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpAutoFlashAEModeDisabler
import androidx.camera.camera2.pipe.integration.testing.FakeCameraProperties
import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCamera
import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCameraRequestControl
import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
import androidx.camera.core.CameraControl
import androidx.camera.core.ImageCapture
import androidx.testutils.assertThrows
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
@RunWith(RobolectricCameraPipeTestRunner::class)
@DoNotInstrument
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
class FlashControlTest {
private val fakeUseCaseThreads by lazy {
val executor = MoreExecutors.directExecutor()
val dispatcher = executor.asCoroutineDispatcher()
val cameraScope = CoroutineScope(Job() + dispatcher)
UseCaseThreads(
cameraScope,
executor,
dispatcher,
)
}
private val metadata = FakeCameraMetadata(
mapOf(
CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES to intArrayOf(
CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH,
CaptureRequest.CONTROL_AE_MODE_ON,
CaptureRequest.CONTROL_AE_MODE_OFF,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH
),
),
)
private val fakeRequestControl = FakeUseCaseCameraRequestControl()
private val fakeUseCaseCamera = FakeUseCaseCamera(requestControl = fakeRequestControl)
private val state3AControl =
State3AControl(FakeCameraProperties(metadata), NoOpAutoFlashAEModeDisabler).apply {
useCaseCamera = fakeUseCaseCamera
}
private lateinit var flashControl: FlashControl
@Before
fun setUp() {
flashControl = FlashControl(
state3AControl = state3AControl,
threads = fakeUseCaseThreads,
)
flashControl.useCaseCamera = fakeUseCaseCamera
}
@Test
fun setFlash_whenInactive(): Unit = runBlocking {
val fakeUseCaseCamera = FakeUseCaseCamera()
val fakeCameraProperties = FakeCameraProperties()
val flashControl = FlashControl(
State3AControl(fakeCameraProperties, NoOpAutoFlashAEModeDisabler).apply {
useCaseCamera = fakeUseCaseCamera
},
fakeUseCaseThreads,
)
assertThrows<CameraControl.OperationCanceledException> {
flashControl.setFlashAsync(ImageCapture.FLASH_MODE_ON).awaitWithTimeout()
}
}
@Test
fun setFlash_flashModeOn(): Unit = runBlocking {
// Arrange, clear data of the initial invocations.
fakeRequestControl.addParameterCalls.clear()
// Act.
flashControl.setFlashAsync(ImageCapture.FLASH_MODE_ON).awaitWithTimeout()
// Assert. AE mode should change accordingly.
assertThat(fakeRequestControl.addParameterCalls).hasSize(1)
assertThat(fakeRequestControl.addParameterCalls[0]).containsAtLeastEntriesIn(
mapOf(CaptureRequest.CONTROL_AE_MODE to CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH)
)
}
@Test
fun setFlash_flashModeAuto(): Unit = runBlocking {
// Arrange, clear data of the initial invocations.
fakeRequestControl.addParameterCalls.clear()
// Act.
flashControl.setFlashAsync(ImageCapture.FLASH_MODE_AUTO).awaitWithTimeout()
// Assert. AE mode should change accordingly.
assertThat(fakeRequestControl.addParameterCalls).hasSize(1)
assertThat(fakeRequestControl.addParameterCalls[0]).containsAtLeastEntriesIn(
mapOf(CaptureRequest.CONTROL_AE_MODE to CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
)
}
@Test
fun setFlash_flashModeOnThenOff(): Unit = runBlocking {
// Arrange, clear data of the initial invocations.
fakeRequestControl.addParameterCalls.clear()
// Act.
flashControl.setFlashAsync(ImageCapture.FLASH_MODE_ON).awaitWithTimeout()
flashControl.setFlashAsync(ImageCapture.FLASH_MODE_OFF).awaitWithTimeout()
// Assert. AE mode should change accordingly.
assertThat(fakeRequestControl.addParameterCalls).hasSize(2)
assertThat(fakeRequestControl.addParameterCalls[0]).containsAtLeastEntriesIn(
mapOf(CaptureRequest.CONTROL_AE_MODE to CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH)
)
assertThat(fakeRequestControl.addParameterCalls[1]).containsAtLeastEntriesIn(
mapOf(CaptureRequest.CONTROL_AE_MODE to CaptureRequest.CONTROL_AE_MODE_ON)
)
}
@Test
fun setFlashTwice_cancelPreviousFuture(): Unit = runBlocking {
// Arrange. Set a never complete deferred.
fakeRequestControl.addParameterResult = CompletableDeferred()
// Act. call setFlashAsync twice.
val deferred = flashControl.setFlashAsync(ImageCapture.FLASH_MODE_ON)
flashControl.setFlashAsync(ImageCapture.FLASH_MODE_ON)
assertThrows<CameraControl.OperationCanceledException> {
deferred.awaitWithTimeout()
}
}
@Test
fun setInActive_cancelPreviousFuture(): Unit = runBlocking {
// Arrange. Set a never complete deferred.
fakeRequestControl.addParameterResult = CompletableDeferred()
val deferred = flashControl.setFlashAsync(ImageCapture.FLASH_MODE_ON)
// Act. call reset & clear the UseCaseCamera.
flashControl.setFlashAsync(ImageCapture.FLASH_MODE_ON)
flashControl.reset()
flashControl.useCaseCamera = null
assertThrows<CameraControl.OperationCanceledException> {
deferred.awaitWithTimeout()
}
}
@Test
fun useCaseCameraUpdated_setFlashResultShouldPropagate(): Unit = runBlocking {
// Arrange.
fakeRequestControl.addParameterResult = CompletableDeferred()
val deferred = flashControl.setFlashAsync(ImageCapture.FLASH_MODE_ON)
val fakeRequestControl = FakeUseCaseCameraRequestControl().apply {
addParameterResult = CompletableDeferred()
}
val fakeUseCaseCamera = FakeUseCaseCamera(requestControl = fakeRequestControl)
// Act. Simulate the UseCaseCamera is recreated.
flashControl.useCaseCamera = fakeUseCaseCamera
state3AControl.useCaseCamera = fakeUseCaseCamera
// Simulate setFlash is completed on the recreated UseCaseCamera
fakeRequestControl.addParameterResult.complete(Unit)
// Assert. The setFlash task should be completed.
assertThat(deferred.awaitWithTimeout()).isNotNull()
}
@Test
fun useCaseCameraUpdated_onlyCompleteLatestRequest(): Unit = runBlocking {
// Arrange.
fakeRequestControl.addParameterResult = CompletableDeferred()
val deferred = flashControl.setFlashAsync(ImageCapture.FLASH_MODE_ON)
val fakeRequestControl = FakeUseCaseCameraRequestControl().apply {
addParameterResult = CompletableDeferred()
}
val fakeUseCaseCamera = FakeUseCaseCamera(requestControl = fakeRequestControl)
// Act. Simulate the UseCaseCamera is recreated.
flashControl.useCaseCamera = fakeUseCaseCamera
state3AControl.useCaseCamera = fakeUseCaseCamera
// Act. Submits a new Flash mode.
val deferred2 = flashControl.setFlashAsync(ImageCapture.FLASH_MODE_AUTO)
// Simulate setFlash is completed on the recreated UseCaseCamera
fakeRequestControl.addParameterResult.complete(Unit)
// Assert. The previous set Flash mode task should be cancelled
assertThrows<CameraControl.OperationCanceledException> {
deferred.awaitWithTimeout()
}
// Assert. The latest set Flash mode task should be completed.
assertThat(deferred2.awaitWithTimeout()).isNotNull()
}
private suspend fun <T> Deferred<T>.awaitWithTimeout(
timeMillis: Long = TimeUnit.SECONDS.toMillis(5)
) = withTimeout(timeMillis) {
await()
}
}