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.camera2.pipe.integration
18 
19 import android.content.Context
20 import android.graphics.SurfaceTexture
21 import android.hardware.camera2.CameraCharacteristics
22 import android.hardware.camera2.CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES
23 import android.hardware.camera2.CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES
24 import android.hardware.camera2.CameraCharacteristics.CONTROL_MAX_REGIONS_AE
25 import android.hardware.camera2.CameraCharacteristics.CONTROL_MAX_REGIONS_AF
26 import android.hardware.camera2.CameraCharacteristics.CONTROL_MAX_REGIONS_AWB
27 import android.hardware.camera2.CameraDevice
28 import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_OFF
29 import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON
30 import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH
31 import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH
32 import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY
33 import android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_AUTO
34 import android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE
35 import android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO
36 import android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_OFF
37 import android.hardware.camera2.CameraMetadata.FLASH_MODE_OFF
38 import android.hardware.camera2.CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION
39 import android.hardware.camera2.CaptureRequest.CONTROL_AE_MODE
40 import android.hardware.camera2.CaptureRequest.CONTROL_AE_REGIONS
41 import android.hardware.camera2.CaptureRequest.CONTROL_AF_MODE
42 import android.hardware.camera2.CaptureRequest.CONTROL_AF_REGIONS
43 import android.hardware.camera2.CaptureRequest.CONTROL_AWB_REGIONS
44 import android.hardware.camera2.CaptureRequest.CONTROL_EFFECT_MODE
45 import android.hardware.camera2.CaptureRequest.CONTROL_ZOOM_RATIO
46 import android.hardware.camera2.CaptureRequest.FLASH_MODE
47 import android.hardware.camera2.CaptureRequest.FLASH_MODE_TORCH
48 import android.hardware.camera2.CaptureRequest.SCALER_CROP_REGION
49 import android.hardware.camera2.params.MeteringRectangle
50 import android.os.Build
51 import android.util.Size
52 import android.view.Surface
53 import androidx.camera.camera2.pipe.CameraGraph
54 import androidx.camera.camera2.pipe.FrameInfo
55 import androidx.camera.camera2.pipe.RequestMetadata
56 import androidx.camera.camera2.pipe.integration.adapter.CameraControlAdapter
57 import androidx.camera.camera2.pipe.integration.compat.quirk.CrashWhenTakingPhotoWithAutoFlashAEModeQuirk
58 import androidx.camera.camera2.pipe.integration.compat.quirk.DeviceQuirks
59 import androidx.camera.camera2.pipe.integration.compat.quirk.ImageCaptureFailWithAutoFlashQuirk
60 import androidx.camera.camera2.pipe.integration.compat.workaround.AutoFlashAEModeDisablerImpl
61 import androidx.camera.camera2.pipe.integration.impl.ComboRequestListener
62 import androidx.camera.camera2.pipe.integration.interop.Camera2CameraInfo
63 import androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions
64 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
65 import androidx.camera.camera2.pipe.testing.VerifyResultListener
66 import androidx.camera.camera2.pipe.testing.toCameraControlAdapter
67 import androidx.camera.camera2.pipe.testing.toCameraInfoAdapter
68 import androidx.camera.core.Camera
69 import androidx.camera.core.CameraControl
70 import androidx.camera.core.CameraSelector
71 import androidx.camera.core.FocusMeteringAction
72 import androidx.camera.core.ImageAnalysis
73 import androidx.camera.core.ImageCapture
74 import androidx.camera.core.Preview
75 import androidx.camera.core.SurfaceOrientedMeteringPointFactory
76 import androidx.camera.core.UseCase
77 import androidx.camera.core.impl.DeferrableSurface
78 import androidx.camera.core.impl.Quirks
79 import androidx.camera.core.impl.SessionConfig
80 import androidx.camera.core.impl.utils.futures.Futures
81 import androidx.camera.core.internal.CameraUseCaseAdapter
82 import androidx.camera.testing.impl.CameraUtil
83 import androidx.camera.testing.impl.CameraXUtil
84 import androidx.camera.testing.impl.SurfaceTextureProvider
85 import androidx.camera.testing.impl.fakes.FakeUseCase
86 import androidx.camera.testing.impl.fakes.FakeUseCaseConfig
87 import androidx.concurrent.futures.await
88 import androidx.test.core.app.ApplicationProvider
89 import androidx.test.ext.junit.runners.AndroidJUnit4
90 import androidx.test.filters.LargeTest
91 import androidx.test.filters.SdkSuppress
92 import com.google.common.truth.Truth
93 import com.google.common.util.concurrent.ListenableFuture
94 import java.util.concurrent.ExecutionException
95 import java.util.concurrent.TimeUnit
96 import kotlinx.coroutines.Dispatchers
97 import kotlinx.coroutines.asExecutor
98 import kotlinx.coroutines.runBlocking
99 import kotlinx.coroutines.withContext
100 import org.hamcrest.CoreMatchers.equalTo
101 import org.hamcrest.CoreMatchers.notNullValue
102 import org.junit.After
103 import org.junit.Assert
104 import org.junit.Assume.assumeThat
105 import org.junit.Assume.assumeTrue
106 import org.junit.Before
107 import org.junit.Rule
108 import org.junit.Test
109 import org.junit.runner.RunWith
110 
111 private val TIMEOUT = TimeUnit.SECONDS.toMillis(10)
112 
113 @LargeTest
114 @RunWith(AndroidJUnit4::class)
115 @OptIn(ExperimentalCamera2Interop::class)
116 @SdkSuppress(minSdkVersion = 21)
117 class CameraControlAdapterDeviceTest {
118     private lateinit var cameraSelector: CameraSelector
119     private lateinit var context: Context
120     private lateinit var camera: CameraUseCaseAdapter
121     private lateinit var cameraControl: CameraControlAdapter
122     private lateinit var comboListener: ComboRequestListener
123     private lateinit var characteristics: CameraCharacteristics
124     private var hasFlashUnit: Boolean = false
125     private var testEffectMode: Int? = null
126 
127     private val imageCapture = ImageCapture.Builder().build()
128     private val imageAnalysis =
129         ImageAnalysis.Builder().build().apply {
130             // set analyzer to make it active.
131             setAnalyzer(Dispatchers.Default.asExecutor()) {
132                 // Fake analyzer, do nothing. Close the ImageProxy immediately to prevent the
133                 // closing
134                 // of the CameraDevice from being stuck.
135                 it.close()
136             }
137         }
138 
139     @get:Rule val useCamera = CameraUtil.grantCameraPermissionAndPreTestAndPostTest()
140 
141     @Before
142     fun setUp() {
143         assumeTrue(CameraUtil.hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK))
144 
145         context = ApplicationProvider.getApplicationContext()
146         CameraXUtil.initialize(context, CameraPipeConfig.defaultConfig())
147         cameraSelector =
148             CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
149         camera = CameraUtil.createCameraUseCaseAdapter(context, cameraSelector)
150         cameraControl = camera.cameraControl.toCameraControlAdapter()
151         comboListener = cameraControl.camera2cameraControl.requestListener
152 
153         characteristics = CameraUtil.getCameraCharacteristics(CameraSelector.LENS_FACING_BACK)!!
154 
155         hasFlashUnit = camera.cameraInfo.hasFlashUnit()
156         testEffectMode = camera.getTestEffectMode()
157     }
158 
159     @After
160     fun tearDown(): Unit = runBlocking {
161         if (::camera.isInitialized) {
162             withContext(Dispatchers.Main) { camera.removeUseCases(camera.useCases) }
163         }
164 
165         CameraXUtil.shutdown()[10000, TimeUnit.MILLISECONDS]
166     }
167 
168     // TODO: test all public API of the CameraControl to ensure the RequestOptions still exist
169     //  after adding/removing the UseCase.
170     @Test
171     fun addUseCase_requestOptionsShouldSetToCamera(): Unit = runBlocking {
172         // Arrange.
173         bindUseCase(imageAnalysis)
174         arrangeRequestOptions()
175 
176         // Act.
177         withContext(Dispatchers.Main) { camera.addUseCases(listOf(imageCapture)) }
178 
179         // Assert. Attaching a new UseCase should not change the RequestOptions that we set the
180         // UseCaseCamera, The CaptureRequest after the new UseCase is attached should have the
181         // same RequestOptions as before. The verify block will verify the CaptureRequest has the
182         // same RequestOptions as we arranged.
183         verifyRequestOptions()
184     }
185 
186     // TODO: test all public API of the CameraControl to ensure the RequestOptions still exist
187     //  after adding/removing the UseCase.
188     @Test
189     fun removeUseCase_requestOptionsShouldSetToCamera(): Unit = runBlocking {
190         // Arrange.
191         bindUseCase(imageAnalysis, imageCapture)
192         arrangeRequestOptions()
193 
194         // Act.
195         withContext(Dispatchers.Main) { camera.removeUseCases(listOf(imageCapture)) }
196 
197         // Assert. Removing one of the UseCases (not all) should not change the
198         // RequestOptions that we set the UseCaseCamera, the CaptureRequest after the UseCase
199         // removal should have the same RequestOptions as before. The verify block will verify
200         // the CaptureRequest has the same RequestOptions as we arranged.
201         verifyRequestOptions()
202     }
203 
204     @Test
205     fun setFlashModeAuto_aeModeSetAndRequestUpdated(): Unit = runBlocking {
206         assumeTrue(hasFlashUnit)
207         bindUseCase(imageAnalysis)
208         cameraControl.flashMode = ImageCapture.FLASH_MODE_AUTO
209 
210         waitForResult(captureCount = 60)
211             .verify(
212                 { requestMeta: RequestMetadata, _ ->
213                     requestMeta.isAeMode(CONTROL_AE_MODE_ON_AUTO_FLASH)
214                 },
215                 TIMEOUT
216             )
217         Truth.assertThat(cameraControl.flashMode).isEqualTo(ImageCapture.FLASH_MODE_AUTO)
218     }
219 
220     @Test
221     fun setFlashModeOff_aeModeSetAndRequestUpdated(): Unit = runBlocking {
222         assumeTrue(hasFlashUnit)
223         bindUseCase(imageAnalysis)
224         cameraControl.flashMode = ImageCapture.FLASH_MODE_OFF
225 
226         waitForResult(captureCount = 60)
227             .verify(
228                 { requestMeta: RequestMetadata, _ -> requestMeta.isAeMode(CONTROL_AE_MODE_ON) },
229                 TIMEOUT
230             )
231         Truth.assertThat(cameraControl.flashMode).isEqualTo(ImageCapture.FLASH_MODE_OFF)
232     }
233 
234     @Test
235     fun setFlashModeOn_aeModeSetAndRequestUpdated(): Unit = runBlocking {
236         assumeTrue(hasFlashUnit)
237         bindUseCase(imageAnalysis)
238         cameraControl.flashMode = ImageCapture.FLASH_MODE_ON
239 
240         waitForResult(captureCount = 60)
241             .verify(
242                 { requestMeta: RequestMetadata, _ ->
243                     requestMeta.isAeMode(CONTROL_AE_MODE_ON_ALWAYS_FLASH)
244                 },
245                 TIMEOUT
246             )
247         Truth.assertThat(cameraControl.flashMode).isEqualTo(ImageCapture.FLASH_MODE_ON)
248     }
249 
250     @Test
251     fun enableTorch_aeModeSetAndRequestUpdated(): Unit = runBlocking {
252         assumeTrue(hasFlashUnit)
253         bindUseCase(imageAnalysis)
254         cameraControl.enableTorch(true).await()
255 
256         waitForResult(captureCount = 30)
257             .verify(
258                 { requestMeta: RequestMetadata, frameInfo: FrameInfo ->
259                     frameInfo.requestMetadata[FLASH_MODE] == FLASH_MODE_TORCH &&
260                         requestMeta.isAeMode(CONTROL_AE_MODE_ON)
261                 },
262                 TIMEOUT
263             )
264     }
265 
266     @Test
267     fun disableTorchFlashModeAuto_aeModeSetAndRequestUpdated(): Unit = runBlocking {
268         assumeTrue(hasFlashUnit)
269         bindUseCase(imageAnalysis)
270         cameraControl.flashMode = ImageCapture.FLASH_MODE_AUTO
271         cameraControl.enableTorch(false).await()
272 
273         waitForResult(captureCount = 30)
274             .verify(
275                 { requestMeta: RequestMetadata, frameInfo: FrameInfo ->
276                     frameInfo.requestMetadata[FLASH_MODE] != FLASH_MODE_TORCH &&
277                         requestMeta.isAeMode(CONTROL_AE_MODE_ON_AUTO_FLASH)
278                 },
279                 TIMEOUT
280             )
281     }
282 
283     @Test
284     @SdkSuppress(minSdkVersion = 35)
285     fun enableLowLightBoost_aeModeSetAndRequestUpdated(): Unit = runBlocking {
286         assumeTrue(camera.cameraInfo.isLowLightBoostSupported)
287         bindUseCase(imageAnalysis)
288         cameraControl.enableLowLightBoostAsync(true).await()
289 
290         waitForResult(captureCount = 30)
291             .verify(
292                 { requestMeta: RequestMetadata, frameInfo: FrameInfo ->
293                     requestMeta.isAeMode(CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY) &&
294                         frameInfo.requestMetadata[FLASH_MODE] == FLASH_MODE_OFF
295                 },
296                 TIMEOUT
297             )
298     }
299 
300     @Test
301     @SdkSuppress(minSdkVersion = 35)
302     fun disableLowLightBoostFlashModeAuto_aeModeSetAndRequestUpdated(): Unit = runBlocking {
303         assumeTrue(camera.cameraInfo.isLowLightBoostSupported)
304         bindUseCase(imageAnalysis)
305         cameraControl.flashMode = ImageCapture.FLASH_MODE_AUTO
306         cameraControl.enableLowLightBoostAsync(false).await()
307 
308         waitForResult(captureCount = 30)
309             .verify(
310                 { requestMeta: RequestMetadata, _ ->
311                     requestMeta.isAeMode(CONTROL_AE_MODE_ON_AUTO_FLASH)
312                 },
313                 TIMEOUT
314             )
315     }
316 
317     @Test
318     fun startFocusAndMetering_3ARegionsUpdated() = runBlocking {
319         assumeTrue(
320             characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AF) > 0 ||
321                 characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AE) > 0 ||
322                 characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AWB) > 0
323         )
324         val factory = SurfaceOrientedMeteringPointFactory(1.0f, 1.0f)
325         val action = FocusMeteringAction.Builder(factory.createPoint(0f, 0f)).build()
326         bindUseCase(imageAnalysis)
327 
328         // Act.
329         cameraControl.startFocusAndMetering(action).await()
330 
331         // Assert. Here we verify only 3A region count is correct.
332         val expectedAfCount =
333             characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AF).coerceAtMost(1)
334         val expectedAeCount =
335             characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AE).coerceAtMost(1)
336         val expectedAwbCount =
337             characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AWB).coerceAtMost(1)
338         waitForResult(captureCount = 60)
339             .verify(
340                 { requestMeta: RequestMetadata, _ ->
341                     // some devices may have a 0 weight region added by default, so
342                     // weightedRegionCount
343 
344                     val afRegionMatched =
345                         requestMeta
346                             .getOrDefault(CONTROL_AF_REGIONS, emptyArray())
347                             .weightedRegionCount == expectedAfCount
348 
349                     val aeRegionMatched =
350                         requestMeta
351                             .getOrDefault(CONTROL_AE_REGIONS, emptyArray())
352                             .weightedRegionCount == expectedAeCount
353 
354                     val awbRegionMatched =
355                         requestMeta
356                             .getOrDefault(CONTROL_AWB_REGIONS, emptyArray())
357                             .weightedRegionCount == expectedAwbCount
358 
359                     afRegionMatched && aeRegionMatched && awbRegionMatched
360                 },
361                 TIMEOUT
362             )
363     }
364 
365     @Test
366     fun cancelFocusAndMetering_3ARegionsReset() = runBlocking {
367         assumeTrue(
368             characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AF) > 0 ||
369                 characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AE) > 0 ||
370                 characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AWB) > 0
371         )
372         val factory = SurfaceOrientedMeteringPointFactory(1.0f, 1.0f)
373         val action = FocusMeteringAction.Builder(factory.createPoint(0f, 0f)).build()
374         bindUseCase(imageAnalysis)
375 
376         // Act.
377         cameraControl.startFocusAndMetering(action).await()
378         cameraControl.cancelFocusAndMetering().await()
379 
380         // Assert. The regions are reset to the default.
381         waitForResult(captureCount = 60)
382             .verify(
383                 { requestMeta: RequestMetadata, _ ->
384                     val isDefaultAfRegion =
385                         requestMeta
386                             .getOrDefault(
387                                 CONTROL_AF_REGIONS,
388                                 CameraGraph.Constants3A.METERING_REGIONS_DEFAULT
389                             )
390                             .contentEquals(CameraGraph.Constants3A.METERING_REGIONS_DEFAULT)
391 
392                     val isDefaultAeRegion =
393                         requestMeta
394                             .getOrDefault(
395                                 CONTROL_AE_REGIONS,
396                                 CameraGraph.Constants3A.METERING_REGIONS_DEFAULT
397                             )
398                             .contentEquals(CameraGraph.Constants3A.METERING_REGIONS_DEFAULT)
399 
400                     val isDefaultAwbRegion =
401                         requestMeta
402                             .getOrDefault(
403                                 CONTROL_AWB_REGIONS,
404                                 CameraGraph.Constants3A.METERING_REGIONS_DEFAULT
405                             )
406                             .contentEquals(CameraGraph.Constants3A.METERING_REGIONS_DEFAULT)
407 
408                     isDefaultAfRegion && isDefaultAeRegion && isDefaultAwbRegion
409                 },
410                 TIMEOUT
411             )
412     }
413 
414     @Test
415     fun setTemplatePreview_afModeToContinuousPicture() = runBlocking {
416         assumeTrue(
417             "Device does not support continuous picture AF mode, ignore the test",
418             characteristics.isAfModeSupported(CONTROL_AF_MODE_CONTINUOUS_PICTURE),
419         )
420 
421         bindUseCase(createPreview())
422 
423         // Assert. Verify the afMode.
424         waitForResult(captureCount = 60)
425             .verify(
426                 { requestMeta: RequestMetadata, _ ->
427                     requestMeta.isAfMode(CONTROL_AF_MODE_CONTINUOUS_PICTURE)
428                 },
429                 TIMEOUT
430             )
431     }
432 
433     @Test
434     fun setTemplateRecord_afModeToContinuousVideo() = runBlocking {
435         assumeTrue(
436             "Device does not support continuous video AF mode, ignore the test",
437             characteristics.isAfModeSupported(CONTROL_AF_MODE_CONTINUOUS_VIDEO),
438         )
439 
440         bindUseCase(createFakeRecordingUseCase())
441 
442         // Assert. Verify the afMode.
443         waitForResult(captureCount = 60)
444             .verify(
445                 { requestMeta: RequestMetadata, _ ->
446                     requestMeta.isAfMode(CONTROL_AF_MODE_CONTINUOUS_VIDEO)
447                 },
448                 TIMEOUT
449             )
450     }
451 
452     @Test
453     fun setZoomRatio_operationCanceledExceptionIfNoUseCase() {
454         val ratio = camera.getMaxSupportedZoomRatio()
455         assertFutureFailedWithOperationCancellation(cameraControl.setZoomRatio(ratio))
456     }
457 
458     private fun <T> assertFutureFailedWithOperationCancellation(future: ListenableFuture<T>) {
459         Assert.assertThrows(ExecutionException::class.java) { future[3, TimeUnit.SECONDS] }
460             .apply {
461                 Truth.assertThat(cause)
462                     .isInstanceOf(CameraControl.OperationCanceledException::class.java)
463             }
464     }
465 
466     private fun CameraCharacteristics.getMaxRegionCount(
467         optionMaxRegions: CameraCharacteristics.Key<Int>
468     ) = get(optionMaxRegions) ?: 0
469 
470     private suspend fun arrangeRequestOptions() {
471         cameraControl.setExposureCompensationIndex(1)
472         cameraControl.setZoomRatio(camera.getMaxSupportedZoomRatio())
473         testEffectMode?.let { effectMode ->
474             cameraControl.camera2cameraControl
475                 .setCaptureRequestOptions(
476                     CaptureRequestOptions.Builder()
477                         .setCaptureRequestOption(CONTROL_EFFECT_MODE, effectMode)
478                         .build()
479                 )
480                 .await()
481         }
482 
483         // Ensure the requests are already set to the CaptureRequest.
484         waitForResult()
485             .verify(
486                 { captureRequest: RequestMetadata, _ ->
487                     // Ensure the EV working before testing
488                     assumeThat(
489                         "EV Request doesn't set to CaptureRequest, ignore the test",
490                         captureRequest.request[CONTROL_AE_EXPOSURE_COMPENSATION],
491                         equalTo(1)
492                     )
493 
494                     // Ensure the Camera2Interop working before testing
495                     if (testEffectMode != null) {
496                         assumeThat(
497                             "Camera2Interop Request doesn't set to CaptureRequest, ignore the test",
498                             captureRequest.request[CONTROL_EFFECT_MODE],
499                             equalTo(testEffectMode)
500                         )
501                     }
502 
503                     // Ensure the Zoom working before testing
504                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
505                         assumeThat(
506                             "Zoom Request doesn't set to CaptureRequest, ignore the test",
507                             captureRequest.request[CONTROL_ZOOM_RATIO],
508                             notNullValue()
509                         )
510                     } else {
511                         assumeThat(
512                             "Zoom Request doesn't set to CaptureRequest, ignore the test",
513                             captureRequest.request[SCALER_CROP_REGION],
514                             notNullValue()
515                         )
516                     }
517                     return@verify true
518                 },
519                 TIMEOUT
520             )
521     }
522 
523     private fun Camera.getTestEffectMode(): Int? {
524         return Camera2CameraInfo.from(cameraInfo)
525             .getCameraCharacteristic(CameraCharacteristics.CONTROL_AVAILABLE_EFFECTS)
526             ?.getOrNull(0)
527     }
528 
529     private fun Camera.getMaxSupportedZoomRatio(): Float {
530         return cameraInfo.toCameraInfoAdapter().zoomState.value!!.maxZoomRatio
531     }
532 
533     private suspend fun verifyRequestOptions() {
534         waitForResult(captureCount = 30)
535             .verify(
536                 { metadata: RequestMetadata, _ ->
537                     val checkEV = metadata.request[CONTROL_AE_EXPOSURE_COMPENSATION] == 1
538                     val checkEffectMode =
539                         testEffectMode?.let { metadata.request[CONTROL_EFFECT_MODE] == it } != false
540                     val checkZoom =
541                         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
542                             metadata.request[CONTROL_ZOOM_RATIO] != null
543                         } else {
544                             metadata.request[SCALER_CROP_REGION] != null
545                         }
546 
547                     checkEV && checkEffectMode && checkZoom
548                 },
549                 TIMEOUT
550             )
551     }
552 
553     private fun waitForResult(captureCount: Int = 1): VerifyResultListener =
554         VerifyResultListener(captureCount).also {
555             comboListener.addListener(it, Dispatchers.Default.asExecutor())
556         }
557 
558     private fun bindUseCase(vararg useCases: UseCase) {
559         camera =
560             CameraUtil.createCameraAndAttachUseCase(
561                 context,
562                 cameraSelector,
563                 *useCases,
564             )
565         cameraControl = camera.cameraControl.toCameraControlAdapter()
566     }
567 
568     private fun createFakeRecordingUseCase(): FakeUseCase {
569         return FakeTestUseCase(
570                 FakeUseCaseConfig.Builder().setTargetName("FakeRecordingUseCase").useCaseConfig
571             )
572             .apply { initAndActive() }
573     }
574 
575     private class FakeTestUseCase(config: FakeUseCaseConfig) : FakeUseCase(config) {
576 
577         val deferrableSurface =
578             object : DeferrableSurface() {
579                 init {
580                     terminationFuture.addListener({ cleanUp() }, Dispatchers.IO.asExecutor())
581                 }
582 
583                 private val surfaceTexture =
584                     SurfaceTexture(0).also { it.setDefaultBufferSize(640, 480) }
585                 val testSurface = Surface(surfaceTexture)
586 
587                 override fun provideSurface(): ListenableFuture<Surface> {
588                     return Futures.immediateFuture(testSurface)
589                 }
590 
591                 fun cleanUp() {
592                     testSurface.release()
593                     surfaceTexture.release()
594                 }
595             }
596 
597         fun initAndActive() {
598             val sessionConfigBuilder =
599                 SessionConfig.Builder().apply {
600                     setTemplateType(CameraDevice.TEMPLATE_RECORD)
601                     addSurface(deferrableSurface)
602                 }
603 
604             updateSessionConfig(listOf(sessionConfigBuilder.build()))
605             notifyActive()
606         }
607 
608         override fun onUnbind() {
609             super.onUnbind()
610             deferrableSurface.close()
611         }
612     }
613 
614     private suspend fun createPreview(): Preview =
615         Preview.Builder().build().also { preview ->
616             withContext(Dispatchers.Main) { preview.surfaceProvider = getSurfaceProvider() }
617         }
618 
619     private fun getSurfaceProvider(): Preview.SurfaceProvider {
620         return SurfaceTextureProvider.createSurfaceTextureProvider(
621             object : SurfaceTextureProvider.SurfaceTextureCallback {
622                 override fun onSurfaceTextureReady(
623                     surfaceTexture: SurfaceTexture,
624                     resolution: Size
625                 ) {
626                     // No-op
627                 }
628 
629                 override fun onSafeToRelease(surfaceTexture: SurfaceTexture) {
630                     surfaceTexture.release()
631                 }
632             }
633         )
634     }
635 
636     private fun RequestMetadata.isAfMode(afMode: Int): Boolean {
637         return if (characteristics.isAfModeSupported(afMode)) {
638             getOrDefault(CONTROL_AF_MODE, null) == afMode
639         } else {
640             val fallbackMode =
641                 if (characteristics.isAfModeSupported(CONTROL_AF_MODE_CONTINUOUS_PICTURE)) {
642                     CONTROL_AF_MODE_CONTINUOUS_PICTURE
643                 } else if (characteristics.isAfModeSupported(CONTROL_AF_MODE_AUTO)) {
644                     CONTROL_AF_MODE_AUTO
645                 } else {
646                     CONTROL_AF_MODE_OFF
647                 }
648             getOrDefault(CONTROL_AF_MODE, null) == fallbackMode
649         }
650     }
651 
652     private fun RequestMetadata.isAeMode(aeMode: Int): Boolean {
653         val aeQuirkEnabled =
654             camera.getCameraQuirks().contains(ImageCaptureFailWithAutoFlashQuirk::class.java) ||
655                 DeviceQuirks[CrashWhenTakingPhotoWithAutoFlashAEModeQuirk::class.java] != null
656         val aeModeCorrected =
657             if (aeQuirkEnabled) AutoFlashAEModeDisablerImpl.getCorrectedAeMode(aeMode) else aeMode
658 
659         return if (characteristics.isAeModeSupported(aeModeCorrected)) {
660             getOrDefault(CONTROL_AE_MODE, null) == aeModeCorrected
661         } else {
662             val fallbackMode =
663                 if (characteristics.isAeModeSupported(CONTROL_AE_MODE_ON)) {
664                     CONTROL_AE_MODE_ON
665                 } else {
666                     CONTROL_AE_MODE_OFF
667                 }
668             getOrDefault(CONTROL_AE_MODE, null) == fallbackMode
669         }
670     }
671 
672     private fun Camera.getCameraQuirks(): Quirks {
673         return cameraInfo.toCameraInfoAdapter().cameraQuirks
674     }
675 
676     private fun CameraCharacteristics.isAfModeSupported(afMode: Int) =
677         (get(CONTROL_AF_AVAILABLE_MODES) ?: intArrayOf(-1)).contains(afMode)
678 
679     private fun CameraCharacteristics.isAeModeSupported(aeMode: Int) =
680         (get(CONTROL_AE_AVAILABLE_MODES) ?: intArrayOf(-1)).contains(aeMode)
681 
682     /** Returns the number of metering regions whose weight is greater than 0. */
683     private val Array<MeteringRectangle>.weightedRegionCount: Int
684         get() {
685             var count = 0
686             forEach { count += if (it.meteringWeight != 0) 1 else 0 }
687             return count
688         }
689 }
690