1 /*
<lambda>null2  * Copyright 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.camera.view
18 
19 import android.content.Context
20 import android.graphics.Matrix
21 import android.graphics.PointF
22 import android.os.Build
23 import android.os.Looper.getMainLooper
24 import android.util.Range
25 import android.util.Rational
26 import android.util.Size
27 import android.view.Surface
28 import androidx.camera.core.AspectRatio.RATIO_16_9
29 import androidx.camera.core.AspectRatio.RATIO_4_3
30 import androidx.camera.core.CameraSelector
31 import androidx.camera.core.DynamicRange
32 import androidx.camera.core.FocusMeteringAction
33 import androidx.camera.core.FocusMeteringResult
34 import androidx.camera.core.ImageAnalysis
35 import androidx.camera.core.ImageAnalysis.COORDINATE_SYSTEM_ORIGINAL
36 import androidx.camera.core.ImageAnalysis.COORDINATE_SYSTEM_VIEW_REFERENCED
37 import androidx.camera.core.ImageCapture
38 import androidx.camera.core.ImageCapture.FLASH_MODE_ON
39 import androidx.camera.core.ImageCapture.ScreenFlash
40 import androidx.camera.core.ImageProxy
41 import androidx.camera.core.MirrorMode
42 import androidx.camera.core.Preview.SurfaceProvider
43 import androidx.camera.core.SurfaceOrientedMeteringPointFactory
44 import androidx.camera.core.TorchState
45 import androidx.camera.core.ViewPort
46 import androidx.camera.core.impl.ImageAnalysisConfig
47 import androidx.camera.core.impl.ImageCaptureConfig
48 import androidx.camera.core.impl.ImageOutputConfig
49 import androidx.camera.core.impl.utils.executor.CameraXExecutors.directExecutor
50 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
51 import androidx.camera.core.resolutionselector.AspectRatioStrategy
52 import androidx.camera.core.resolutionselector.ResolutionSelector
53 import androidx.camera.testing.fakes.FakeCamera
54 import androidx.camera.testing.fakes.FakeCameraControl
55 import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
56 import androidx.camera.testing.impl.fakes.FakeSurfaceEffect
57 import androidx.camera.testing.impl.fakes.FakeSurfaceProcessor
58 import androidx.camera.video.Quality
59 import androidx.camera.video.QualitySelector
60 import androidx.camera.view.CameraController.TAP_TO_FOCUS_FOCUSED
61 import androidx.camera.view.CameraController.TAP_TO_FOCUS_NOT_FOCUSED
62 import androidx.camera.view.CameraController.TAP_TO_FOCUS_NOT_STARTED
63 import androidx.camera.view.CameraController.TAP_TO_FOCUS_STARTED
64 import androidx.camera.view.internal.ScreenFlashUiInfo
65 import androidx.concurrent.futures.CallbackToFutureAdapter
66 import androidx.test.annotation.UiThreadTest
67 import androidx.test.core.app.ApplicationProvider
68 import com.google.common.truth.Truth.assertThat
69 import com.google.common.util.concurrent.MoreExecutors
70 import java.util.concurrent.Executors
71 import java.util.concurrent.TimeUnit
72 import org.junit.Assert
73 import org.junit.Assume.assumeTrue
74 import org.junit.Before
75 import org.junit.Test
76 import org.junit.runner.RunWith
77 import org.robolectric.RobolectricTestRunner
78 import org.robolectric.Shadows.shadowOf
79 import org.robolectric.annotation.Config
80 import org.robolectric.annotation.internal.DoNotInstrument
81 import org.robolectric.shadows.ShadowSystemClock
82 
83 /** Unit tests for [CameraController]. */
84 @RunWith(RobolectricTestRunner::class)
85 @DoNotInstrument
86 @Config(
87     minSdk = Build.VERSION_CODES.LOLLIPOP,
88     instrumentedPackages = ["androidx.camera.view"], // required for shadow clock to work
89 )
90 class CameraControllerTest {
91     companion object {
92         const val LINEAR_ZOOM = .1F
93         const val ZOOM_RATIO = .5F
94         const val TORCH_ENABLED = true
95         const val FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS =
96             FocusMeteringAction.DEFAULT_AUTO_CANCEL_DURATION_MILLIS
97         val TAP_POINT_1 = PointF(0F, 0F)
98         val TAP_POINT_2 = PointF(1F, 2F)
99     }
100 
101     private val previewViewTransform = Matrix().also { it.postRotate(90F) }
102     private val context = ApplicationProvider.getApplicationContext<Context>()
103     private lateinit var controller: LifecycleCameraController
104 
105     @Suppress("deprecation")
106     private val targetSizeWithAspectRatio = CameraController.OutputSize(RATIO_16_9)
107     private val resolutionSelector =
108         ResolutionSelector.Builder()
109             .setAspectRatioStrategy(AspectRatioStrategy.RATIO_16_9_FALLBACK_AUTO_STRATEGY)
110             .build()
111 
112     @Suppress("deprecation")
113     private val targetSizeWithResolution = CameraController.OutputSize(Size(1080, 1960))
114     private val targetVideoQuality = Quality.HIGHEST
115     private val fakeViewPort = ViewPort.Builder(Rational(1, 1), Surface.ROTATION_0).build()
116     private val fakeCameraControl = FakeCameraControl()
117     private val fakeCamera = FakeCamera(fakeCameraControl)
118     private val processCameraProviderWrapper = FakeProcessCameraProviderWrapper(fakeCamera)
119     private lateinit var lifecycleCameraProviderCompleter:
120         CallbackToFutureAdapter.Completer<ProcessCameraProviderWrapper>
121 
122     private val pointFactory = SurfaceOrientedMeteringPointFactory(1f, 1f)
123 
124     @Before
125     fun setUp() {
126         val lifecycleCameraProviderFuture =
127             CallbackToFutureAdapter.getFuture { completer ->
128                 lifecycleCameraProviderCompleter = completer
129                 "CameraControllerTest.lifecycleCameraProviderFuture"
130             }
131         controller = LifecycleCameraController(context, lifecycleCameraProviderFuture)
132         controller.bindToLifecycle(FakeLifecycleOwner())
133         controller.attachPreviewSurface({}, fakeViewPort)
134     }
135 
136     @Test
137     fun setEffects_unbindInvoked() {
138         // Arrange.
139         completeCameraInitialization()
140         assertThat(processCameraProviderWrapper.unbindInvoked()).isFalse()
141         // Act.
142         controller.setEffects(
143             setOf(FakeSurfaceEffect(directExecutor(), FakeSurfaceProcessor(directExecutor())))
144         )
145         // Assert.
146         assertThat(processCameraProviderWrapper.unbindInvoked()).isTrue()
147     }
148 
149     @Test
150     fun clearEffects_unbindInvoked() {
151         // Arrange.
152         completeCameraInitialization()
153         assertThat(processCameraProviderWrapper.unbindInvoked()).isFalse()
154         // Act.
155         controller.clearEffects()
156         // Assert.
157         assertThat(processCameraProviderWrapper.unbindInvoked()).isTrue()
158     }
159 
160     @Test
161     fun setPendingValues_valuesPropagateAfterInit() {
162         // Arrange: set pending values
163         val linearZoomFuture = controller.setLinearZoom(LINEAR_ZOOM)
164         val zoomRatioFuture = controller.setZoomRatio(ZOOM_RATIO)
165         val torchFuture = controller.enableTorch(TORCH_ENABLED)
166         assertThat(fakeCameraControl.linearZoom).isNotEqualTo(LINEAR_ZOOM)
167         assertThat(fakeCameraControl.zoomRatio).isNotEqualTo(ZOOM_RATIO)
168         assertThat(fakeCameraControl.torchEnabled).isNotEqualTo(TORCH_ENABLED)
169         assertThat(linearZoomFuture.isDone).isFalse()
170         assertThat(zoomRatioFuture.isDone).isFalse()
171         assertThat(torchFuture.isDone).isFalse()
172 
173         // Act.
174         completeCameraInitialization()
175 
176         // Assert:
177         assertThat(fakeCameraControl.linearZoom).isEqualTo(LINEAR_ZOOM)
178         assertThat(fakeCameraControl.zoomRatio).isEqualTo(ZOOM_RATIO)
179         assertThat(fakeCameraControl.torchEnabled).isEqualTo(TORCH_ENABLED)
180         assertThat(linearZoomFuture.isDone).isTrue()
181         assertThat(zoomRatioFuture.isDone).isTrue()
182         assertThat(torchFuture.isDone).isTrue()
183     }
184 
185     @Test
186     fun unbindController_canSetPendingValueAgain() {
187         // Arrange: set pending values
188         var linearZoomFuture = controller.setLinearZoom(LINEAR_ZOOM)
189 
190         // Act: complete initialization.
191         completeCameraInitialization()
192         // Assert: pending value is set.
193         assertThat(fakeCameraControl.linearZoom).isEqualTo(LINEAR_ZOOM)
194         assertThat(linearZoomFuture.isDone).isTrue()
195 
196         // Act: unbind controller, set pending value again and rebind.
197         controller.unbind()
198         linearZoomFuture = controller.setLinearZoom(1F)
199         controller.bindToLifecycle(FakeLifecycleOwner())
200         // Assert: pending value is set to new value.
201         assertThat(fakeCameraControl.linearZoom).isEqualTo(1F)
202         assertThat(linearZoomFuture.isDone).isTrue()
203     }
204 
205     @Test
206     fun initCompletes_torchStatePropagated() {
207         // Arrange: get LiveData before init completes
208         val torchState = controller.torchState
209         // State is null.
210         assertThat(torchState.value).isNull()
211         // Act: complete initialization.
212         completeCameraInitialization()
213         // Assert: LiveData gets a value update.
214         assertThat(torchState.value).isEqualTo(TorchState.OFF)
215     }
216 
217     @Test
218     fun initCompletes_zoomStatePropagated() {
219         // Arrange: get LiveData before init completes
220         val zoomState = controller.zoomState
221         // State is null.
222         assertThat(zoomState.value).isNull()
223         // Act: complete initialization.
224         completeCameraInitialization()
225         // Assert: LiveData gets a value update.
226         assertThat(zoomState.value).isEqualTo(fakeCamera.cameraInfo.zoomState.value)
227     }
228 
229     private fun completeCameraInitialization() {
230         lifecycleCameraProviderCompleter.set(processCameraProviderWrapper)
231         shadowOf(getMainLooper()).idle()
232     }
233 
234     @Test
235     fun setAnalyzerWithNewResolutionOverride_imageAnalysisIsRecreated() {
236         // Arrange: record the original ImageAnalysis
237         val originalImageAnalysis = controller.mImageAnalysis
238         // Act: set a Analyzer with overridden size.
239         controller.setImageAnalysisAnalyzer(mainThreadExecutor(), createAnalyzer(Size(1, 1)))
240         // Assert: the ImageAnalysis has be recreated.
241         assertThat(controller.mImageAnalysis).isNotEqualTo(originalImageAnalysis)
242         val newImageAnalysis = controller.mImageAnalysis
243         // Act: set a Analyzer with a different overridden size.
244         controller.setImageAnalysisAnalyzer(mainThreadExecutor(), createAnalyzer(Size(1, 2)))
245         // Assert: the ImageAnalysis has be recreated, again.
246         assertThat(controller.mImageAnalysis).isNotEqualTo(newImageAnalysis)
247     }
248 
249     @Test
250     fun clearAnalyzerWithResolutionOverride_imageAnalysisIsRecreated() {
251         // Arrange: set a Analyzer with resolution and record the ImageAnalysis.
252         controller.setImageAnalysisAnalyzer(mainThreadExecutor(), createAnalyzer(Size(1, 1)))
253         val originalImageAnalysis = controller.mImageAnalysis
254         // Act: clear Analyzer
255         controller.clearImageAnalysisAnalyzer()
256         // Assert: the ImageAnalysis has been recreated.
257         assertThat(controller.mImageAnalysis).isNotEqualTo(originalImageAnalysis)
258     }
259 
260     @Test
261     fun setAnalyzerWithNoOverride_imageAnalysisIsNotRecreated() {
262         // Arrange: record the original ImageAnalysis
263         val originalImageAnalysis = controller.mImageAnalysis
264         // Act: setAnalyzer with no resolution.
265         controller.setImageAnalysisAnalyzer(mainThreadExecutor(), createAnalyzer(null))
266         // Assert: the ImageAnalysis is the same.
267         assertThat(controller.mImageAnalysis).isEqualTo(originalImageAnalysis)
268     }
269 
270     @Test
271     fun setAnalysisFormat_setSuccessfully() {
272         // Act: set the format to RGBA.
273         controller.imageAnalysisOutputImageFormat = ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888
274         // Assert: returned format is RGBA.
275         assertThat(controller.imageAnalysisOutputImageFormat)
276             .isEqualTo(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
277     }
278 
279     /** Creates a [ImageAnalysis.Analyzer] with the given resolution override. */
280     private fun createAnalyzer(size: Size?): ImageAnalysis.Analyzer {
281         return object : ImageAnalysis.Analyzer {
282             override fun analyze(image: ImageProxy) {
283                 // no-op
284             }
285 
286             override fun getDefaultTargetResolution(): Size? {
287                 return size
288             }
289         }
290     }
291 
292     @Test
293     fun viewTransform_valueIsPassedToAnalyzer() {
294         // Non-null value passed to analyzer.
295         assertThat(
296                 getPreviewTransformPassedToAnalyzer(
297                     COORDINATE_SYSTEM_VIEW_REFERENCED,
298                     previewViewTransform
299                 )
300             )
301             .isEqualTo(previewViewTransform)
302 
303         // Null value passed to analyzer.
304         assertThat(getPreviewTransformPassedToAnalyzer(COORDINATE_SYSTEM_VIEW_REFERENCED, null))
305             .isEqualTo(null)
306     }
307 
308     @Test
309     fun originalTransform_valueIsNotPassedToAnalyzer() {
310         // Value not passed to analyzer. Analyzer still has it's original value which is identity
311         // matrix.
312         assertThat(
313                 getPreviewTransformPassedToAnalyzer(
314                         COORDINATE_SYSTEM_ORIGINAL,
315                         previewViewTransform
316                     )!!
317                     .isIdentity
318             )
319             .isTrue()
320     }
321 
322     private fun getPreviewTransformPassedToAnalyzer(
323         coordinateSystem: Int,
324         previewTransform: Matrix?
325     ): Matrix? {
326         var matrix: Matrix? = Matrix()
327         val analyzer =
328             object : ImageAnalysis.Analyzer {
329                 override fun analyze(image: ImageProxy) {
330                     // no-op
331                 }
332 
333                 override fun updateTransform(newMatrix: Matrix?) {
334                     matrix = newMatrix
335                 }
336 
337                 override fun getTargetCoordinateSystem(): Int {
338                     return coordinateSystem
339                 }
340             }
341         controller.setImageAnalysisAnalyzer(mainThreadExecutor(), analyzer)
342         controller.updatePreviewViewTransform(previewTransform)
343         return matrix
344     }
345 
346     @UiThreadTest
347     @Test
348     fun setPreviewResolutionSelector() {
349         controller.previewResolutionSelector = resolutionSelector
350         assertThat(controller.previewResolutionSelector).isEqualTo(resolutionSelector)
351 
352         val config = controller.mPreview.currentConfig as ImageOutputConfig
353         assertThat(config.resolutionSelector).isEqualTo(resolutionSelector)
354     }
355 
356     @UiThreadTest
357     @Test
358     fun setAnalysisResolutionSelector() {
359         controller.imageAnalysisResolutionSelector = resolutionSelector
360         assertThat(controller.imageAnalysisResolutionSelector).isEqualTo(resolutionSelector)
361 
362         val config = controller.mImageAnalysis.currentConfig as ImageOutputConfig
363         assertThat(config.resolutionSelector).isEqualTo(resolutionSelector)
364     }
365 
366     @UiThreadTest
367     @Test
368     fun setImageCaptureResolutionSelector() {
369         controller.imageCaptureResolutionSelector = resolutionSelector
370         assertThat(controller.imageCaptureResolutionSelector).isEqualTo(resolutionSelector)
371 
372         val config = controller.mImageCapture.currentConfig as ImageOutputConfig
373         assertThat(config.resolutionSelector).isEqualTo(resolutionSelector)
374     }
375 
376     @UiThreadTest
377     @Test
378     @Suppress("deprecation")
379     fun setPreviewAspectRatio() {
380         controller.previewTargetSize = targetSizeWithAspectRatio
381         assertThat(controller.previewTargetSize).isEqualTo(targetSizeWithAspectRatio)
382 
383         val config = controller.mPreview.currentConfig as ImageOutputConfig
384         assertThat(config.targetAspectRatio).isEqualTo(targetSizeWithAspectRatio.aspectRatio)
385     }
386 
387     @UiThreadTest
388     @Test
389     @Suppress("deprecation")
390     fun setPreviewResolution() {
391         controller.previewTargetSize = targetSizeWithResolution
392         assertThat(controller.previewTargetSize).isEqualTo(targetSizeWithResolution)
393 
394         val config = controller.mPreview.currentConfig as ImageOutputConfig
395         assertThat(config.targetResolution).isEqualTo(targetSizeWithResolution.resolution)
396     }
397 
398     @UiThreadTest
399     @Test
400     @Suppress("deprecation")
401     fun setAnalysisAspectRatio() {
402         controller.imageAnalysisTargetSize = targetSizeWithAspectRatio
403         assertThat(controller.imageAnalysisTargetSize).isEqualTo(targetSizeWithAspectRatio)
404 
405         val config = controller.mImageAnalysis.currentConfig as ImageOutputConfig
406         assertThat(config.targetAspectRatio).isEqualTo(targetSizeWithAspectRatio.aspectRatio)
407     }
408 
409     @UiThreadTest
410     @Test
411     fun setAnalysisBackgroundExecutor() {
412         val executor = Executors.newSingleThreadExecutor()
413         controller.imageAnalysisBackgroundExecutor = executor
414         assertThat(controller.imageAnalysisBackgroundExecutor).isEqualTo(executor)
415         val config = controller.mImageAnalysis.currentConfig as ImageAnalysisConfig
416         assertThat(config.backgroundExecutor).isEqualTo(executor)
417     }
418 
419     @UiThreadTest
420     @Test
421     fun setAnalysisQueueDepth() {
422         controller.imageAnalysisImageQueueDepth = 100
423         assertThat(controller.imageAnalysisImageQueueDepth).isEqualTo(100)
424         assertThat(controller.mImageAnalysis.imageQueueDepth).isEqualTo(100)
425     }
426 
427     @UiThreadTest
428     @Test
429     fun setAnalysisBackpressureStrategy() {
430         controller.imageAnalysisBackpressureStrategy = ImageAnalysis.STRATEGY_BLOCK_PRODUCER
431         assertThat(controller.imageAnalysisBackpressureStrategy)
432             .isEqualTo(ImageAnalysis.STRATEGY_BLOCK_PRODUCER)
433         assertThat(controller.mImageAnalysis.backpressureStrategy)
434             .isEqualTo(ImageAnalysis.STRATEGY_BLOCK_PRODUCER)
435     }
436 
437     @UiThreadTest
438     @Test
439     @Suppress("deprecation")
440     fun setImageCaptureResolution() {
441         controller.imageCaptureTargetSize = targetSizeWithResolution
442         assertThat(controller.imageCaptureTargetSize).isEqualTo(targetSizeWithResolution)
443 
444         val config = controller.mImageCapture.currentConfig as ImageOutputConfig
445         assertThat(config.targetResolution).isEqualTo(targetSizeWithResolution.resolution)
446     }
447 
448     @UiThreadTest
449     @Test
450     @Suppress("deprecation")
451     fun setImageCaptureAspectRatio() {
452         controller.imageCaptureTargetSize = targetSizeWithAspectRatio
453         assertThat(controller.imageCaptureTargetSize).isEqualTo(targetSizeWithAspectRatio)
454 
455         val config = controller.mImageCapture.currentConfig as ImageOutputConfig
456         assertThat(config.targetAspectRatio).isEqualTo(targetSizeWithAspectRatio.aspectRatio)
457     }
458 
459     @UiThreadTest
460     @Test
461     fun setImageCaptureMode() {
462         controller.imageCaptureMode = ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
463         assertThat(controller.imageCaptureMode)
464             .isEqualTo(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
465         assertThat(controller.mImageCapture.captureMode)
466             .isEqualTo(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
467     }
468 
469     @UiThreadTest
470     @Test
471     fun setImageCaptureIoExecutor() {
472         val ioExecutor = Executors.newSingleThreadExecutor()
473         controller.imageCaptureIoExecutor = ioExecutor
474         assertThat(controller.imageCaptureIoExecutor).isEqualTo(ioExecutor)
475         val config = controller.mImageCapture.currentConfig as ImageCaptureConfig
476         assertThat(config.ioExecutor).isEqualTo(ioExecutor)
477     }
478 
479     @UiThreadTest
480     @Test
481     fun setVideoCaptureQuality() {
482         val qualitySelector = QualitySelector.from(targetVideoQuality)
483         controller.videoCaptureQualitySelector = qualitySelector
484         assertThat(controller.videoCaptureQualitySelector).isEqualTo(qualitySelector)
485     }
486 
487     @UiThreadTest
488     @Test
489     fun setVideoCaptureMirrorMode() {
490         controller.videoCaptureMirrorMode = MirrorMode.MIRROR_MODE_ON_FRONT_ONLY
491         assertThat(controller.videoCaptureMirrorMode)
492             .isEqualTo(MirrorMode.MIRROR_MODE_ON_FRONT_ONLY)
493         assertThat(controller.mVideoCapture.mirrorMode)
494             .isEqualTo(MirrorMode.MIRROR_MODE_ON_FRONT_ONLY)
495     }
496 
497     @UiThreadTest
498     @Test
499     fun setVideoCaptureDynamicRange() {
500         controller.videoCaptureDynamicRange = DynamicRange.HDR10_10_BIT
501         assertThat(controller.videoCaptureDynamicRange).isEqualTo(DynamicRange.HDR10_10_BIT)
502         assertThat(controller.mVideoCapture.dynamicRange).isEqualTo(DynamicRange.HDR10_10_BIT)
503     }
504 
505     @UiThreadTest
506     @Test
507     fun setPreviewDynamicRange() {
508         controller.previewDynamicRange = DynamicRange.HDR10_10_BIT
509         assertThat(controller.previewDynamicRange).isEqualTo(DynamicRange.HDR10_10_BIT)
510         assertThat(controller.mPreview.dynamicRange).isEqualTo(DynamicRange.HDR10_10_BIT)
511     }
512 
513     @UiThreadTest
514     @Test
515     fun setVideoCaptureFrameRate() {
516         controller.videoCaptureTargetFrameRate = Range.create(60, 120)
517         assertThat(controller.videoCaptureTargetFrameRate).isEqualTo(Range.create(60, 120))
518         assertThat(controller.mVideoCapture.targetFrameRate).isEqualTo(Range.create(60, 120))
519     }
520 
521     @UiThreadTest
522     @Test
523     fun sensorRotationChanges_useCaseTargetRotationUpdated() {
524         // Act.
525         controller.mDeviceRotationListener.onRotationChanged(Surface.ROTATION_180)
526 
527         // Assert.
528         assertThat(controller.mImageAnalysis.targetRotation).isEqualTo(Surface.ROTATION_180)
529         assertThat(controller.mImageCapture.targetRotation).isEqualTo(Surface.ROTATION_180)
530         val videoConfig = controller.mVideoCapture.currentConfig as ImageOutputConfig
531         assertThat(videoConfig.targetRotation).isEqualTo(Surface.ROTATION_180)
532     }
533 
534     @UiThreadTest
535     @Test
536     fun setSelectorBeforeBound_selectorSet() {
537         // Arrange.
538         assertThat(controller.cameraSelector.lensFacing).isEqualTo(CameraSelector.LENS_FACING_BACK)
539 
540         // Act.
541         controller.cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
542 
543         // Assert.
544         assertThat(controller.cameraSelector.lensFacing).isEqualTo(CameraSelector.LENS_FACING_FRONT)
545     }
546 
547     @Test
548     fun throwsException_whenScreenFlashModeSetWithBackCamera() {
549         controller.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
550 
551         Assert.assertThrows(IllegalArgumentException::class.java) {
552             controller.imageCaptureFlashMode = ImageCapture.FLASH_MODE_SCREEN
553         }
554     }
555 
556     @Test
557     fun canSetScreenFlashMode_whenScreenFlashUiInfoNotSetYet() {
558         controller.cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
559         controller.imageCaptureFlashMode = ImageCapture.FLASH_MODE_SCREEN
560 
561         assertThat(controller.imageCaptureFlashMode).isEqualTo(ImageCapture.FLASH_MODE_SCREEN)
562     }
563 
564     @Test
565     fun canTakePictureWithScreenFlashMode_whenFrontCameraAndScreenFlashUiInfoSet() {
566         controller.cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
567         controller.setScreenFlashUiInfo(
568             ScreenFlashUiInfo(
569                 ScreenFlashUiInfo.ProviderType.SCREEN_FLASH_VIEW,
570                 object : ScreenFlash {
571                     override fun apply(
572                         expirationTimeMillis: Long,
573                         screenFlashListener: ImageCapture.ScreenFlashListener,
574                     ) {
575                         screenFlashListener.onCompleted()
576                     }
577 
578                     override fun clear() {}
579                 }
580             )
581         )
582 
583         controller.imageCaptureFlashMode = ImageCapture.FLASH_MODE_SCREEN
584         completeCameraInitialization()
585 
586         controller.takePicture(
587             MoreExecutors.directExecutor(),
588             object : ImageCapture.OnImageCapturedCallback() {}
589         )
590 
591         // ensure FLASH_MODE_SCREEN was retained
592         assertThat(controller.imageCaptureFlashMode).isEqualTo(ImageCapture.FLASH_MODE_SCREEN)
593     }
594 
595     @Test
596     fun throwException_whenTakePictureWithScreenFlashModeButWithoutScreenFlashUiInfo() {
597         controller.cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
598         controller.imageCaptureFlashMode = ImageCapture.FLASH_MODE_SCREEN
599         completeCameraInitialization()
600 
601         Assert.assertThrows(IllegalStateException::class.java) {
602             controller.takePicture(
603                 MoreExecutors.directExecutor(),
604                 object : ImageCapture.OnImageCapturedCallback() {}
605             )
606         }
607     }
608 
609     @Test
610     fun throwsException_whenSwitchToBackCameraAfterScreenFlashSetToFrontCamera() {
611         controller.cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
612         controller.imageCaptureFlashMode = ImageCapture.FLASH_MODE_SCREEN
613 
614         Assert.assertThrows(IllegalStateException::class.java) {
615             controller.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
616         }
617     }
618 
619     @UiThreadTest
620     @Test
621     fun preview_surfaceProviderIsPreserved_afterRebind() {
622         // Arrange.
623         val surfaceProvider = SurfaceProvider {}
624         controller.attachPreviewSurface(surfaceProvider, fakeViewPort)
625 
626         // Act: Setting a different resolution selector triggers a rebinding.
627         controller.previewResolutionSelector = resolutionSelector
628 
629         // Assert.
630         assertThat(controller.mPreview.surfaceProvider).isSameInstanceAs(surfaceProvider)
631     }
632 
633     @UiThreadTest
634     @Test
635     fun imageCapture_flashModeIsPreserved_afterRebind() {
636         // Arrange.
637         controller.imageCaptureFlashMode = FLASH_MODE_ON
638 
639         // Act: Setting a different resolution selector triggers a rebinding.
640         controller.imageCaptureResolutionSelector = resolutionSelector
641 
642         // Assert.
643         assertThat(controller.imageCaptureFlashMode).isEqualTo(FLASH_MODE_ON)
644     }
645 
646     @Suppress("deprecation")
647     @Test
648     fun setResolutionSelectorAndOutputSizeAtTheSameTime() {
649         // Arrange & Act: Set resolution selector and target size together.
650         controller.previewResolutionSelector = resolutionSelector
651         controller.imageCaptureResolutionSelector = resolutionSelector
652         controller.imageAnalysisResolutionSelector = resolutionSelector
653         controller.previewTargetSize = targetSizeWithResolution
654         controller.imageCaptureTargetSize = targetSizeWithResolution
655         controller.imageAnalysisTargetSize = targetSizeWithResolution
656 
657         // Assert: The resolution selector should be set, while the target resolution should not.
658         val previewConfig = controller.mPreview.currentConfig as ImageOutputConfig
659         assertThat(previewConfig.resolutionSelector).isEqualTo(resolutionSelector)
660         assertThat(previewConfig.getTargetResolution(null)).isNull()
661         val imageCaptureConfig = controller.mImageCapture.currentConfig as ImageOutputConfig
662         assertThat(imageCaptureConfig.resolutionSelector).isEqualTo(resolutionSelector)
663         assertThat(imageCaptureConfig.getTargetResolution(null)).isNull()
664         val imageAnalysisConfig = controller.mImageAnalysis.currentConfig as ImageOutputConfig
665         assertThat(imageAnalysisConfig.resolutionSelector).isEqualTo(resolutionSelector)
666         assertThat(imageAnalysisConfig.getTargetResolution(null)).isNull()
667     }
668 
669     @Test
670     fun setViewport_overrideUseCasesAspectRatio() {
671         // Arrange & Act: Set a 16:9 viewport.
672         controller.attachPreviewSurface(
673             {},
674             ViewPort.Builder(Rational(9, 16), Surface.ROTATION_90).build()
675         )
676 
677         // Assert: The aspect ratio of the use case configs should be override by viewport,
678         // which should be 16:9.
679         val previewConfig = controller.mPreview.currentConfig as ImageOutputConfig
680         assertThat(previewConfig.resolutionSelector.aspectRatioStrategy.preferredAspectRatio)
681             .isEqualTo(RATIO_16_9)
682         val imageCaptureConfig = controller.mImageCapture.currentConfig as ImageOutputConfig
683         assertThat(imageCaptureConfig.resolutionSelector.aspectRatioStrategy.preferredAspectRatio)
684             .isEqualTo(RATIO_16_9)
685         val imageAnalysisConfig = controller.mImageAnalysis.currentConfig as ImageOutputConfig
686         assertThat(imageAnalysisConfig.resolutionSelector.aspectRatioStrategy.preferredAspectRatio)
687             .isEqualTo(RATIO_16_9)
688         assertThat(controller.mVideoCapture.output.aspectRatio).isEqualTo(RATIO_16_9)
689     }
690 
691     @Test
692     fun setViewport_notOverrideUseCasesAspectRatioIfResolutionSelectorAlreadySet() {
693         // Arrange: Set a 4:3 viewport.
694         controller.attachPreviewSurface(
695             {},
696             ViewPort.Builder(Rational(4, 3), Surface.ROTATION_0).build()
697         )
698 
699         // Act: Explicitly set a 16:9 resolution selector.
700         controller.previewResolutionSelector = resolutionSelector
701         controller.imageCaptureResolutionSelector = resolutionSelector
702         controller.imageAnalysisResolutionSelector = resolutionSelector
703         controller.videoCaptureQualitySelector = QualitySelector.from(targetVideoQuality)
704 
705         // Assert: The aspect ratio of the use case configs should not be override.
706         val previewConfig = controller.mPreview.currentConfig as ImageOutputConfig
707         assertThat(previewConfig.resolutionSelector.aspectRatioStrategy.preferredAspectRatio)
708             .isNotEqualTo(RATIO_4_3)
709         val imageCaptureConfig = controller.mImageCapture.currentConfig as ImageOutputConfig
710         assertThat(imageCaptureConfig.resolutionSelector.aspectRatioStrategy.preferredAspectRatio)
711             .isNotEqualTo(RATIO_4_3)
712         val imageAnalysisConfig = controller.mImageAnalysis.currentConfig as ImageOutputConfig
713         assertThat(imageAnalysisConfig.resolutionSelector.aspectRatioStrategy.preferredAspectRatio)
714             .isNotEqualTo(RATIO_4_3)
715         assertThat(controller.mVideoCapture.output.aspectRatio).isNotEqualTo(RATIO_4_3)
716     }
717 
718     @Suppress("deprecation")
719     @Test
720     fun setViewport_notOverrideUseCasesAspectRatioIfOutputSizeAlreadySet() {
721         // Arrange: Set a 4:3 viewport.
722         controller.attachPreviewSurface(
723             {},
724             ViewPort.Builder(Rational(4, 3), Surface.ROTATION_0).build()
725         )
726 
727         // Act: Explicitly set a 16:9 target size.
728         controller.previewTargetSize = targetSizeWithAspectRatio
729         controller.imageCaptureTargetSize = targetSizeWithAspectRatio
730         controller.imageAnalysisTargetSize = targetSizeWithAspectRatio
731 
732         // Assert: The resolution selector should not exist in the config.
733         val previewConfig = controller.mPreview.currentConfig as ImageOutputConfig
734         assertThat(previewConfig.getResolutionSelector(null)).isNull()
735         val imageCaptureConfig = controller.mImageCapture.currentConfig as ImageOutputConfig
736         assertThat(imageCaptureConfig.getResolutionSelector(null)).isNull()
737         val imageAnalysisConfig = controller.mImageAnalysis.currentConfig as ImageOutputConfig
738         assertThat(imageAnalysisConfig.getResolutionSelector(null)).isNull()
739     }
740 
741     @Test
742     fun onTapToFocus_focusMeteringActionSubmittedToCamera() {
743         completeCameraInitialization()
744 
745         controller.onTapToFocus(pointFactory, 0f, 0f)
746         assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction).isNotNull()
747     }
748 
749     @Test
750     fun getTapToFocusInfoState_defaultStateIsTapToFocusNotStarted() {
751         assertThat(controller.tapToFocusInfoState.value?.focusState)
752             .isEqualTo(TAP_TO_FOCUS_NOT_STARTED)
753     }
754 
755     @Test
756     fun getTapToFocusInfoState_tapPointIsSameAsOriginal_whenOnTapToFocusIsCalled() {
757         val tapPoint = PointF(1F, 2F)
758         completeCameraInitialization()
759 
760         controller.onTapToFocus(pointFactory, tapPoint.x, tapPoint.y)
761 
762         shadowOf(getMainLooper()).idle()
763         assertThat(controller.tapToFocusInfoState.value?.tapPoint).isEqualTo(tapPoint)
764     }
765 
766     @Test
767     fun getTapToFocusInfoState_tapPointIsSameAsLastOne_whenOnTapToFocusIsCalledTwice() {
768         completeCameraInitialization()
769 
770         controller.onTapToFocus(pointFactory, TAP_POINT_1.x, TAP_POINT_1.y)
771         controller.onTapToFocus(pointFactory, TAP_POINT_2.x, TAP_POINT_2.y)
772 
773         shadowOf(getMainLooper()).idle()
774         assertThat(controller.tapToFocusInfoState.value?.tapPoint).isEqualTo(TAP_POINT_2)
775     }
776 
777     @Test
778     fun getTapToFocusInfoState_stateIsTapToFocusStarted_whenOnTapToFocusIsCalled() {
779         completeCameraInitialization()
780         fakeCameraControl.disableFocusMeteringAutoComplete(true)
781 
782         controller.onTapToFocus(pointFactory, 0f, 0f)
783 
784         shadowOf(getMainLooper()).idle()
785         assertThat(controller.tapToFocusInfoState.value?.focusState).isEqualTo(TAP_TO_FOCUS_STARTED)
786     }
787 
788     @Suppress("DEPRECATION")
789     @Test
790     fun getTapToFocusState_stateIsTapToFocusStarted_whenOnTapToFocusIsCalled() {
791         completeCameraInitialization()
792         fakeCameraControl.disableFocusMeteringAutoComplete(true)
793 
794         controller.onTapToFocus(pointFactory, 0f, 0f)
795 
796         shadowOf(getMainLooper()).idle()
797         assertThat(controller.tapToFocusState.value).isEqualTo(TAP_TO_FOCUS_STARTED)
798     }
799 
800     @Test
801     fun getTapToFocusInfoState_stateIsTapToFocusFocused_whenSuccessfulResultIsSubmitted() {
802         completeCameraInitialization()
803         fakeCameraControl.disableFocusMeteringAutoComplete(true)
804 
805         controller.onTapToFocus(pointFactory, 0f, 0f)
806         fakeCameraControl.submitFocusMeteringResult(FocusMeteringResult.create(true))
807 
808         shadowOf(getMainLooper()).idle()
809         assertThat(controller.tapToFocusInfoState.value?.focusState).isEqualTo(TAP_TO_FOCUS_FOCUSED)
810     }
811 
812     @Suppress("DEPRECATION")
813     @Test
814     fun getTapToFocusState_stateIsTapToFocusFocused_whenSuccessfulResultIsSubmitted() {
815         completeCameraInitialization()
816         fakeCameraControl.disableFocusMeteringAutoComplete(true)
817 
818         controller.onTapToFocus(pointFactory, 0f, 0f)
819         fakeCameraControl.submitFocusMeteringResult(FocusMeteringResult.create(true))
820 
821         shadowOf(getMainLooper()).idle()
822         assertThat(controller.tapToFocusState.value).isEqualTo(TAP_TO_FOCUS_FOCUSED)
823     }
824 
825     @Test
826     fun getTapToFocusInfoState_stateIsTapToFocusNotFocused_whenUnsuccessfulResultIsSubmitted() {
827         completeCameraInitialization()
828         fakeCameraControl.disableFocusMeteringAutoComplete(true)
829 
830         controller.onTapToFocus(pointFactory, 0f, 0f)
831         fakeCameraControl.submitFocusMeteringResult(FocusMeteringResult.create(false))
832 
833         shadowOf(getMainLooper()).idle()
834         assertThat(controller.tapToFocusInfoState.value?.focusState)
835             .isEqualTo(TAP_TO_FOCUS_NOT_FOCUSED)
836     }
837 
838     @Suppress("DEPRECATION")
839     @Test
840     fun getTapToFocusState_stateIsTapToFocusNotFocused_whenUnsuccessfulResultIsSubmitted() {
841         completeCameraInitialization()
842         fakeCameraControl.disableFocusMeteringAutoComplete(true)
843 
844         controller.onTapToFocus(pointFactory, 0f, 0f)
845         fakeCameraControl.submitFocusMeteringResult(FocusMeteringResult.create(false))
846 
847         shadowOf(getMainLooper()).idle()
848         assertThat(controller.tapToFocusState.value).isEqualTo(TAP_TO_FOCUS_NOT_FOCUSED)
849     }
850 
851     @Test
852     fun getTapToFocusInfoState_stateIsTapToFocusStarted_beforeAutoCancelDurationIsElapsed() {
853         completeCameraInitialization()
854         fakeCameraControl.disableFocusMeteringAutoComplete(true)
855 
856         controller.onTapToFocus(pointFactory, 0f, 0f)
857         ShadowSystemClock.advanceBy(
858             FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS - 1,
859             TimeUnit.MILLISECONDS
860         )
861 
862         shadowOf(getMainLooper()).idle()
863         assertThat(controller.tapToFocusInfoState.value?.focusState).isEqualTo(TAP_TO_FOCUS_STARTED)
864     }
865 
866     @Suppress("DEPRECATION")
867     @Test
868     fun getTapToFocusState_stateIsTapToFocusStarted_beforeAutoCancelDurationIsElapsed() {
869         completeCameraInitialization()
870         fakeCameraControl.disableFocusMeteringAutoComplete(true)
871 
872         controller.onTapToFocus(pointFactory, 0f, 0f)
873         ShadowSystemClock.advanceBy(
874             FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS - 1,
875             TimeUnit.MILLISECONDS
876         )
877 
878         shadowOf(getMainLooper()).idle()
879         assertThat(controller.tapToFocusState.value).isEqualTo(TAP_TO_FOCUS_STARTED)
880     }
881 
882     @Test
883     fun getTapToFocusInfoState_stateIsNotStarted_whenAutoCancelDurationIsElapsedAfterStarted() {
884         completeCameraInitialization()
885         fakeCameraControl.disableFocusMeteringAutoComplete(true)
886 
887         controller.onTapToFocus(pointFactory, 0f, 0f)
888 
889         shadowOf(getMainLooper()).idle()
890         assumeTrue(controller.tapToFocusInfoState.value?.focusState == TAP_TO_FOCUS_STARTED)
891 
892         ShadowSystemClock.advanceBy(FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
893 
894         shadowOf(getMainLooper()).idle()
895         assertThat(controller.tapToFocusInfoState.value?.focusState)
896             .isEqualTo(TAP_TO_FOCUS_NOT_STARTED)
897     }
898 
899     @Suppress("DEPRECATION")
900     @Test
901     fun getTapToFocusState_stateIsNotStarted_whenAutoCancelDurationIsElapsedAfterStarted() {
902         completeCameraInitialization()
903         fakeCameraControl.disableFocusMeteringAutoComplete(true)
904 
905         controller.onTapToFocus(pointFactory, 0f, 0f)
906 
907         shadowOf(getMainLooper()).idle()
908         assumeTrue(controller.tapToFocusState.value == TAP_TO_FOCUS_STARTED)
909 
910         ShadowSystemClock.advanceBy(FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
911 
912         shadowOf(getMainLooper()).idle()
913         assertThat(controller.tapToFocusState.value).isEqualTo(TAP_TO_FOCUS_NOT_STARTED)
914     }
915 
916     @Test
917     fun getTapToFocusInfoState_stateIsNotStarted_whenAutoCancelDurationIsElapsedAfterFocused() {
918         completeCameraInitialization()
919         fakeCameraControl.disableFocusMeteringAutoComplete(true)
920 
921         controller.onTapToFocus(pointFactory, 0f, 0f)
922         fakeCameraControl.submitFocusMeteringResult(FocusMeteringResult.create(true))
923         shadowOf(getMainLooper()).idle()
924         assumeTrue(controller.tapToFocusInfoState.value?.focusState == TAP_TO_FOCUS_FOCUSED)
925 
926         ShadowSystemClock.advanceBy(FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
927 
928         shadowOf(getMainLooper()).idle()
929         assertThat(controller.tapToFocusInfoState.value?.focusState)
930             .isEqualTo(TAP_TO_FOCUS_NOT_STARTED)
931     }
932 
933     @Suppress("DEPRECATION")
934     @Test
935     fun getTapToFocusState_stateIsNotStarted_whenAutoCancelDurationIsElapsedAfterFocused() {
936         completeCameraInitialization()
937         fakeCameraControl.disableFocusMeteringAutoComplete(true)
938 
939         controller.onTapToFocus(pointFactory, 0f, 0f)
940         fakeCameraControl.submitFocusMeteringResult(FocusMeteringResult.create(true))
941         shadowOf(getMainLooper()).idle()
942         assumeTrue(controller.tapToFocusState.value == TAP_TO_FOCUS_FOCUSED)
943 
944         ShadowSystemClock.advanceBy(FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
945 
946         shadowOf(getMainLooper()).idle()
947         assertThat(controller.tapToFocusState.value).isEqualTo(TAP_TO_FOCUS_NOT_STARTED)
948     }
949 
950     @Test
951     fun getTapToFocusInfoState_stateIsNotStarted_whenAutoCancelDurationIsElapsedAfterNotFocused() {
952         completeCameraInitialization()
953         fakeCameraControl.disableFocusMeteringAutoComplete(true)
954 
955         controller.onTapToFocus(pointFactory, 0f, 0f)
956         fakeCameraControl.submitFocusMeteringResult(FocusMeteringResult.create(false))
957         shadowOf(getMainLooper()).idle()
958         assumeTrue(controller.tapToFocusInfoState.value?.focusState == TAP_TO_FOCUS_NOT_FOCUSED)
959 
960         ShadowSystemClock.advanceBy(FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
961 
962         shadowOf(getMainLooper()).idle()
963         assertThat(controller.tapToFocusInfoState.value?.focusState)
964             .isEqualTo(TAP_TO_FOCUS_NOT_STARTED)
965     }
966 
967     @Suppress("DEPRECATION")
968     @Test
969     fun getTapToFocusState_stateIsNotStarted_whenAutoCancelDurationIsElapsedAfterNotFocused() {
970         completeCameraInitialization()
971         fakeCameraControl.disableFocusMeteringAutoComplete(true)
972 
973         controller.onTapToFocus(pointFactory, 0f, 0f)
974         fakeCameraControl.submitFocusMeteringResult(FocusMeteringResult.create(false))
975         shadowOf(getMainLooper()).idle()
976         assumeTrue(controller.tapToFocusState.value == TAP_TO_FOCUS_NOT_FOCUSED)
977 
978         ShadowSystemClock.advanceBy(FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
979 
980         shadowOf(getMainLooper()).idle()
981         assertThat(controller.tapToFocusState.value).isEqualTo(TAP_TO_FOCUS_NOT_STARTED)
982     }
983 
984     @Test
985     fun setTapToFocusAutoCancelDuration_stateIsNotStarted_whenSetDurationIsElapsedAfterStarted() {
986         val autoCancelDurationSeconds = 2L
987 
988         completeCameraInitialization()
989         fakeCameraControl.disableFocusMeteringAutoComplete(true)
990 
991         controller.setTapToFocusAutoCancelDuration(autoCancelDurationSeconds, TimeUnit.SECONDS)
992 
993         controller.onTapToFocus(pointFactory, 0f, 0f)
994 
995         // Ensure focus state has is not the initial NOT_STARTED state.
996         shadowOf(getMainLooper()).idle()
997         assumeTrue(controller.tapToFocusInfoState.value?.focusState == TAP_TO_FOCUS_STARTED)
998 
999         ShadowSystemClock.advanceBy(autoCancelDurationSeconds, TimeUnit.SECONDS)
1000 
1001         shadowOf(getMainLooper()).idle()
1002         assertThat(controller.tapToFocusInfoState.value?.focusState)
1003             .isEqualTo(TAP_TO_FOCUS_NOT_STARTED)
1004     }
1005 
1006     @Test
1007     fun setTapToFocusAutoCancelDuration_stateNeverResetsToNotStarted_whenDurationIsZero() {
1008         completeCameraInitialization()
1009         fakeCameraControl.disableFocusMeteringAutoComplete(true)
1010 
1011         controller.setTapToFocusAutoCancelDuration(0, TimeUnit.SECONDS)
1012 
1013         controller.onTapToFocus(pointFactory, 0f, 0f)
1014 
1015         // Ensure focus state has is not the initial NOT_STARTED state.
1016         shadowOf(getMainLooper()).idle()
1017         assumeTrue(controller.tapToFocusInfoState.value?.focusState == TAP_TO_FOCUS_STARTED)
1018 
1019         // Advance to end of time
1020         ShadowSystemClock.advanceBy(Long.MAX_VALUE, TimeUnit.SECONDS)
1021 
1022         // State is still the previous STARTED state
1023         shadowOf(getMainLooper()).idle()
1024         assertThat(controller.tapToFocusInfoState.value?.focusState).isEqualTo(TAP_TO_FOCUS_STARTED)
1025     }
1026 
1027     @Test
1028     fun getTapToFocusInfoState_stateIsStartedAfterAutoCancelTimeOfFirstTap_whenTappedTwice() {
1029         completeCameraInitialization()
1030         fakeCameraControl.disableFocusMeteringAutoComplete(true)
1031         val tapInterval = FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS / 2
1032 
1033         controller.onTapToFocus(pointFactory, TAP_POINT_1.x, TAP_POINT_1.y)
1034 
1035         // Advance the clock by `tapInterval` to that first tap is supposed to be auto-canceled at
1036         // FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS while 2nd tap is supposed to be canceled at
1037         // FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS + tapInterval
1038         ShadowSystemClock.advanceBy(tapInterval, TimeUnit.MILLISECONDS)
1039         controller.onTapToFocus(pointFactory, TAP_POINT_2.x, TAP_POINT_2.y)
1040 
1041         // Advance the clock to the 1st tap cancellation time by advancing by the remaining time.
1042         ShadowSystemClock.advanceBy(
1043             FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS - tapInterval,
1044             TimeUnit.MILLISECONDS
1045         )
1046 
1047         shadowOf(getMainLooper()).idle()
1048         assertThat(controller.tapToFocusInfoState.value?.focusState).isEqualTo(TAP_TO_FOCUS_STARTED)
1049     }
1050 
1051     @Test
1052     fun getTapToFocusInfoState_stateIsNotStartedAfterAutoCancelTimeOfSecondTap_whenTappedTwice() {
1053         completeCameraInitialization()
1054         fakeCameraControl.disableFocusMeteringAutoComplete(true)
1055         val tapInterval = FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS / 2
1056 
1057         controller.onTapToFocus(pointFactory, TAP_POINT_1.x, TAP_POINT_1.y)
1058 
1059         // Ensure focus state has is not the initial NOT_STARTED state.
1060         shadowOf(getMainLooper()).idle()
1061         assumeTrue(controller.tapToFocusInfoState.value?.focusState == TAP_TO_FOCUS_STARTED)
1062 
1063         // Advance the clock by `tapInterval` to that first tap is supposed to be auto-canceled at
1064         // FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS while 2nd tap is supposed to be canceled at
1065         // FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS + tapInterval
1066         ShadowSystemClock.advanceBy(tapInterval, TimeUnit.MILLISECONDS)
1067         controller.onTapToFocus(pointFactory, TAP_POINT_2.x, TAP_POINT_2.y)
1068 
1069         // Advance the clock to the 2nd tap cancellation time.
1070         ShadowSystemClock.advanceBy(FOCUS_AUTO_CANCEL_DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
1071 
1072         shadowOf(getMainLooper()).idle()
1073         assertThat(controller.tapToFocusInfoState.value?.focusState)
1074             .isEqualTo(TAP_TO_FOCUS_NOT_STARTED)
1075     }
1076 }
1077