[go: nahoru, domu]

blob: 902b8ade2aee047a6bedcc049f800133acfe4610 [file] [log] [blame]
/*
* 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.
*/
package androidx.camera.integration.extensions
import android.content.Context
import androidx.camera.camera2.Camera2Config
import androidx.camera.camera2.impl.Camera2ImplConfig
import androidx.camera.camera2.impl.CameraEventCallback
import androidx.camera.camera2.impl.CameraEventCallbacks
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.core.Camera
import androidx.camera.core.CameraFilter
import androidx.camera.core.CameraInfo
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
import androidx.camera.core.Preview
import androidx.camera.core.UseCase
import androidx.camera.core.impl.CameraConfig
import androidx.camera.core.impl.CaptureConfig
import androidx.camera.core.impl.Config
import androidx.camera.core.impl.ExtendedCameraConfigProviderStore
import androidx.camera.core.impl.Identifier
import androidx.camera.core.impl.MutableOptionsBundle
import androidx.camera.core.impl.OptionsBundle
import androidx.camera.core.impl.UseCaseConfigFactory
import androidx.camera.extensions.ExtensionMode
import androidx.camera.extensions.ExtensionsManager
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
import androidx.camera.integration.extensions.utils.CameraIdExtensionModePair
import androidx.camera.integration.extensions.utils.CameraSelectorUtil
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.testing.CameraUtil
import androidx.camera.testing.CameraUtil.PreTestCameraIdList
import androidx.camera.testing.StressTestRule
import androidx.camera.testing.SurfaceTextureProvider
import androidx.camera.testing.fakes.FakeLifecycleOwner
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.junit.After
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@LargeTest
@RunWith(Parameterized::class)
@SdkSuppress(minSdkVersion = 21)
class OpenCloseCaptureSessionStressTest(private val config: CameraIdExtensionModePair) {
@get:Rule
val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
PreTestCameraIdList(Camera2Config.defaultConfig())
)
private val context = ApplicationProvider.getApplicationContext<Context>()
private lateinit var cameraProvider: ProcessCameraProvider
private lateinit var extensionsManager: ExtensionsManager
private lateinit var camera: Camera
private lateinit var baseCameraSelector: CameraSelector
private lateinit var extensionCameraSelector: CameraSelector
private lateinit var preview: Preview
private lateinit var imageCapture: ImageCapture
private lateinit var imageAnalysis: ImageAnalysis
private var isImageAnalysisSupported = false
private lateinit var lifecycleOwner: FakeLifecycleOwner
private val cameraEventMonitor = CameraEventMonitor()
@Before
fun setUp(): Unit = runBlocking {
assumeTrue(CameraXExtensionsTestUtil.isTargetDeviceAvailableForExtensions())
cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
extensionsManager = ExtensionsManager.getInstanceAsync(
context,
cameraProvider
)[10000, TimeUnit.MILLISECONDS]
val (cameraId, extensionMode) = config
baseCameraSelector = CameraSelectorUtil.createCameraSelectorById(cameraId)
assumeTrue(extensionsManager.isExtensionAvailable(baseCameraSelector, extensionMode))
extensionCameraSelector = extensionsManager.getExtensionEnabledCameraSelector(
baseCameraSelector,
extensionMode
)
camera = withContext(Dispatchers.Main) {
lifecycleOwner = FakeLifecycleOwner()
lifecycleOwner.startAndResume()
cameraProvider.bindToLifecycle(lifecycleOwner, extensionCameraSelector)
}
preview = Preview.Builder().build()
withContext(Dispatchers.Main) {
preview.setSurfaceProvider(SurfaceTextureProvider.createSurfaceTextureProvider())
}
imageCapture = ImageCapture.Builder().build()
imageAnalysis = ImageAnalysis.Builder().build()
isImageAnalysisSupported =
camera.isUseCasesCombinationSupported(preview, imageCapture, imageAnalysis)
}
@After
fun cleanUp(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
cameraProvider.unbindAll()
cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
}
}
if (::extensionsManager.isInitialized) {
extensionsManager.shutdown()[10000, TimeUnit.MILLISECONDS]
}
}
@Test
fun openCloseCaptureSessionStressTest_withPreviewImageCapture(): Unit = runBlocking {
bindUseCase_unbindAll_toCheckCameraEvent_repeatedly(preview, imageCapture)
}
@Test
fun openCloseCaptureSessionStressTest_withPreviewImageCaptureImageAnalysis(): Unit =
runBlocking {
val imageAnalysis = ImageAnalysis.Builder().build()
assumeTrue(camera.isUseCasesCombinationSupported(preview, imageCapture, imageAnalysis))
bindUseCase_unbindAll_toCheckCameraEvent_repeatedly(
preview,
imageCapture,
imageAnalysis
)
}
/**
* Repeatedly binds use cases, unbind all to check whether the capture session can be opened
* and closed successfully by monitoring the CameraEvent callbacks.
*/
private fun bindUseCase_unbindAll_toCheckCameraEvent_repeatedly(
vararg useCases: UseCase,
repeatCount: Int = CameraXExtensionsTestUtil.getStressTestRepeatingCount()
): Unit = runBlocking {
for (i in 1..repeatCount) {
// Arrange: resets the camera event monitor
cameraEventMonitor.reset()
withContext(Dispatchers.Main) {
// Arrange: retrieves the camera selector which allows to monitor camera event
// callbacks
val extensionEnabledCameraEventMonitorCameraSelector =
getExtensionsCameraEventMonitorCameraSelector(
extensionsManager,
config.extensionMode,
baseCameraSelector
)
// Act: binds use cases
cameraProvider.bindToLifecycle(
lifecycleOwner,
extensionEnabledCameraEventMonitorCameraSelector,
*useCases
)
}
// Assert: checks the CameraEvent#onEnableSession callback function is called
cameraEventMonitor.awaitSessionEnabledAndAssert()
// Act: unbinds all use cases
withContext(Dispatchers.Main) {
cameraProvider.unbindAll()
}
// Assert: checks the CameraEvent#onSessionDisabled callback function is called
cameraEventMonitor.awaitSessionDisabledAndAssert()
}
}
companion object {
@ClassRule
@JvmField val stressTest = StressTestRule()
@JvmStatic
@get:Parameterized.Parameters(name = "config = {0}")
val parameters: Collection<CameraIdExtensionModePair>
get() = CameraXExtensionsTestUtil.getAllCameraIdExtensionModeCombinations()
/**
* Retrieves the default extended camera config provider id string
*/
private fun getExtendedCameraConfigProviderId(@ExtensionMode.Mode mode: Int): String =
when (mode) {
ExtensionMode.BOKEH -> "EXTENSION_MODE_BOKEH"
ExtensionMode.HDR -> "EXTENSION_MODE_HDR"
ExtensionMode.NIGHT -> "EXTENSION_MODE_NIGHT"
ExtensionMode.FACE_RETOUCH -> "EXTENSION_MODE_FACE_RETOUCH"
ExtensionMode.AUTO -> "EXTENSION_MODE_AUTO"
else -> throw IllegalArgumentException("Invalid extension mode!")
}.let {
return ":camera:camera-extensions-$it"
}
/**
* Retrieves the camera event monitor extended camera config provider id string
*/
private fun getCameraEventMonitorCameraConfigProviderId(
@ExtensionMode.Mode mode: Int
): String = "${getExtendedCameraConfigProviderId(mode)}-camera-event-monitor"
}
/**
* Gets the camera selector which allows to monitor the camera event callbacks
*/
private fun getExtensionsCameraEventMonitorCameraSelector(
extensionsManager: ExtensionsManager,
extensionMode: Int,
baseCameraSelector: CameraSelector
): CameraSelector {
// Injects the ExtensionsCameraEventMonitorUseCaseConfigFactory which allows to monitor and
// verify the camera event callbacks
injectExtensionsCameraEventMonitorUseCaseConfigFactory(
extensionsManager,
extensionMode,
baseCameraSelector
)
val builder = CameraSelector.Builder.fromSelector(baseCameraSelector)
// Add an ExtensionCameraEventMonitorCameraFilter which includes the CameraFilter to check
// whether the camera is supported for the extension mode or not and also includes the
// identifier to find the extended camera config provider from
// ExtendedCameraConfigProviderStore
builder.addCameraFilter(
ExtensionsCameraEventMonitorCameraFilter(
extensionsManager,
extensionMode
)
)
return builder.build()
}
/**
* Injects the ExtensionsCameraEventMonitorUseCaseConfigFactory which allows to monitor and
* verify the camera event callbacks
*/
private fun injectExtensionsCameraEventMonitorUseCaseConfigFactory(
extensionsManager: ExtensionsManager,
extensionMode: Int,
baseCameraSelector: CameraSelector
): Unit = runBlocking {
val defaultConfigProviderId =
Identifier.create(getExtendedCameraConfigProviderId(extensionMode))
val cameraEventConfigProviderId =
Identifier.create(getCameraEventMonitorCameraConfigProviderId(extensionMode))
// Calls the ExtensionsManager#getExtensionEnabledCameraSelector() function to add the
// default extended camera config provider to ExtendedCameraConfigProviderStore
extensionsManager.getExtensionEnabledCameraSelector(baseCameraSelector, extensionMode)
// Injects the new camera config provider which will keep the original extensions needed
// configs and also add additional CameraEventMonitor to monitor the camera event callbacks.
ExtendedCameraConfigProviderStore.addConfig(cameraEventConfigProviderId) {
cameraInfo: CameraInfo, context: Context ->
// Retrieves the default extended camera config provider and
// ExtensionsUseCaseConfigFactory
val defaultCameraConfigProvider =
ExtendedCameraConfigProviderStore.getConfigProvider(defaultConfigProviderId)
val defaultCameraConfig = defaultCameraConfigProvider.getConfig(cameraInfo, context)!!
val defaultExtensionsUseCaseConfigFactory =
defaultCameraConfig.retrieveOption(CameraConfig.OPTION_USECASE_CONFIG_FACTORY, null)
// Creates a new ExtensionsCameraEventMonitorUseCaseConfigFactory on top of the default
// ExtensionsCameraEventMonitorUseCaseConfigFactory to monitor the capture session
// callbacks
val extensionsCameraEventMonitorUseCaseConfigFactory =
ExtensionsCameraEventMonitorUseCaseConfigFactory(
defaultExtensionsUseCaseConfigFactory,
cameraEventMonitor
)
// Creates the config from the original config and replaces its use case config factory
// with the ExtensionsCameraEventMonitorUseCaseConfigFactory
val mutableOptionsBundle = MutableOptionsBundle.from(defaultCameraConfig)
mutableOptionsBundle.insertOption(
CameraConfig.OPTION_USECASE_CONFIG_FACTORY,
extensionsCameraEventMonitorUseCaseConfigFactory
)
// Returns a CameraConfig implemented with the updated config
object : CameraConfig {
val config = OptionsBundle.from(mutableOptionsBundle)
override fun getConfig(): Config {
return config
}
override fun getCompatibilityId(): Identifier {
return config.retrieveOption(CameraConfig.OPTION_COMPATIBILITY_ID)!!
}
}
}
}
/**
* A ExtensionsCameraEventMonitorCameraFilter which includes the CameraFilter to check whether
* the camera is supported for the extension mode or not and also includes the identifier to
* find the extended camera config provider from ExtendedCameraConfigProviderStore.
*/
private class ExtensionsCameraEventMonitorCameraFilter constructor(
private val extensionManager: ExtensionsManager,
@ExtensionMode.Mode private val mode: Int
) : CameraFilter {
override fun getIdentifier(): Identifier {
return Identifier.create(getCameraEventMonitorCameraConfigProviderId(mode))
}
override fun filter(cameraInfos: MutableList<CameraInfo>): MutableList<CameraInfo> =
cameraInfos.mapNotNull { cameraInfo ->
val cameraId = Camera2CameraInfo.from(cameraInfo).cameraId
val cameraIdCameraSelector = CameraSelectorUtil.createCameraSelectorById(cameraId)
if (extensionManager.isExtensionAvailable(cameraIdCameraSelector, mode)) {
cameraInfo
} else {
null
}
}.toMutableList()
}
/**
* A UseCaseConfigFactory implemented on top of the default ExtensionsUseCaseConfigFactory to
* monitor the camera event callbacks
*/
private class ExtensionsCameraEventMonitorUseCaseConfigFactory constructor(
private val useCaseConfigFactory: UseCaseConfigFactory?,
private val cameraEventMonitor: CameraEventMonitor
) :
UseCaseConfigFactory {
override fun getConfig(
captureType: UseCaseConfigFactory.CaptureType,
captureMode: Int
): Config {
// Retrieves the config from the default ExtensionsUseCaseConfigFactory
val mutableOptionsBundle = useCaseConfigFactory?.getConfig(
captureType, captureMode
)?.let {
MutableOptionsBundle.from(it)
} ?: MutableOptionsBundle.create()
// Adds the CameraEventMonitor to the original CameraEventCallbacks of ImageCapture to
// monitor the camera event callbacks
if (captureType.equals(UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE)) {
var cameraEventCallbacks = mutableOptionsBundle.retrieveOption(
Camera2ImplConfig.CAMERA_EVENT_CALLBACK_OPTION,
null
)
if (cameraEventCallbacks != null) {
cameraEventCallbacks.addAll(
mutableListOf<CameraEventCallback>(
cameraEventMonitor
)
)
} else {
cameraEventCallbacks = CameraEventCallbacks(cameraEventMonitor)
}
mutableOptionsBundle.insertOption(
Camera2ImplConfig.CAMERA_EVENT_CALLBACK_OPTION,
cameraEventCallbacks
)
}
return OptionsBundle.from(mutableOptionsBundle)
}
}
/**
* An implementation of CameraEventCallback to monitor whether the camera event callbacks are
* called properly or not.
*/
private class CameraEventMonitor : CameraEventCallback() {
private var sessionEnabledLatch = CountDownLatch(1)
private var sessionDisabledLatch = CountDownLatch(1)
override fun onEnableSession(): CaptureConfig? {
sessionEnabledLatch.countDown()
return super.onEnableSession()
}
override fun onDisableSession(): CaptureConfig? {
sessionDisabledLatch.countDown()
return super.onDisableSession()
}
fun reset() {
sessionEnabledLatch = CountDownLatch(1)
sessionDisabledLatch = CountDownLatch(1)
}
fun awaitSessionEnabledAndAssert() {
assertThat(sessionEnabledLatch.await(3000, TimeUnit.MILLISECONDS)).isTrue()
}
fun awaitSessionDisabledAndAssert() {
assertThat(sessionDisabledLatch.await(3000, TimeUnit.MILLISECONDS)).isTrue()
}
}
}