blob: 7ab1e17be7e8183dc9dac0621b5a8b060430d2e5 [file] [log] [blame]
/*
* Copyright 2020 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.core
import android.content.Context
import android.graphics.Matrix
import android.graphics.Rect
import android.graphics.SurfaceTexture
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper.getMainLooper
import android.util.Range
import android.util.Rational
import android.util.Size
import android.view.Surface
import android.view.Surface.ROTATION_90
import androidx.annotation.RequiresApi
import androidx.camera.core.CameraEffect.IMAGE_CAPTURE
import androidx.camera.core.CameraEffect.PREVIEW
import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
import androidx.camera.core.CameraSelector.LENS_FACING_FRONT
import androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY
import androidx.camera.core.Preview.SurfaceProvider
import androidx.camera.core.SurfaceRequest.TransformationInfo
import androidx.camera.core.impl.CameraFactory
import androidx.camera.core.impl.CameraThreadConfig
import androidx.camera.core.impl.MutableOptionsBundle
import androidx.camera.core.impl.SessionConfig
import androidx.camera.core.impl.StreamSpec
import androidx.camera.core.impl.UseCaseConfig
import androidx.camera.core.impl.utils.executor.CameraXExecutors
import androidx.camera.core.impl.utils.executor.CameraXExecutors.directExecutor
import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
import androidx.camera.core.internal.CameraUseCaseAdapter
import androidx.camera.core.internal.utils.SizeUtil
import androidx.camera.core.resolutionselector.ResolutionSelector
import androidx.camera.testing.fakes.FakeAppConfig
import androidx.camera.testing.fakes.FakeCamera
import androidx.camera.testing.fakes.FakeCameraInfoInternal
import androidx.camera.testing.impl.CameraUtil
import androidx.camera.testing.impl.CameraXUtil
import androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager
import androidx.camera.testing.impl.fakes.FakeCameraFactory
import androidx.camera.testing.impl.fakes.FakeSurfaceEffect
import androidx.camera.testing.impl.fakes.FakeSurfaceProcessorInternal
import androidx.camera.testing.impl.fakes.FakeUseCase
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import java.util.Collections
import java.util.concurrent.ExecutionException
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
@RequiresApi(21)
private val TEST_CAMERA_SELECTOR = CameraSelector.DEFAULT_BACK_CAMERA
/**
* Unit tests for [Preview].
*/
@RunWith(RobolectricTestRunner::class)
@DoNotInstrument
@Config(
minSdk = Build.VERSION_CODES.LOLLIPOP
)
// Option Declarations:
// *********************************************************************************************
class PreviewTest {
companion object {
val FRAME_RATE_RANGE = Range(30, 60)
}
private var cameraUseCaseAdapter: CameraUseCaseAdapter? = null
private lateinit var appSurface: Surface
private lateinit var appSurfaceTexture: SurfaceTexture
private lateinit var backCamera: FakeCamera
private lateinit var frontCamera: FakeCamera
private lateinit var cameraXConfig: CameraXConfig
private lateinit var context: Context
private lateinit var previewToDetach: Preview
private lateinit var processor: FakeSurfaceProcessorInternal
private lateinit var effect: CameraEffect
private val handlersToRelease = mutableListOf<Handler>()
private val sensorToBufferTransform = Matrix().apply {
setScale(1f, 2f)
}
private val testImplementationOption: androidx.camera.core.impl.Config.Option<Int> =
androidx.camera.core.impl.Config.Option.create(
"test.testOption",
Int::class.javaPrimitiveType!!
)
private val testImplementationOptionValue = 5
@Before
@Throws(ExecutionException::class, InterruptedException::class)
fun setUp() {
appSurfaceTexture = SurfaceTexture(0)
appSurface = Surface(appSurfaceTexture)
backCamera = FakeCamera("back")
frontCamera = FakeCamera("front", null, FakeCameraInfoInternal(0, LENS_FACING_FRONT))
val cameraFactoryProvider =
CameraFactory.Provider { _: Context?, _: CameraThreadConfig?,
_: CameraSelector?, _: Long? ->
val cameraFactory = FakeCameraFactory()
cameraFactory.insertDefaultBackCamera(
backCamera.cameraInfoInternal.cameraId
) { backCamera }
cameraFactory.insertDefaultFrontCamera(
frontCamera.cameraInfoInternal.cameraId
) { frontCamera }
cameraFactory
}
cameraXConfig = CameraXConfig.Builder.fromConfig(
FakeAppConfig.create()
).setCameraFactoryProvider(cameraFactoryProvider).build()
context = ApplicationProvider.getApplicationContext<Context>()
CameraXUtil.initialize(context, cameraXConfig).get()
processor = FakeSurfaceProcessorInternal(mainThreadExecutor())
effect = FakeSurfaceEffect(processor)
}
@After
@Throws(ExecutionException::class, InterruptedException::class)
fun tearDown() {
appSurfaceTexture.release()
appSurface.release()
with(cameraUseCaseAdapter) {
this?.removeUseCases(useCases)
}
cameraUseCaseAdapter = null
if (::previewToDetach.isInitialized) {
previewToDetach.onUnbind()
}
processor.release()
CameraXUtil.shutdown().get()
for (handler in handlersToRelease) {
handler.looper.quitSafely()
}
}
@Test
fun attachPreview_receiveTransformationInfoOnlyOnce() {
// Arrange.
val semaphore = Semaphore(0)
// Act: create preview and listen to transformation info.
createPreview(surfaceProvider = {
it.setTransformationInfoListener(directExecutor()) {
semaphore.release()
}
})
// Assert: only receive transformation info once.
assertThat(semaphore.tryAcquire(1, 1, TimeUnit.SECONDS)).isTrue()
assertThat(semaphore.tryAcquire(2, 1, TimeUnit.SECONDS)).isFalse()
}
@Test
fun createPreview_sessionConfigMatchesStreamSpec() {
// Act: Create a preview use case.
val preview = createPreview()
// Assert: The session config matches the stream spec.
val sessionConfig = preview.sessionConfig
assertThat(sessionConfig.expectedFrameRateRange).isEqualTo(FRAME_RATE_RANGE)
assertThat(sessionConfig.implementationOptions.retrieveOption(testImplementationOption))
.isEqualTo(testImplementationOptionValue)
}
@Test
fun createPreview_deferrableSurfaceIsTheSurfaceRequestSurface() {
// Act: Create a preview use case.
val preview = createPreview()
// Assert: The preview's deferrable surface is the surface request surface.
assertThat(preview.sessionConfig.surfaces.single())
.isEqualTo(preview.mCurrentSurfaceRequest!!.deferrableSurface)
}
@Test
fun verifySupportedEffects() {
val preview = Preview.Builder().build()
assertThat(preview.isEffectTargetsSupported(PREVIEW)).isTrue()
assertThat(preview.isEffectTargetsSupported(PREVIEW or VIDEO_CAPTURE)).isTrue()
assertThat(
preview.isEffectTargetsSupported(PREVIEW or VIDEO_CAPTURE or IMAGE_CAPTURE)
).isTrue()
assertThat(preview.isEffectTargetsSupported(VIDEO_CAPTURE)).isFalse()
assertThat(preview.isEffectTargetsSupported(IMAGE_CAPTURE)).isFalse()
assertThat(preview.isEffectTargetsSupported(IMAGE_CAPTURE or VIDEO_CAPTURE)).isFalse()
}
@Test
fun viewPortSet_cropRectIsBasedOnViewPort() {
val transformationInfo = bindToLifecycleAndGetTransformationInfo(
ViewPort.Builder(Rational(1, 1), Surface.ROTATION_0).build()
)
// The expected value is based on fitting the 1:1 view port into a rect with the size of
// FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE.
val expectedPadding = (
FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE.width -
FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE.height
) / 2
assertThat(transformationInfo.cropRect).isEqualTo(
Rect(
expectedPadding,
0,
FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE.width - expectedPadding,
FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE.height
)
)
}
@Test
fun viewPortNotSet_cropRectIsFullSurface() {
val transformationInfo = bindToLifecycleAndGetTransformationInfo(
null
)
assertThat(transformationInfo.cropRect).isEqualTo(
Rect(
0,
0,
FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE.width,
FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE.height
)
)
}
@Test
fun surfaceRequestSize_isSurfaceSize() {
assertThat(bindToLifecycleAndGetSurfaceRequest().resolution).isEqualTo(
Size(
FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE.width,
FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE.height
)
)
}
@Test
fun surfaceRequestFrameRateRange_isUnspecified() {
// Target frame rate range isn't specified, so SurfaceRequest
// expected frame rate range should be unspecified.
assertThat(bindToLifecycleAndGetSurfaceRequest().expectedFrameRate).isEqualTo(
SurfaceRequest.FRAME_RATE_RANGE_UNSPECIFIED
)
}
@Test
fun defaultMirrorModeIsOnFrontOnly() {
val preview = Preview.Builder().build()
assertThat(preview.mirrorModeInternal).isEqualTo(MIRROR_MODE_ON_FRONT_ONLY)
}
@Test(expected = UnsupportedOperationException::class)
fun setMirrorMode_throwException() {
Preview.Builder().setMirrorMode(MIRROR_MODE_ON_FRONT_ONLY)
}
@Test
fun setTargetRotation_rotationIsChanged() {
// Arrange.
val preview = Preview.Builder().setTargetRotation(Surface.ROTATION_0).build()
// Act: set target rotation.
preview.targetRotation = Surface.ROTATION_180
// Assert: target rotation is updated.
assertThat(preview.targetRotation).isEqualTo(Surface.ROTATION_180)
}
@Test
fun setTargetRotation_rotationIsPropagated() {
// Arrange: create preview and wait for transformation info.
val preview = createPreview()
var transformationInfo: TransformationInfo? = null
preview.mCurrentSurfaceRequest!!.setTransformationInfoListener(
mainThreadExecutor()
) { newValue: TransformationInfo -> transformationInfo = newValue }
// Act: set target rotation.
preview.targetRotation = Surface.ROTATION_180
shadowOf(getMainLooper()).idle()
// Assert: target rotation is updated.
assertThat(transformationInfo!!.targetRotation).isEqualTo(Surface.ROTATION_180)
}
// @Test TODO re-enable once b/284336967 is done.
fun attachUseCase_transformationInfoUpdates() {
// Arrange: attach Preview without a SurfaceProvider.
// Build and bind use case.
val sessionOptionUnpacker =
{ _: Size, _: UseCaseConfig<*>?, _: SessionConfig.Builder? -> }
val preview = Preview.Builder()
.setTargetRotation(Surface.ROTATION_0)
.setSessionOptionUnpacker(sessionOptionUnpacker)
.build()
cameraUseCaseAdapter = CameraUtil.createCameraUseCaseAdapter(
ApplicationProvider.getApplicationContext(), TEST_CAMERA_SELECTOR
)
val rational1 = Rational(1, 1)
cameraUseCaseAdapter!!.setViewPort(ViewPort.Builder(rational1, Surface.ROTATION_0).build())
cameraUseCaseAdapter!!.addUseCases(Collections.singleton<UseCase>(preview))
// Set SurfaceProvider
var receivedTransformationInfo: TransformationInfo? = null
preview.setSurfaceProvider { request ->
request.setTransformationInfoListener(
CameraXExecutors.directExecutor(),
SurfaceRequest.TransformationInfoListener {
receivedTransformationInfo = it
}
)
}
shadowOf(getMainLooper()).idle()
assertThat(receivedTransformationInfo!!.cropRect.getAspectRatio()).isEqualTo(rational1)
// Act: bind another use case with a different viewport.
val fakeUseCase = FakeUseCase()
val rational2 = Rational(2, 1)
cameraUseCaseAdapter!!.setViewPort(ViewPort.Builder(rational2, Surface.ROTATION_0).build())
cameraUseCaseAdapter!!.addUseCases(listOf(preview, fakeUseCase))
shadowOf(getMainLooper()).idle()
// Assert: received viewport's aspect ratio is the latest one.
assertThat(receivedTransformationInfo!!.cropRect.getAspectRatio()).isEqualTo(rational2)
}
private fun Rect.getAspectRatio(): Rational {
return Rational(width(), height())
}
@Test
fun createSurfaceRequestWithProcessor_noCameraTransform() {
// Arrange: attach Preview without a SurfaceProvider.
var transformationInfo: TransformationInfo? = null
// Act: create pipeline in Preview and provide Surface.
val preview = createPreview(effect)
preview.mCurrentSurfaceRequest!!.setTransformationInfoListener(mainThreadExecutor()) {
transformationInfo = it
}
shadowOf(getMainLooper()).idle()
// Get pending SurfaceRequest created by pipeline.
assertThat(transformationInfo!!.hasCameraTransform()).isFalse()
}
@Test
fun createSurfaceRequestWithoutProcessor_hasCameraTransform() {
// Arrange: attach Preview without a SurfaceProvider.
var transformationInfo: TransformationInfo? = null
// Act: create pipeline in Preview and provide Surface.
val preview = createPreview()
preview.mCurrentSurfaceRequest!!.setTransformationInfoListener(mainThreadExecutor()) {
transformationInfo = it
}
shadowOf(getMainLooper()).idle()
// Get pending SurfaceRequest created by pipeline.
assertThat(transformationInfo!!.hasCameraTransform()).isTrue()
assertThat(transformationInfo!!.sensorToBufferTransform).isEqualTo(sensorToBufferTransform)
}
@Test
fun backCameraWithProcessor_notMirrored() {
// Act: create pipeline
val preview = createPreview(effect, backCamera)
// Assert
assertThat(preview.cameraEdge.isMirroring).isFalse()
}
@Test
fun frontCameraWithProcessor_mirrored() {
// Act: create pipeline
val preview = createPreview(effect, frontCamera)
// Assert
assertThat(preview.cameraEdge.isMirroring).isTrue()
}
@Test
fun setTargetRotationWithProcessor_rotationChangesOnSurfaceEdge() {
// Act: create pipeline
val preview = createPreview(effect)
// Act: update target rotation
preview.targetRotation = Surface.ROTATION_0
shadowOf(getMainLooper()).idle()
// Assert that the rotation of the SettableFuture is updated based on ROTATION_0.
assertThat(preview.cameraEdge.rotationDegrees).isEqualTo(0)
// Act: update target rotation again.
preview.targetRotation = Surface.ROTATION_180
shadowOf(getMainLooper()).idle()
// Assert: the rotation of the SettableFuture is updated based on ROTATION_90.
assertThat(preview.cameraEdge.rotationDegrees).isEqualTo(180)
}
@Test
fun setTargetRotationWithProcessorOnBackground_rotationChangesOnSurfaceEdge() {
// Act: create pipeline
val preview = createPreview(effect)
// Act: update target rotation
preview.targetRotation = Surface.ROTATION_0
shadowOf(getMainLooper()).idle()
// Assert that the rotation of the SettableFuture is updated based on ROTATION_0.
assertThat(preview.cameraEdge.rotationDegrees).isEqualTo(0)
// Act: update target rotation again.
val backgroundHandler = createBackgroundHandler()
backgroundHandler.post { preview.targetRotation = Surface.ROTATION_180 }
shadowOf(backgroundHandler.looper).idle()
shadowOf(getMainLooper()).idle()
// Assert: the rotation of the SettableFuture is updated based on ROTATION_90.
assertThat(preview.cameraEdge.rotationDegrees).isEqualTo(180)
}
@Test
fun invalidateAppSurfaceRequestWithProcessing_cameraNotReset() {
// Arrange: create Preview with processing.
val surfaceRequest = createPreview(effect).mCurrentSurfaceRequest
// Act: invalidate.
surfaceRequest!!.invalidate()
shadowOf(getMainLooper()).idle()
// Assert: preview is not reset.
assertThat(backCamera.useCaseResetHistory).isEmpty()
}
@Test
fun invalidateNodeSurfaceRequest_cameraReset() {
// Arrange: create Preview with processing.
val preview = createPreview(effect)
// Act: invalidate.
processor.surfaceRequest!!.invalidate()
shadowOf(getMainLooper()).idle()
// Assert: preview is reset.
assertThat(backCamera.useCaseResetHistory).containsExactly(preview)
}
@Test
fun invalidateAppSurfaceRequestWithoutProcessing_cameraReset() {
// Arrange: create Preview without processing.
val preview = createPreview()
val surfaceRequest = preview.mCurrentSurfaceRequest
// Act: invalidate
surfaceRequest!!.invalidate()
shadowOf(getMainLooper()).idle()
// Assert: preview is reset.
assertThat(backCamera.useCaseResetHistory).containsExactly(preview)
}
@Test
fun invalidateWhenDetached_deferrableSurfaceClosed() {
// Arrange: create Preview with processing then detach.
val preview = createPreview(effect)
val surfaceRequest = processor.surfaceRequest!!
preview.unbindFromCamera(backCamera)
// Act: invalidate.
surfaceRequest.invalidate()
shadowOf(getMainLooper()).idle()
// Assert: preview is not reset and the DeferrableSurface is closed.
assertThat(backCamera.useCaseResetHistory).isEmpty()
assertThat(surfaceRequest.deferrableSurface.isClosed).isTrue()
}
@Test
fun hasCameraTransform_rotationDegreesNotFlipped() {
// Act: create preview with hasCameraTransform == true
val preview = createPreview(
effect,
frontCamera,
targetRotation = ROTATION_90
)
assertThat(preview.cameraEdge.hasCameraTransform()).isTrue()
// Assert: rotationDegrees is not flipped.
assertThat(preview.cameraEdge.rotationDegrees).isEqualTo(90)
}
@Test
fun setNoCameraTransform_propagatesToCameraEdge() {
// Act: create preview with hasCameraTransform == false
frontCamera.hasTransform = false
val preview = createPreview(
effect,
frontCamera,
targetRotation = ROTATION_90
)
// Assert
assertThat(preview.cameraEdge.hasCameraTransform()).isFalse()
assertThat(preview.cameraEdge.isMirroring).isFalse()
}
@Test
fun frontCameraWithoutCameraTransform_noMirroring() {
// Act: create preview with hasCameraTransform == false
frontCamera.hasTransform = false
val preview = createPreview(
effect,
frontCamera,
targetRotation = ROTATION_90
)
// Assert
assertThat(preview.cameraEdge.isMirroring).isFalse()
}
@Test
fun cameraEdgeHasTransformByDefault() {
assertThat(createPreview(effect).cameraEdge.hasCameraTransform()).isTrue()
}
@Test
fun bindAndUnbindPreview_surfacesPropagated() {
// Act: create pipeline in Preview and provide Surface.
val preview = createPreview(effect)
val surfaceRequest = preview.mCurrentSurfaceRequest!!
var appSurfaceReadyToRelease = false
surfaceRequest.provideSurface(appSurface, mainThreadExecutor()) {
appSurfaceReadyToRelease = true
}
shadowOf(getMainLooper()).idle()
// Assert: surfaceOutput received.
assertThat(processor.surfaceOutputs).hasSize(1)
assertThat(processor.isReleased).isFalse()
assertThat(processor.isOutputSurfaceRequestedToClose[PREVIEW]).isNull()
assertThat(processor.isInputSurfaceReleased).isFalse()
assertThat(appSurfaceReadyToRelease).isFalse()
// processor surface is provided to camera.
assertThat(preview.sessionConfig.surfaces[0].surface.get())
.isEqualTo(processor.inputSurface)
// Act: unbind Preview.
preview.onUnbind()
shadowOf(getMainLooper()).idle()
// Assert: processor and processor surface is released.
assertThat(processor.isReleased).isTrue()
assertThat(processor.isOutputSurfaceRequestedToClose[PREVIEW]).isTrue()
assertThat(processor.isInputSurfaceReleased).isTrue()
assertThat(appSurfaceReadyToRelease).isTrue()
}
@Test
fun invokedErrorListener_recreatePipeline() {
// Arrange: create pipeline and get a reference of the SessionConfig.
val preview = createPreview(effect)
val originalSessionConfig = preview.sessionConfig
// Act: invoke the error listener.
preview.sessionConfig.errorListeners[0].onError(
preview.sessionConfig, SessionConfig.SessionError.SESSION_ERROR_UNKNOWN
)
shadowOf(getMainLooper()).idle()
// Assert: the SessionConfig changed.
assertThat(preview.sessionConfig).isNotEqualTo(originalSessionConfig)
}
@Test
fun setTargetRotation_transformationInfoUpdated() {
// Arrange: set up preview and verify target rotation in TransformationInfo.
val sessionOptionUnpacker =
{ _: Size, _: UseCaseConfig<*>?, _: SessionConfig.Builder? -> }
val preview = Preview.Builder()
.setTargetRotation(Surface.ROTATION_0)
.setSessionOptionUnpacker(sessionOptionUnpacker)
.build()
cameraUseCaseAdapter = CameraUtil.createCameraUseCaseAdapter(
ApplicationProvider.getApplicationContext(), TEST_CAMERA_SELECTOR
)
cameraUseCaseAdapter!!.addUseCases(Collections.singleton<UseCase>(preview))
var receivedTransformationInfo: TransformationInfo? = null
preview.setSurfaceProvider { request ->
request.setTransformationInfoListener(
CameraXExecutors.directExecutor(),
SurfaceRequest.TransformationInfoListener {
receivedTransformationInfo = it
}
)
}
shadowOf(getMainLooper()).idle()
assertThat(receivedTransformationInfo!!.targetRotation).isEqualTo(Surface.ROTATION_0)
// Act: set target rotation to a different value.
preview.targetRotation = Surface.ROTATION_180
shadowOf(getMainLooper()).idle()
// Assert: target rotation changed.
assertThat(receivedTransformationInfo!!.targetRotation).isEqualTo(Surface.ROTATION_180)
}
@Test
fun setSurfaceProviderAfterAttachment_receivesSurfaceProviderCallbacks() {
// Arrange: attach Preview without a SurfaceProvider.
val sessionOptionUnpacker =
{ _: Size, _: UseCaseConfig<*>?, _: SessionConfig.Builder? -> }
val preview = Preview.Builder()
.setTargetRotation(Surface.ROTATION_0)
.setSessionOptionUnpacker(sessionOptionUnpacker)
.build()
val cameraUseCaseAdapter = CameraUtil.createCameraUseCaseAdapter(
ApplicationProvider
.getApplicationContext(),
TEST_CAMERA_SELECTOR
)
cameraUseCaseAdapter.addUseCases(Collections.singleton<UseCase>(preview))
// Get pending SurfaceRequest created by pipeline.
val pendingSurfaceRequest = preview.mCurrentSurfaceRequest
var receivedSurfaceRequest: SurfaceRequest? = null
var receivedTransformationInfo: TransformationInfo? = null
// Act: set a SurfaceProvider after attachment.
preview.setSurfaceProvider { request ->
request.setTransformationInfoListener(
CameraXExecutors.directExecutor(),
SurfaceRequest.TransformationInfoListener {
receivedTransformationInfo = it
}
)
receivedSurfaceRequest = request
}
shadowOf(getMainLooper()).idle()
// Assert: received SurfaceRequest is not the pending SurfaceRequest.
assertThat(receivedSurfaceRequest).isNotSameInstanceAs(pendingSurfaceRequest)
assertThat(receivedTransformationInfo).isNotNull()
// Act: set a different SurfaceProvider.
preview.setSurfaceProvider { request ->
request.setTransformationInfoListener(
CameraXExecutors.directExecutor(),
SurfaceRequest.TransformationInfoListener {
receivedTransformationInfo = it
}
)
receivedSurfaceRequest = request
}
shadowOf(getMainLooper()).idle()
// Assert: received a different SurfaceRequest.
assertThat(receivedSurfaceRequest).isNotSameInstanceAs(pendingSurfaceRequest)
}
@Test
fun setSurfaceProviderAfterDetach_receivesSurfaceRequestAfterAttach() {
// Arrange: attach Preview without a SurfaceProvider.
val sessionOptionUnpacker =
{ _: Size, _: UseCaseConfig<*>?, _: SessionConfig.Builder? -> }
val preview = Preview.Builder()
.setTargetRotation(Surface.ROTATION_0)
.setSessionOptionUnpacker(sessionOptionUnpacker)
.build()
cameraUseCaseAdapter = CameraUtil.createCameraUseCaseAdapter(
ApplicationProvider
.getApplicationContext(),
TEST_CAMERA_SELECTOR
)
// Attach
cameraUseCaseAdapter!!.addUseCases(Collections.singleton<UseCase>(preview))
// Detach
cameraUseCaseAdapter!!.removeUseCases(Collections.singleton<UseCase>(preview))
// Act: set a SurfaceProvider after detaching
var receivedSurfaceRequest = false
preview.setSurfaceProvider { receivedSurfaceRequest = true }
shadowOf(getMainLooper()).idle()
val receivedWhileDetached = receivedSurfaceRequest
// Attach
cameraUseCaseAdapter!!.addUseCases(Collections.singleton<UseCase>(preview))
shadowOf(getMainLooper()).idle()
val receivedAfterAttach = receivedSurfaceRequest
// Assert: received a SurfaceRequest.
assertThat(receivedWhileDetached).isFalse()
assertThat(receivedAfterAttach).isTrue()
}
@Test
fun sessionConfigHasStreamSpecImplementationOptions_whenCreatePipeline() {
val preview = createPreview(effect)
assertThat(
preview.sessionConfig.implementationOptions.retrieveOption(
testImplementationOption
)
).isEqualTo(testImplementationOptionValue)
}
@Test
fun sessionConfigHasStreamSpecImplementationOptions_whenUpdateStreamSpecImplOptions() {
val preview = createPreview(effect)
val newImplementationOptionValue = 6
val streamSpecOptions = MutableOptionsBundle.create()
streamSpecOptions.insertOption(testImplementationOption, newImplementationOptionValue)
preview.updateSuggestedStreamSpecImplementationOptions(streamSpecOptions)
assertThat(
preview.sessionConfig.implementationOptions.retrieveOption(
testImplementationOption
)
).isEqualTo(newImplementationOptionValue)
}
@Suppress("DEPRECATION") // test for legacy resolution API
@Test
fun throwException_whenSetBothTargetResolutionAndAspectRatio() {
Assert.assertThrows(IllegalArgumentException::class.java) {
Preview.Builder().setTargetResolution(SizeUtil.RESOLUTION_VGA)
.setTargetAspectRatio(AspectRatio.RATIO_4_3).build()
}
}
@Suppress("DEPRECATION") // test for legacy resolution API
@Test
fun throwException_whenSetTargetResolutionWithResolutionSelector() {
Assert.assertThrows(IllegalArgumentException::class.java) {
Preview.Builder().setTargetResolution(SizeUtil.RESOLUTION_VGA)
.setResolutionSelector(ResolutionSelector.Builder().build())
.build()
}
}
@Suppress("DEPRECATION") // test for legacy resolution API
@Test
fun throwException_whenSetTargetAspectRatioWithResolutionSelector() {
Assert.assertThrows(IllegalArgumentException::class.java) {
Preview.Builder().setTargetAspectRatio(AspectRatio.RATIO_4_3)
.setResolutionSelector(ResolutionSelector.Builder().build())
.build()
}
}
@Test
fun canSetTargetFrameRate() {
val preview = Preview.Builder().setTargetFrameRate(Range(15, 30))
.build()
assertThat(preview.targetFrameRate).isEqualTo(Range(15, 30))
}
@Test
fun canSetPreviewStabilization() {
val preview = Preview.Builder().setPreviewStabilizationEnabled(true)
.build()
assertThat(preview.isPreviewStabilizationEnabled).isTrue()
}
@Test
fun canSetDynamicRange() {
// Use an unspecified dynamic range that isn't the default, UNSPECIFIED.
val preview = Preview.Builder().setDynamicRange(DynamicRange.HDR_UNSPECIFIED_10_BIT).build()
assertThat(preview.dynamicRange).isEqualTo(DynamicRange.HDR_UNSPECIFIED_10_BIT)
}
@Test
fun defaultDynamicRange_isUnspecified() {
val preview = Preview.Builder().build()
assertThat(preview.dynamicRange).isEqualTo(DynamicRange.UNSPECIFIED)
}
private fun bindToLifecycleAndGetSurfaceRequest(): SurfaceRequest {
return bindToLifecycleAndGetResult(null).first
}
private fun bindToLifecycleAndGetTransformationInfo(viewPort: ViewPort?): TransformationInfo {
return bindToLifecycleAndGetResult(viewPort).second
}
private fun bindToLifecycleAndGetResult(viewPort: ViewPort?): Pair<SurfaceRequest,
TransformationInfo> {
// Arrange.
val sessionOptionUnpacker =
{ _: Size, _: UseCaseConfig<*>?, _: SessionConfig.Builder? -> }
val preview = Preview.Builder()
.setTargetRotation(Surface.ROTATION_0)
.setSessionOptionUnpacker(sessionOptionUnpacker)
.build()
var surfaceRequest: SurfaceRequest? = null
var transformationInfo: TransformationInfo? = null
preview.setSurfaceProvider { request ->
request.setTransformationInfoListener(
CameraXExecutors.directExecutor(),
SurfaceRequest.TransformationInfoListener {
transformationInfo = it
}
)
surfaceRequest = request
}
// Act.
cameraUseCaseAdapter = CameraUtil.createCameraUseCaseAdapter(
ApplicationProvider.getApplicationContext(), TEST_CAMERA_SELECTOR
)
cameraUseCaseAdapter!!.setViewPort(viewPort)
cameraUseCaseAdapter!!.addUseCases(Collections.singleton<UseCase>(preview))
shadowOf(getMainLooper()).idle()
return Pair(surfaceRequest!!, transformationInfo!!)
}
private fun createPreview(
effect: CameraEffect? = null,
camera: FakeCamera = backCamera,
targetRotation: Int = ROTATION_90,
surfaceProvider: SurfaceProvider = SurfaceProvider {
}
): Preview {
previewToDetach = Preview.Builder()
.setTargetRotation(targetRotation)
.build()
previewToDetach.effect = effect
previewToDetach.setSurfaceProvider(directExecutor(), surfaceProvider)
previewToDetach.bindToCamera(
camera, null, previewToDetach.getDefaultConfig(
true,
cameraXConfig.getUseCaseConfigFactoryProvider(null)!!.newInstance(context)
)
)
val streamSpecOptions = MutableOptionsBundle.create()
streamSpecOptions.insertOption(testImplementationOption, testImplementationOptionValue)
val streamSpec = StreamSpec.builder(Size(640, 480))
.setExpectedFrameRateRange(FRAME_RATE_RANGE)
.setImplementationOptions(streamSpecOptions).build()
previewToDetach.sensorToBufferTransformMatrix = sensorToBufferTransform
previewToDetach.updateSuggestedStreamSpec(streamSpec)
return previewToDetach
}
private fun createBackgroundHandler(): Handler {
val handler = Handler(HandlerThread("PreviewTest").run {
start()
looper
})
handlersToRelease.add(handler)
return handler
}
}