1 /*
<lambda>null2  * Copyright 2022 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.impl
18 
19 import android.graphics.SurfaceTexture
20 import android.hardware.camera2.CameraCharacteristics
21 import android.hardware.camera2.CameraDevice
22 import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH
23 import android.hardware.camera2.CaptureRequest
24 import android.hardware.camera2.CaptureRequest.CONTROL_AE_MODE
25 import android.hardware.camera2.CaptureResult
26 import android.hardware.camera2.params.MeteringRectangle
27 import android.media.Image
28 import android.os.Build
29 import android.os.Looper
30 import android.view.Surface
31 import androidx.camera.camera2.pipe.AeMode
32 import androidx.camera.camera2.pipe.AfMode
33 import androidx.camera.camera2.pipe.AwbMode
34 import androidx.camera.camera2.pipe.FrameInfo
35 import androidx.camera.camera2.pipe.FrameMetadata
36 import androidx.camera.camera2.pipe.FrameNumber
37 import androidx.camera.camera2.pipe.Lock3ABehavior
38 import androidx.camera.camera2.pipe.Request
39 import androidx.camera.camera2.pipe.RequestTemplate
40 import androidx.camera.camera2.pipe.Result3A
41 import androidx.camera.camera2.pipe.StreamId
42 import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
43 import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter
44 import androidx.camera.camera2.pipe.integration.adapter.CaptureResultAdapter
45 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
46 import androidx.camera.camera2.pipe.integration.adapter.ZslControl
47 import androidx.camera.camera2.pipe.integration.adapter.asListenableFuture
48 import androidx.camera.camera2.pipe.integration.compat.workaround.CapturePipelineTorchCorrection
49 import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpAutoFlashAEModeDisabler
50 import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpTemplateParamsOverride
51 import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseFlashModeTorchFor3aUpdate
52 import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseTorchAsFlash
53 import androidx.camera.camera2.pipe.integration.compat.workaround.UseTorchAsFlash
54 import androidx.camera.camera2.pipe.integration.compat.workaround.UseTorchAsFlashImpl
55 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
56 import androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions
57 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
58 import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraph
59 import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraphSession
60 import androidx.camera.camera2.pipe.integration.testing.FakeCameraProperties
61 import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCameraRequestControl
62 import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
63 import androidx.camera.camera2.pipe.testing.FakeFrameInfo
64 import androidx.camera.camera2.pipe.testing.FakeFrameMetadata
65 import androidx.camera.camera2.pipe.testing.FakeRequestFailure
66 import androidx.camera.camera2.pipe.testing.FakeRequestMetadata
67 import androidx.camera.core.ImageCapture
68 import androidx.camera.core.ImageCaptureException
69 import androidx.camera.core.ImageProxy
70 import androidx.camera.core.impl.CaptureConfig
71 import androidx.camera.core.impl.DeferrableSurface
72 import androidx.camera.core.impl.ImmediateSurface
73 import androidx.camera.core.impl.MutableOptionsBundle
74 import androidx.camera.core.impl.SessionConfig
75 import androidx.camera.core.impl.utils.futures.Futures
76 import androidx.camera.core.internal.CameraCaptureResultImageInfo
77 import androidx.camera.testing.impl.mocks.MockScreenFlash
78 import androidx.testutils.MainDispatcherRule
79 import com.google.common.truth.Truth.assertThat
80 import java.util.concurrent.ExecutionException
81 import java.util.concurrent.Semaphore
82 import java.util.concurrent.TimeUnit
83 import kotlin.collections.removeFirst as removeFirstKt
84 import kotlin.test.assertFailsWith
85 import kotlinx.coroutines.CompletableDeferred
86 import kotlinx.coroutines.Deferred
87 import kotlinx.coroutines.ExperimentalCoroutinesApi
88 import kotlinx.coroutines.Job
89 import kotlinx.coroutines.asExecutor
90 import kotlinx.coroutines.awaitAll
91 import kotlinx.coroutines.delay
92 import kotlinx.coroutines.joinAll
93 import kotlinx.coroutines.launch
94 import kotlinx.coroutines.test.StandardTestDispatcher
95 import kotlinx.coroutines.test.TestScope
96 import kotlinx.coroutines.test.advanceUntilIdle
97 import kotlinx.coroutines.test.currentTime
98 import kotlinx.coroutines.test.runTest
99 import kotlinx.coroutines.withTimeoutOrNull
100 import org.junit.After
101 import org.junit.Assert
102 import org.junit.Assume.assumeTrue
103 import org.junit.Before
104 import org.junit.Rule
105 import org.junit.Test
106 import org.junit.runner.RunWith
107 import org.mockito.kotlin.mock
108 import org.mockito.kotlin.whenever
109 import org.robolectric.annotation.Config
110 import org.robolectric.annotation.internal.DoNotInstrument
111 import org.robolectric.util.ReflectionHelpers
112 
113 @OptIn(ExperimentalCoroutinesApi::class, ExperimentalCamera2Interop::class)
114 @RunWith(RobolectricCameraPipeTestRunner::class)
115 @DoNotInstrument
116 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
117 class CapturePipelineTest {
118     private val testScope = TestScope()
119     private val testDispatcher = StandardTestDispatcher(testScope.testScheduler)
120 
121     @get:Rule val mainDispatcherRule = MainDispatcherRule(testDispatcher)
122 
123     private val fakeUseCaseThreads by lazy {
124         UseCaseThreads(testScope, testDispatcher.asExecutor(), testDispatcher)
125     }
126 
127     private val fakeRequestControl =
128         object : FakeUseCaseCameraRequestControl() {
129             val torchUpdateEventList = mutableListOf<Boolean>()
130             val setTorchSemaphore = Semaphore(0)
131 
132             override fun setTorchOnAsync(): Deferred<Result3A> {
133                 torchUpdateEventList.add(true)
134                 setTorchSemaphore.release()
135                 return CompletableDeferred(Result3A(Result3A.Status.OK))
136             }
137 
138             override fun setTorchOffAsync(aeMode: AeMode): Deferred<Result3A> {
139                 torchUpdateEventList.add(false)
140                 setTorchSemaphore.release()
141                 return CompletableDeferred(Result3A(Result3A.Status.OK))
142             }
143         }
144     private val comboRequestListener = ComboRequestListener()
145     private val fakeCameraGraphSession =
146         object : FakeCameraGraphSession() {
147             var requestHandler: (List<Request>) -> Unit = { requests -> requests.complete() }
148             val lock3ASemaphore = Semaphore(0)
149             val unlock3ASemaphore = Semaphore(0)
150             val lock3AForCaptureSemaphore = Semaphore(0)
151             val unlock3APostCaptureSemaphore = Semaphore(0)
152             val submitSemaphore = Semaphore(0)
153 
154             var virtualTimeAtLock3AForCapture: Long = -1
155             var triggerAfAtLock3AForCapture: Boolean = false
156             var waitForAwbAtLock3AForCapture: Boolean = false
157 
158             var cancelAfAtUnlock3AForCapture: Boolean = false
159 
160             override suspend fun lock3A(
161                 aeMode: AeMode?,
162                 afMode: AfMode?,
163                 awbMode: AwbMode?,
164                 aeRegions: List<MeteringRectangle>?,
165                 afRegions: List<MeteringRectangle>?,
166                 awbRegions: List<MeteringRectangle>?,
167                 aeLockBehavior: Lock3ABehavior?,
168                 afLockBehavior: Lock3ABehavior?,
169                 awbLockBehavior: Lock3ABehavior?,
170                 afTriggerStartAeMode: AeMode?,
171                 convergedCondition: ((FrameMetadata) -> Boolean)?,
172                 lockedCondition: ((FrameMetadata) -> Boolean)?,
173                 frameLimit: Int,
174                 convergedTimeLimitNs: Long,
175                 lockedTimeLimitNs: Long
176             ): Deferred<Result3A> {
177                 lock3ASemaphore.release()
178                 return CompletableDeferred(Result3A(Result3A.Status.OK))
179             }
180 
181             override suspend fun unlock3A(
182                 ae: Boolean?,
183                 af: Boolean?,
184                 awb: Boolean?,
185                 unlockedCondition: ((FrameMetadata) -> Boolean)?,
186                 frameLimit: Int,
187                 timeLimitNs: Long
188             ): Deferred<Result3A> {
189                 unlock3ASemaphore.release()
190                 return CompletableDeferred(Result3A(Result3A.Status.OK))
191             }
192 
193             override suspend fun lock3AForCapture(
194                 lockedCondition: ((FrameMetadata) -> Boolean)?,
195                 frameLimit: Int,
196                 timeLimitNs: Long
197             ): Deferred<Result3A> {
198                 lock3AForCaptureSemaphore.release()
199                 return CompletableDeferred(Result3A(Result3A.Status.OK))
200             }
201 
202             override suspend fun lock3AForCapture(
203                 triggerAf: Boolean,
204                 waitForAwb: Boolean,
205                 frameLimit: Int,
206                 timeLimitNs: Long
207             ): Deferred<Result3A> {
208                 virtualTimeAtLock3AForCapture = testScope.currentTime
209                 triggerAfAtLock3AForCapture = triggerAf
210                 waitForAwbAtLock3AForCapture = waitForAwb
211                 lock3AForCaptureSemaphore.release()
212                 return CompletableDeferred(Result3A(Result3A.Status.OK))
213             }
214 
215             override fun submit(requests: List<Request>) {
216                 requestHandler(requests)
217                 submitSemaphore.release()
218             }
219 
220             override suspend fun unlock3APostCapture(cancelAf: Boolean): Deferred<Result3A> {
221                 cancelAfAtUnlock3AForCapture = cancelAf
222                 unlock3APostCaptureSemaphore.release()
223                 return CompletableDeferred(Result3A(Result3A.Status.OK))
224             }
225         }
226     private val fakeStreamId = StreamId(0)
227     private val fakeSurfaceTexture = SurfaceTexture(0).apply { setDefaultBufferSize(640, 480) }
228     private val fakeSurface = Surface(fakeSurfaceTexture)
229     private val fakeDeferrableSurface = ImmediateSurface(fakeSurface)
230     private val singleConfig =
231         CaptureConfig.Builder().apply { addSurface(fakeDeferrableSurface) }.build()
232     private val singleRequest =
233         Request(
234             streams = emptyList(),
235             listeners = emptyList(),
236             parameters = emptyMap(),
237             extras = emptyMap(),
238             template = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
239         )
240     private val fakeCameraProperties =
241         FakeCameraProperties(
242             FakeCameraMetadata(
243                 mapOf(CameraCharacteristics.FLASH_INFO_AVAILABLE to true),
244             )
245         )
246     private val fakeUseCaseGraphConfig =
247         UseCaseGraphConfig(
248             graph = FakeCameraGraph(fakeCameraGraphSession = fakeCameraGraphSession),
249             surfaceToStreamMap = mapOf(fakeDeferrableSurface to fakeStreamId),
250             cameraStateAdapter = CameraStateAdapter(),
251         )
252     private val fakeZslControl =
253         object : ZslControl {
254             var _isZslDisabledByUseCaseConfig = false
255             var _isZslDisabledByFlashMode = false
256             var imageProxyToDequeue: ImageProxy? = null
257 
258             override fun addZslConfig(sessionConfigBuilder: SessionConfig.Builder) {
259                 // Do nothing
260             }
261 
262             override fun clearZslConfig() {
263                 // Do nothing
264             }
265 
266             override fun isZslSurface(
267                 surface: DeferrableSurface,
268                 sessionConfig: SessionConfig
269             ): Boolean {
270                 return false
271             }
272 
273             override fun setZslDisabledByUserCaseConfig(disabled: Boolean) {
274                 _isZslDisabledByUseCaseConfig = disabled
275             }
276 
277             override fun isZslDisabledByUserCaseConfig(): Boolean {
278                 return _isZslDisabledByUseCaseConfig
279             }
280 
281             override fun setZslDisabledByFlashMode(disabled: Boolean) {
282                 _isZslDisabledByFlashMode = disabled
283             }
284 
285             override fun isZslDisabledByFlashMode(): Boolean {
286                 return _isZslDisabledByFlashMode
287             }
288 
289             override fun dequeueImageFromBuffer(): ImageProxy? {
290                 return imageProxyToDequeue
291             }
292         }
293     private val fakeCaptureConfigAdapter =
294         CaptureConfigAdapter(
295             fakeCameraProperties,
296             fakeUseCaseGraphConfig,
297             fakeZslControl,
298             fakeUseCaseThreads,
299             NoOpTemplateParamsOverride,
300         )
301     private var runningRepeatingJob: Job? = null
302         set(value) {
303             runningRepeatingJob?.cancel()
304             field = value
305         }
306 
307     private lateinit var flashControl: FlashControl
308     private lateinit var state3AControl: State3AControl
309     private lateinit var torchControl: TorchControl
310     private lateinit var capturePipeline: CapturePipelineImpl
311 
312     private lateinit var fakeUseCaseCameraState: UseCaseCameraState
313 
314     private val screenFlash = MockScreenFlash()
315 
316     @Before
317     fun setUp() {
318         state3AControl =
319             State3AControl(
320                     fakeCameraProperties,
321                     NoOpAutoFlashAEModeDisabler,
322                 )
323                 .apply { requestControl = fakeRequestControl }
324 
325         torchControl =
326             TorchControl(
327                     fakeCameraProperties,
328                     state3AControl,
329                     fakeUseCaseThreads,
330                 )
331                 .also {
332                     it.requestControl = fakeRequestControl
333 
334                     // Ensure the control is updated after the UseCaseCamera been set.
335                     assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(testScope)).isTrue()
336                     fakeRequestControl.torchUpdateEventList.clear()
337                 }
338 
339         flashControl =
340             FlashControl(
341                     cameraProperties = fakeCameraProperties,
342                     state3AControl = state3AControl,
343                     threads = fakeUseCaseThreads,
344                     torchControl = torchControl,
345                     useFlashModeTorchFor3aUpdate = NotUseFlashModeTorchFor3aUpdate,
346                 )
347                 .apply { setScreenFlash(this@CapturePipelineTest.screenFlash) }
348 
349         fakeUseCaseCameraState =
350             UseCaseCameraState(
351                 fakeUseCaseGraphConfig,
352                 fakeUseCaseThreads,
353                 sessionProcessorManager = null,
354                 templateParamsOverride = NoOpTemplateParamsOverride,
355             )
356 
357         capturePipeline = createCapturePipeline()
358     }
359 
360     @After
361     fun tearDown() {
362         runningRepeatingJob = null
363         fakeSurface.release()
364         fakeSurfaceTexture.release()
365     }
366 
367     @Test
368     fun miniLatency_flashOn_shouldTriggerAePreCapture(): Unit = runTest {
369         flashOn_shouldTriggerAePreCapture(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
370     }
371 
372     @Test
373     fun maxQuality_flashOn_shouldTriggerAePreCapture(): Unit = runTest {
374         flashOn_shouldTriggerAePreCapture(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
375     }
376 
377     private suspend fun TestScope.flashOn_shouldTriggerAePreCapture(imageCaptureMode: Int) {
378         // Arrange.
379         val requestList = mutableListOf<Request>()
380         fakeCameraGraphSession.requestHandler = { requests -> requestList.addAll(requests) }
381 
382         // Act.
383         capturePipeline.submitStillCaptures(
384             configs = listOf(singleConfig),
385             requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
386             sessionConfigOptions = MutableOptionsBundle.create(),
387             captureMode = imageCaptureMode,
388             flashMode = ImageCapture.FLASH_MODE_ON,
389             flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
390         )
391 
392         // Assert.
393         assertThat(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this)).isTrue()
394 
395         // Complete the capture request.
396         assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue()
397         requestList.complete()
398 
399         // Assert 2, unlock3APostCapture should be called.
400         assertThat(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this)).isTrue()
401     }
402 
403     @Test
404     fun miniLatency_flashAutoFlashRequired_shouldTriggerAePreCapture(): Unit = runTest {
405         flashAutoFlashRequired_shouldTriggerAePreCapture(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
406     }
407 
408     @Test
409     fun maxQuality_flashAutoFlashRequired_shouldTriggerAePreCapture(): Unit = runTest {
410         flashAutoFlashRequired_shouldTriggerAePreCapture(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
411     }
412 
413     private suspend fun TestScope.flashAutoFlashRequired_shouldTriggerAePreCapture(
414         imageCaptureMode: Int
415     ) {
416         // Arrange.
417         comboRequestListener.simulateRepeatingResult(
418             resultParameters =
419                 mapOf(
420                     CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED,
421                 )
422         )
423         val requestList = mutableListOf<Request>()
424         fakeCameraGraphSession.requestHandler = { requests -> requestList.addAll(requests) }
425 
426         // Act.
427         capturePipeline.submitStillCaptures(
428             configs = listOf(singleConfig),
429             requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
430             sessionConfigOptions = MutableOptionsBundle.create(),
431             captureMode = imageCaptureMode,
432             flashMode = ImageCapture.FLASH_MODE_AUTO,
433             flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
434         )
435 
436         // Assert 1, lock3AForCapture should be called, but not call unlock3APostCapture
437         // (before capturing is finished).
438         assertThat(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this)).isTrue()
439         assertThat(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this)).isFalse()
440 
441         // Complete the capture request.
442         assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue()
443         requestList.complete()
444 
445         // Assert 2, unlock3APostCapture should be called.
446         assertThat(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this)).isTrue()
447     }
448 
449     @Test
450     fun miniLatency_withTorchAsFlashQuirk_shouldOpenTorch(): Unit = runTest {
451         withTorchAsFlashQuirk_shouldOpenTorch(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
452     }
453 
454     @Test
455     fun maxQuality_withTorchAsFlashQuirk_shouldOpenTorch(): Unit = runTest {
456         withTorchAsFlashQuirk_shouldOpenTorch(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
457     }
458 
459     private suspend fun TestScope.withTorchAsFlashQuirk_shouldOpenTorch(imageCaptureMode: Int) {
460         // Arrange.
461         capturePipeline = createCapturePipeline(useTorchAsFlash = UseTorchAsFlashImpl)
462 
463         val requestList = mutableListOf<Request>()
464         fakeCameraGraphSession.requestHandler = { requests -> requestList.addAll(requests) }
465 
466         comboRequestListener.simulate3aConvergence()
467 
468         // Act.
469         capturePipeline.submitStillCaptures(
470             configs = listOf(singleConfig),
471             requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
472             sessionConfigOptions = MutableOptionsBundle.create(),
473             captureMode = imageCaptureMode,
474             flashMode = ImageCapture.FLASH_MODE_ON,
475             flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
476         )
477 
478         // Assert 1, torch should be turned on.
479         assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isTrue()
480         assertThat(fakeRequestControl.torchUpdateEventList.size).isEqualTo(1)
481         assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isTrue()
482 
483         // Complete the capture request.
484         assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue()
485         requestList.complete()
486 
487         // Assert 2, torch should be turned off.
488         assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isTrue()
489         assertThat(fakeRequestControl.torchUpdateEventList.size).isEqualTo(1)
490         assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isFalse()
491     }
492 
493     @Test
494     fun miniLatency_withTemplateRecord_shouldOpenTorch(): Unit = runTest {
495         withTemplateRecord_shouldOpenTorch(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
496     }
497 
498     @Test
499     fun maxQuality_withTemplateRecord_shouldOpenTorch(): Unit = runTest {
500         withTemplateRecord_shouldOpenTorch(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
501     }
502 
503     private suspend fun TestScope.withTemplateRecord_shouldOpenTorch(imageCaptureMode: Int) {
504         // Arrange.
505         capturePipeline.template = CameraDevice.TEMPLATE_RECORD
506 
507         val requestList = mutableListOf<Request>()
508         fakeCameraGraphSession.requestHandler = { requests -> requestList.addAll(requests) }
509 
510         // Act.
511         capturePipeline.submitStillCaptures(
512             configs = listOf(singleConfig),
513             requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
514             sessionConfigOptions = MutableOptionsBundle.create(),
515             captureMode = imageCaptureMode,
516             flashMode = ImageCapture.FLASH_MODE_ON,
517             flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
518         )
519 
520         // Assert 1, torch should be turned on.
521         assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isTrue()
522         assertThat(fakeRequestControl.torchUpdateEventList.size).isEqualTo(1)
523         assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isTrue()
524 
525         // Complete the capture request.
526         assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue()
527         requestList.complete()
528 
529         // Assert 2, torch should be turned off.
530         assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isTrue()
531         assertThat(fakeRequestControl.torchUpdateEventList.size).isEqualTo(1)
532         assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isFalse()
533     }
534 
535     @Test
536     fun miniLatency_withFlashTypeTorch_shouldOpenTorch(): Unit = runTest {
537         withFlashTypeTorch_shouldOpenTorch(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
538     }
539 
540     @Test
541     fun maxQuality_withFlashTypeTorch_shouldOpenTorch(): Unit = runTest {
542         withFlashTypeTorch_shouldOpenTorch(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
543     }
544 
545     private suspend fun TestScope.withFlashTypeTorch_shouldOpenTorch(imageCaptureMode: Int) {
546         // Arrange.
547         val requestList = mutableListOf<Request>()
548         fakeCameraGraphSession.requestHandler = { requests -> requestList.addAll(requests) }
549 
550         // Act.
551         capturePipeline.submitStillCaptures(
552             configs = listOf(singleConfig),
553             requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
554             sessionConfigOptions = MutableOptionsBundle.create(),
555             captureMode = imageCaptureMode,
556             flashMode = ImageCapture.FLASH_MODE_ON,
557             flashType = ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH,
558         )
559 
560         // Assert 1, torch should be turned on.
561         assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isTrue()
562         assertThat(fakeRequestControl.torchUpdateEventList.size).isEqualTo(1)
563         assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isTrue()
564 
565         // Complete the capture request.
566         assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue()
567         requestList.complete()
568 
569         // Assert 2, torch should be turned off.
570         assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isTrue()
571         assertThat(fakeRequestControl.torchUpdateEventList.size).isEqualTo(1)
572         assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isFalse()
573     }
574 
575     @Test
576     fun miniLatency_flashRequired_withFlashTypeTorchAndNoQuirk_shouldLock3AForCapture(): Unit =
577         runTest {
578             withFlashTypeTorch_shouldLock3AAsNeeded(
579                 capturePipeline,
580                 ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
581                 ImageCapture.FLASH_MODE_ON,
582                 shouldLock3AForCapture = true,
583             )
584         }
585 
586     @Test
587     fun miniLatency_flashRequired_withFlashTypeTorchAndQuirk_shouldNotLock3A(): Unit = runTest {
588         capturePipeline = createCapturePipeline(useTorchAsFlash = UseTorchAsFlashImpl)
589 
590         withFlashTypeTorch_shouldLock3AAsNeeded(
591             capturePipeline,
592             ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
593             ImageCapture.FLASH_MODE_ON,
594             shouldLock3A = false,
595         )
596     }
597 
598     @Test
599     fun miniLatency_flashRequired_withFlashTypeTorchAndQuirk_worksViaTimeoutWithout3aConverge():
600         Unit = runTest {
601         capturePipeline = createCapturePipeline(useTorchAsFlash = UseTorchAsFlashImpl)
602 
603         withFlashTypeTorch_shouldLock3AAsNeeded(
604             capturePipeline,
605             ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
606             ImageCapture.FLASH_MODE_ON,
607             shouldLock3A = false,
608             simulate3aConvergence = false,
609         )
610     }
611 
612     @Test
613     fun miniLatency_flashRequired_withFlashTypeTorchAndQuirk_listenerRemovedAfterTimeout(): Unit =
614         runTest {
615             capturePipeline = createCapturePipeline(useTorchAsFlash = UseTorchAsFlashImpl)
616             val initialListenerSize = comboRequestListener.listeners.size
617 
618             withFlashTypeTorch_shouldLock3AAsNeeded(
619                 capturePipeline,
620                 ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
621                 ImageCapture.FLASH_MODE_ON,
622                 shouldLock3A = false,
623                 simulate3aConvergence = false,
624             )
625 
626             assertThat(comboRequestListener.listeners.size).isEqualTo(initialListenerSize)
627         }
628 
629     @Test
630     fun maxQuality_flashOffWithFlashTypeTorchAndNoQuirk_shouldLock3A(): Unit = runTest {
631         withFlashTypeTorch_shouldLock3AAsNeeded(
632             capturePipeline,
633             ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
634             ImageCapture.FLASH_MODE_OFF,
635             shouldLock3AForCapture = false,
636         )
637     }
638 
639     @Test
640     fun maxQuality_withFlashTypeTorch_shouldLock3AForCapture(): Unit = runTest {
641         withFlashTypeTorch_shouldLock3AAsNeeded(
642             capturePipeline,
643             ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
644             ImageCapture.FLASH_MODE_ON,
645             shouldLock3AForCapture = true,
646         )
647     }
648 
649     @Test
650     fun maxQuality_withFlashTypeTorchAndQuirk_shouldLock3A(): Unit = runTest {
651         capturePipeline = createCapturePipeline(useTorchAsFlash = UseTorchAsFlashImpl)
652 
653         withFlashTypeTorch_shouldLock3AAsNeeded(
654             capturePipeline,
655             ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
656             ImageCapture.FLASH_MODE_ON,
657         )
658     }
659 
660     /**
661      * Tests whether 3A is locked or AE pre-capture is triggered (i.e. lock3AForCapture) for image
662      * captures with flash type torch based on requirements.
663      *
664      * @param shouldLock3AForCapture Whether capture-specific 3A locking should be used which uses
665      *   AE pre-capture. This is not used in most workaround cases (thus false by default) while
666      *   still used in a few safer cases.
667      * @param shouldLock3A Whether 3A should be locked. No 3A locking may be required for minimum
668      *   latency image capture mode. By default, this value depends on [imageCaptureMode] and
669      *   [shouldLock3AForCapture].
670      * @param simulate3aConvergence Whether 3A convergence should be simulated, true by default.
671      */
672     private suspend fun TestScope.withFlashTypeTorch_shouldLock3AAsNeeded(
673         capturePipeline: CapturePipeline,
674         imageCaptureMode: Int,
675         flashMode: Int,
676         shouldLock3AForCapture: Boolean = false,
677         shouldLock3A: Boolean =
678             imageCaptureMode == ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY &&
679                 !shouldLock3AForCapture,
680         simulate3aConvergence: Boolean = true,
681     ) {
682         // Arrange.
683         val requestList = mutableListOf<Request>()
684         fakeCameraGraphSession.requestHandler = { requests -> requestList.addAll(requests) }
685 
686         if (simulate3aConvergence) {
687             comboRequestListener.simulate3aConvergence()
688         }
689 
690         // Act.
691         capturePipeline.submitStillCaptures(
692             configs = listOf(singleConfig),
693             requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
694             sessionConfigOptions = MutableOptionsBundle.create(),
695             captureMode = imageCaptureMode,
696             flashMode = flashMode,
697             flashType = ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH,
698         )
699 
700         // Assert 1, should call lock3A, but not call unlock3A (before capturing is finished).
701         if (shouldLock3A) {
702             assertThat(fakeCameraGraphSession.lock3ASemaphore.tryAcquire(this)).isTrue()
703         } else {
704             assertThat(fakeCameraGraphSession.lock3ASemaphore.tryAcquire(this)).isFalse()
705         }
706 
707         if (shouldLock3AForCapture) {
708             assertThat(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this)).isTrue()
709         } else {
710             assertThat(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this)).isFalse()
711         }
712 
713         // Shouldn't unlock util the capture request is submitted.
714         assertThat(fakeCameraGraphSession.unlock3ASemaphore.tryAcquire(this)).isFalse()
715         assertThat(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this)).isFalse()
716 
717         // Complete the capture request.
718         assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue()
719         requestList.complete()
720 
721         advanceUntilIdle()
722 
723         // Assert 2, should call unlock3A as required.
724         if (shouldLock3A) {
725             assertThat(fakeCameraGraphSession.unlock3ASemaphore.tryAcquire(this)).isTrue()
726         } else {
727             assertThat(fakeCameraGraphSession.unlock3ASemaphore.tryAcquire(this)).isFalse()
728         }
729 
730         if (shouldLock3AForCapture) {
731             assertThat(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this))
732                 .isTrue()
733         } else {
734             assertThat(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this))
735                 .isFalse()
736         }
737     }
738 
739     @Test
740     fun miniLatency_withFlashTypeTorch_shouldNotLock3A(): Unit = runTest {
741         // Act.
742         capturePipeline
743             .submitStillCaptures(
744                 configs = listOf(singleConfig),
745                 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
746                 sessionConfigOptions = MutableOptionsBundle.create(),
747                 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
748                 flashMode = ImageCapture.FLASH_MODE_OFF,
749                 flashType = ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH,
750             )
751             .awaitAllWithTimeout()
752 
753         // Assert, there is no invocation on lock3A().
754         assertThat(fakeCameraGraphSession.lock3ASemaphore.tryAcquire(this)).isFalse()
755     }
756 
757     @Test
758     fun withFlashTypeTorch_torchAlreadyOn_skipTurnOnTorch(): Unit = runTest {
759         // Arrange.
760         // Ensure the torch is already turned on before capturing.
761         torchControl.setTorchAsync(true)
762         assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isTrue()
763 
764         // Act.
765         capturePipeline
766             .submitStillCaptures(
767                 configs = listOf(singleConfig),
768                 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
769                 sessionConfigOptions = MutableOptionsBundle.create(),
770                 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
771                 flashMode = ImageCapture.FLASH_MODE_ON,
772                 flashType = ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH,
773             )
774             .awaitAllWithTimeout()
775 
776         // Assert, there is no invocation on setTorch().
777         assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isFalse()
778     }
779 
780     @Test
781     fun miniLatency_shouldNotAePreCapture(): Unit = runTest {
782         // Act.
783         capturePipeline
784             .submitStillCaptures(
785                 configs = listOf(singleConfig),
786                 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
787                 sessionConfigOptions = MutableOptionsBundle.create(),
788                 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
789                 flashMode = ImageCapture.FLASH_MODE_OFF,
790                 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
791             )
792             .awaitAllWithTimeout()
793 
794         // Assert, there is only 1 single capture request.
795         assertThat(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this)).isFalse()
796     }
797 
798     @Config(minSdk = 23)
799     @Test
800     fun submitZslCaptureRequests_withZslTemplate_templateZeroShutterLagSent(): Unit = runTest {
801         // Arrange.
802         val requestList = mutableListOf<Request>()
803         fakeCameraGraphSession.requestHandler = { requests ->
804             requestList.addAll(requests)
805             requests.complete()
806         }
807         val imageCaptureConfig =
808             CaptureConfig.Builder().let {
809                 it.addSurface(fakeDeferrableSurface)
810                 it.templateType = CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG
811                 it.build()
812             }
813         configureZslControl()
814 
815         // Act.
816         capturePipeline
817             .submitStillCaptures(
818                 listOf(imageCaptureConfig),
819                 RequestTemplate(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG),
820                 MutableOptionsBundle.create(),
821                 captureMode = ImageCapture.CAPTURE_MODE_ZERO_SHUTTER_LAG,
822                 flashMode = ImageCapture.FLASH_MODE_OFF,
823                 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
824             )
825             .awaitAllWithTimeout()
826         advanceUntilIdle()
827 
828         // Assert.
829         val request = requestList.single()
830         assertThat(request.streams.single()).isEqualTo(fakeStreamId)
831         assertThat(request.template)
832             .isEqualTo(RequestTemplate(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG))
833     }
834 
835     @Config(minSdk = 23)
836     @Test
837     fun submitZslCaptureRequests_withNoTemplate_templateStillPictureSent(): Unit = runTest {
838         // Arrange.
839         val requestList = mutableListOf<Request>()
840         fakeCameraGraphSession.requestHandler = { requests ->
841             requestList.addAll(requests)
842             requests.complete()
843         }
844         val imageCaptureConfig =
845             CaptureConfig.Builder().let {
846                 it.addSurface(fakeDeferrableSurface)
847                 it.build()
848             }
849         configureZslControl()
850 
851         // Act.
852         capturePipeline
853             .submitStillCaptures(
854                 listOf(imageCaptureConfig),
855                 RequestTemplate(CameraDevice.TEMPLATE_PREVIEW),
856                 MutableOptionsBundle.create(),
857                 captureMode = ImageCapture.CAPTURE_MODE_ZERO_SHUTTER_LAG,
858                 flashMode = ImageCapture.FLASH_MODE_OFF,
859                 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
860             )
861             .awaitAllWithTimeout()
862 
863         // Assert.
864         val request = requestList.single()
865         assertThat(request.streams.single()).isEqualTo(fakeStreamId)
866         assertThat(request.template).isEqualTo(RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE))
867     }
868 
869     @Config(minSdk = 23)
870     @Test
871     fun submitZslCaptureRequests_withZslDisabledByUseCaseConfig_templateStillPictureSent(): Unit =
872         runTest {
873             // Arrange.
874             val requestList = mutableListOf<Request>()
875             fakeCameraGraphSession.requestHandler = { requests ->
876                 requestList.addAll(requests)
877                 requests.complete()
878             }
879             val imageCaptureConfig =
880                 CaptureConfig.Builder().let {
881                     it.addSurface(fakeDeferrableSurface)
882                     it.templateType = CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG
883                     it.build()
884                 }
885             configureZslControl()
886             fakeZslControl.setZslDisabledByUserCaseConfig(true)
887 
888             // Act.
889             capturePipeline
890                 .submitStillCaptures(
891                     listOf(imageCaptureConfig),
892                     RequestTemplate(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG),
893                     MutableOptionsBundle.create(),
894                     captureMode = ImageCapture.CAPTURE_MODE_ZERO_SHUTTER_LAG,
895                     flashMode = ImageCapture.FLASH_MODE_OFF,
896                     flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
897                 )
898                 .awaitAllWithTimeout()
899 
900             // Assert.
901             val request = requestList.single()
902             assertThat(request.streams.single()).isEqualTo(fakeStreamId)
903             assertThat(request.template)
904                 .isEqualTo(RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE))
905         }
906 
907     @Config(minSdk = 23)
908     @Test
909     fun submitZslCaptureRequests_withZslDisabledByFlashMode_templateStillPictureSent(): Unit =
910         runTest {
911             // Arrange.
912             val requestList = mutableListOf<Request>()
913             fakeCameraGraphSession.requestHandler = { requests ->
914                 requestList.addAll(requests)
915                 requests.complete()
916             }
917             val imageCaptureConfig =
918                 CaptureConfig.Builder().let {
919                     it.addSurface(fakeDeferrableSurface)
920                     it.templateType = CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG
921                     it.build()
922                 }
923             configureZslControl()
924             fakeZslControl.setZslDisabledByFlashMode(true)
925 
926             // Act.
927             capturePipeline
928                 .submitStillCaptures(
929                     listOf(imageCaptureConfig),
930                     RequestTemplate(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG),
931                     MutableOptionsBundle.create(),
932                     captureMode = ImageCapture.CAPTURE_MODE_ZERO_SHUTTER_LAG,
933                     flashMode = ImageCapture.FLASH_MODE_OFF,
934                     flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
935                 )
936                 .awaitAllWithTimeout()
937 
938             // Assert.
939             val request = requestList.single()
940             assertThat(request.streams.single()).isEqualTo(fakeStreamId)
941             assertThat(request.template)
942                 .isEqualTo(RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE))
943         }
944 
945     private fun configureZslControl() {
946         val fakeImageProxy: ImageProxy = mock()
947         val fakeCaptureResult =
948             CaptureResultAdapter(FakeRequestMetadata(), FrameNumber(1), FakeFrameInfo())
949         val fakeImageInfo = CameraCaptureResultImageInfo(fakeCaptureResult)
950         val fakeImage: Image = mock()
951         whenever(fakeImageProxy.imageInfo).thenReturn(fakeImageInfo)
952         whenever(fakeImageProxy.image).thenReturn(fakeImage)
953         fakeZslControl.imageProxyToDequeue = fakeImageProxy
954     }
955 
956     @Test
957     fun captureFailure_taskShouldFailure(): Unit = runTest {
958         // Arrange.
959         fakeCameraGraphSession.requestHandler = { requests ->
960             requests.forEach { request ->
961                 // Callback capture fail immediately.
962                 request.listeners.forEach {
963                     val requestMetadata = FakeRequestMetadata()
964                     val frameNumber = FrameNumber(100L)
965                     it.onFailed(
966                         requestMetadata = requestMetadata,
967                         frameNumber = frameNumber,
968                         requestFailure = FakeRequestFailure(requestMetadata, frameNumber)
969                     )
970                 }
971             }
972         }
973 
974         // Act.
975         val resultDeferredList =
976             capturePipeline.submitStillCaptures(
977                 configs = listOf(singleConfig),
978                 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
979                 sessionConfigOptions = MutableOptionsBundle.create(),
980                 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
981                 flashMode = ImageCapture.FLASH_MODE_OFF,
982                 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
983             )
984 
985         // Assert.
986         advanceUntilIdle()
987         val exception =
988             assertFailsWith(ImageCaptureException::class) {
989                 resultDeferredList.awaitAllWithTimeout()
990             }
991         assertThat(exception.imageCaptureError).isEqualTo(ImageCapture.ERROR_CAPTURE_FAILED)
992     }
993 
994     @Test
995     fun captureCancel_taskShouldFailureWithCAMERA_CLOSED(): Unit = runTest {
996         // Arrange.
997         fakeCameraGraphSession.requestHandler = { requests ->
998             requests.forEach { request ->
999                 // Callback capture abort immediately.
1000                 request.listeners.forEach { it.onAborted(singleRequest) }
1001             }
1002         }
1003 
1004         // Act.
1005         val resultDeferredList =
1006             capturePipeline.submitStillCaptures(
1007                 configs = listOf(singleConfig),
1008                 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
1009                 sessionConfigOptions = MutableOptionsBundle.create(),
1010                 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
1011                 flashMode = ImageCapture.FLASH_MODE_OFF,
1012                 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
1013             )
1014 
1015         // Assert.
1016         advanceUntilIdle()
1017         val exception =
1018             Assert.assertThrows(ExecutionException::class.java) {
1019                 Futures.allAsList(resultDeferredList.map { it.asListenableFuture() })
1020                     .get(2, TimeUnit.SECONDS)
1021             }
1022         Assert.assertTrue(exception.cause is ImageCaptureException)
1023         assertThat((exception.cause as ImageCaptureException).imageCaptureError)
1024             .isEqualTo(ImageCapture.ERROR_CAMERA_CLOSED)
1025     }
1026 
1027     @Test
1028     fun stillCaptureWithFlashStopRepeatingQuirk_shouldStopRepeatingTemporarily() = runTest {
1029         // Arrange
1030         ReflectionHelpers.setStaticField(Build::class.java, "MANUFACTURER", "SAMSUNG")
1031         ReflectionHelpers.setStaticField(Build::class.java, "MODEL", "SM-A716")
1032 
1033         val submittedRequestList = mutableListOf<Request>()
1034         fakeCameraGraphSession.requestHandler = { requests ->
1035             submittedRequestList.addAll(requests)
1036         }
1037         fakeUseCaseCameraState.update(streams = setOf(StreamId(0)))
1038 
1039         // Act.
1040         capturePipeline.submitStillCaptures(
1041             configs =
1042                 listOf(
1043                     CaptureConfig.Builder()
1044                         .apply {
1045                             addSurface(fakeDeferrableSurface)
1046                             implementationOptions =
1047                                 CaptureRequestOptions.Builder()
1048                                     .apply {
1049                                         setCaptureRequestOption(
1050                                             CONTROL_AE_MODE,
1051                                             CONTROL_AE_MODE_ON_ALWAYS_FLASH
1052                                         )
1053                                     }
1054                                     .build()
1055                         }
1056                         .build()
1057                 ),
1058             requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
1059             sessionConfigOptions = MutableOptionsBundle.create(),
1060             captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
1061             flashMode = ImageCapture.FLASH_MODE_ON,
1062             flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
1063         )
1064 
1065         // Assert, stopRepeating -> submit -> startRepeating flow should be used.
1066         assertThat(fakeCameraGraphSession.stopRepeatingSemaphore.tryAcquire(this)).isTrue()
1067 
1068         assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue()
1069 
1070         // Completing the submitted capture request.
1071         submittedRequestList.complete()
1072 
1073         assertThat(fakeCameraGraphSession.repeatingRequestSemaphore.tryAcquire(this)).isTrue()
1074     }
1075 
1076     @Test
1077     fun stillCaptureWithFlashStopRepeatingQuirkNotEnabled_shouldNotStopRepeating() = runTest {
1078         // Arrange
1079         val submittedRequestList = mutableListOf<Request>()
1080         fakeCameraGraphSession.requestHandler = { requests ->
1081             submittedRequestList.addAll(requests)
1082         }
1083         fakeUseCaseCameraState.update(streams = setOf(StreamId(0)))
1084 
1085         // Act.
1086         capturePipeline.submitStillCaptures(
1087             configs =
1088                 listOf(
1089                     CaptureConfig.Builder()
1090                         .apply {
1091                             addSurface(fakeDeferrableSurface)
1092                             implementationOptions =
1093                                 CaptureRequestOptions.Builder()
1094                                     .apply {
1095                                         setCaptureRequestOption(
1096                                             CONTROL_AE_MODE,
1097                                             CONTROL_AE_MODE_ON_ALWAYS_FLASH
1098                                         )
1099                                     }
1100                                     .build()
1101                         }
1102                         .build()
1103                 ),
1104             requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
1105             sessionConfigOptions = MutableOptionsBundle.create(),
1106             captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
1107             flashMode = ImageCapture.FLASH_MODE_ON,
1108             flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
1109         )
1110 
1111         // Assert, repeating should not be stopped when quirk not enabled.
1112         assertThat(fakeCameraGraphSession.stopRepeatingSemaphore.tryAcquire(this)).isFalse()
1113 
1114         assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue()
1115 
1116         // Resetting repeatingRequestSemaphore because startRepeating can be called before
1117         fakeCameraGraphSession.repeatingRequestSemaphore = Semaphore(0)
1118 
1119         // Completing the submitted capture request.
1120         submittedRequestList.complete()
1121 
1122         assertThat(fakeCameraGraphSession.repeatingRequestSemaphore.tryAcquire(this)).isFalse()
1123     }
1124 
1125     @Test
1126     fun torchAsFlash_torchCorrection_shouldTurnsTorchOffOn(): Unit = runTest {
1127         torchStateCorrectionTest(ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH)
1128     }
1129 
1130     @Test
1131     fun defaultCapture_torchCorrection_shouldTurnsTorchOffOn(): Unit = runTest {
1132         torchStateCorrectionTest(ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH)
1133     }
1134 
1135     private suspend fun TestScope.torchStateCorrectionTest(flashType: Int) {
1136         // Arrange.
1137         torchControl.setTorchAsync(torch = true).join()
1138         verifyTorchState(true)
1139 
1140         val requestList = mutableListOf<Request>()
1141         fakeCameraGraphSession.requestHandler = { requests -> requestList.addAll(requests) }
1142         val capturePipelineTorchCorrection =
1143             CapturePipelineTorchCorrection(
1144                 cameraProperties = FakeCameraProperties(),
1145                 capturePipelineImpl = capturePipeline,
1146                 threads = fakeUseCaseThreads,
1147                 torchControl = torchControl,
1148             )
1149 
1150         // Act.
1151         capturePipelineTorchCorrection.submitStillCaptures(
1152             configs = listOf(singleConfig),
1153             requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
1154             sessionConfigOptions = MutableOptionsBundle.create(),
1155             captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
1156             flashMode = ImageCapture.FLASH_MODE_ON,
1157             flashType = flashType,
1158         )
1159 
1160         assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue()
1161         assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isFalse()
1162         // Complete the capture request.
1163         requestList.complete()
1164 
1165         // Assert, the Torch should be turned off, and then turned on.
1166         verifyTorchState(false)
1167         verifyTorchState(true)
1168         // No more invocation to set Torch mode.
1169         assertThat(fakeRequestControl.torchUpdateEventList.size).isEqualTo(0)
1170     }
1171 
1172     private fun TestScope.verifyTorchState(state: Boolean) {
1173         assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isTrue()
1174         assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt() == state).isTrue()
1175     }
1176 
1177     // TODO(b/326170400): port torch related precapture tests
1178 
1179     @Test
1180     fun lock3aTriggered_whenScreenFlashPreCaptureCalled() = runTest {
1181         capturePipeline.invokeScreenFlashPreCaptureTasks(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
1182 
1183         assertThat(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this)).isTrue()
1184     }
1185 
1186     @Test
1187     fun lock3aTriggeredAfterTimeout_whenScreenFlashApplyNotCompleted() = runTest {
1188         screenFlash.setApplyCompletedInstantly(false)
1189 
1190         capturePipeline.invokeScreenFlashPreCaptureTasks(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
1191 
1192         assertThat(fakeCameraGraphSession.virtualTimeAtLock3AForCapture)
1193             .isEqualTo(
1194                 TimeUnit.SECONDS.toMillis(ImageCapture.SCREEN_FLASH_UI_APPLY_TIMEOUT_SECONDS)
1195             )
1196     }
1197 
1198     @Test
1199     fun afNotTriggered_whenScreenFlashPreCaptureCalledWithMinimizeLatency() = runTest {
1200         capturePipeline.invokeScreenFlashPreCaptureTasks(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
1201 
1202         assumeTrue(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this))
1203         assertThat(fakeCameraGraphSession.triggerAfAtLock3AForCapture).isFalse()
1204     }
1205 
1206     @Test
1207     fun waitsForAwb_whenScreenFlashPreCaptureCalledWithMinimizeLatency() = runTest {
1208         capturePipeline.invokeScreenFlashPreCaptureTasks(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
1209 
1210         assumeTrue(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this))
1211         assertThat(fakeCameraGraphSession.waitForAwbAtLock3AForCapture).isTrue()
1212     }
1213 
1214     @Test
1215     fun afTriggered_whenScreenFlashPreCaptureCalledWithMaximumQuality() = runTest {
1216         capturePipeline.invokeScreenFlashPreCaptureTasks(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
1217 
1218         assumeTrue(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this))
1219         assertThat(fakeCameraGraphSession.triggerAfAtLock3AForCapture).isTrue()
1220     }
1221 
1222     @Test
1223     fun screenFlashClearInvokedInMainThread_whenScreenFlashPostCaptureCalled() = runTest {
1224         capturePipeline.invokeScreenFlashPostCaptureTasks(
1225             ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
1226         )
1227 
1228         assertThat(screenFlash.lastClearThreadLooper).isEqualTo(Looper.getMainLooper())
1229     }
1230 
1231     // TODO(b/326170400): port torch related postcapture tests
1232 
1233     @Test
1234     fun unlock3aTriggered_whenPostCaptureCalled() = runTest {
1235         capturePipeline.invokeScreenFlashPostCaptureTasks(
1236             ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
1237         )
1238 
1239         assertThat(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this)).isTrue()
1240     }
1241 
1242     @Test
1243     fun doesNotCancelAf_whenPostCaptureCalledWithMinimizeLatency() = runTest {
1244         capturePipeline.invokeScreenFlashPostCaptureTasks(
1245             ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
1246         )
1247 
1248         assumeTrue(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this))
1249         assertThat(fakeCameraGraphSession.cancelAfAtUnlock3AForCapture).isFalse()
1250     }
1251 
1252     @Test
1253     fun cancelsAf_whenPostCaptureCalledWithMaximumQuality() = runTest {
1254         capturePipeline.invokeScreenFlashPostCaptureTasks(
1255             ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
1256         )
1257 
1258         assumeTrue(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this))
1259         assertThat(fakeCameraGraphSession.cancelAfAtUnlock3AForCapture).isTrue()
1260     }
1261 
1262     @Test
1263     fun screenFlashApplyInvoked_whenStillCaptureSubmittedWithScreenFlash() = runTest {
1264         capturePipeline
1265             .submitStillCaptures(
1266                 configs = listOf(singleConfig),
1267                 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
1268                 sessionConfigOptions = MutableOptionsBundle.create(),
1269                 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
1270                 flashMode = ImageCapture.FLASH_MODE_SCREEN,
1271                 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
1272             )
1273             .joinAll()
1274 
1275         assertThat(screenFlash.lastApplyThreadLooper).isNotNull()
1276     }
1277 
1278     @Test
1279     fun mainCaptureRequestSubmitted_whenSubmittedWithScreenFlash() = runTest {
1280         capturePipeline
1281             .submitStillCaptures(
1282                 configs = listOf(singleConfig),
1283                 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
1284                 sessionConfigOptions = MutableOptionsBundle.create(),
1285                 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
1286                 flashMode = ImageCapture.FLASH_MODE_SCREEN,
1287                 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
1288             )
1289             .joinAll()
1290 
1291         assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue()
1292     }
1293 
1294     @Test
1295     fun screenFlashClearInvoked_whenStillCaptureSubmittedWithScreenFlash() = runTest {
1296         capturePipeline
1297             .submitStillCaptures(
1298                 configs = listOf(singleConfig),
1299                 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
1300                 sessionConfigOptions = MutableOptionsBundle.create(),
1301                 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
1302                 flashMode = ImageCapture.FLASH_MODE_SCREEN,
1303                 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
1304             )
1305             .joinAll()
1306 
1307         // submitStillCaptures method does not wait for post-capture to be completed, so need to
1308         // wait a little to ensure it is completed
1309         delay(1000)
1310 
1311         assertThat(screenFlash.awaitClear(3000)).isTrue()
1312     }
1313 
1314     private fun createCapturePipeline(
1315         useTorchAsFlash: UseTorchAsFlash = NotUseTorchAsFlash,
1316     ) =
1317         CapturePipelineImpl(
1318             configAdapter = fakeCaptureConfigAdapter,
1319             cameraProperties = fakeCameraProperties,
1320             requestListener = comboRequestListener,
1321             threads = fakeUseCaseThreads,
1322             torchControl = torchControl,
1323             useCaseGraphConfig = fakeUseCaseGraphConfig,
1324             useCaseCameraState = fakeUseCaseCameraState,
1325             useTorchAsFlash = useTorchAsFlash,
1326             sessionProcessorManager = null,
1327             flashControl = flashControl,
1328             videoUsageControl = VideoUsageControl(),
1329         )
1330 
1331     // TODO(wenhungteng@): Porting overrideAeModeForStillCapture_quirkAbsent_notOverride,
1332     //  overrideAeModeForStillCapture_aePrecaptureStarted_override,
1333     //  overrideAeModeForStillCapture_aePrecaptureFinish_notOverride,
1334     //  overrideAeModeForStillCapture_noAePrecaptureTriggered_notOverride
1335 
1336     private fun List<Request>.complete() {
1337         // Callback capture complete.
1338         forEach { request ->
1339             request.listeners.forEach {
1340                 it.onTotalCaptureResult(
1341                     requestMetadata = FakeRequestMetadata(),
1342                     frameNumber = FrameNumber(100L),
1343                     totalCaptureResult = FakeFrameInfo(),
1344                 )
1345             }
1346         }
1347     }
1348 
1349     private fun ComboRequestListener.simulate3aConvergence() {
1350         simulateRepeatingResult(
1351             resultParameters =
1352                 mapOf(
1353                     CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_CONVERGED,
1354                     CaptureResult.CONTROL_AF_STATE to CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED,
1355                     CaptureResult.CONTROL_AWB_STATE to CaptureResult.CONTROL_AWB_STATE_CONVERGED,
1356                 )
1357         )
1358     }
1359 
1360     private fun ComboRequestListener.simulateRepeatingResult(
1361         initialDelay: Long = 100,
1362         period: Long = 100, // in milliseconds
1363         requestParameters: Map<CaptureRequest.Key<*>, Any> = mutableMapOf(),
1364         resultParameters: Map<CaptureResult.Key<*>, Any> = mutableMapOf(),
1365         onResultSubmitted: (FrameInfo) -> Unit = {},
1366     ) {
1367         let { listener ->
1368             runningRepeatingJob =
1369                 fakeUseCaseThreads.scope.launch {
1370                     delay(initialDelay)
1371 
1372                     // the counter uses 1000 frames for repeating request instead of infinity so
1373                     // that
1374                     // coroutine can complete and lead to an idle state, should be sufficient for
1375                     // all
1376                     // our testing purposes here
1377                     var counter = 1000
1378                     while (counter-- > 0) {
1379                         val fakeRequestMetadata =
1380                             FakeRequestMetadata(requestParameters = requestParameters)
1381                         val fakeFrameMetadata = FakeFrameMetadata(resultMetadata = resultParameters)
1382                         val fakeFrameInfo =
1383                             FakeFrameInfo(
1384                                 metadata = fakeFrameMetadata,
1385                                 requestMetadata = fakeRequestMetadata,
1386                             )
1387                         listener.onTotalCaptureResult(
1388                             requestMetadata = fakeRequestMetadata,
1389                             frameNumber = FrameNumber(101L),
1390                             totalCaptureResult = fakeFrameInfo,
1391                         )
1392                         onResultSubmitted(fakeFrameInfo)
1393                         delay(period)
1394                     }
1395                 }
1396         }
1397     }
1398 
1399     private suspend fun <T> Collection<Deferred<T>>.awaitAllWithTimeout(
1400         timeMillis: Long = TimeUnit.SECONDS.toMillis(5)
1401     ) =
1402         checkNotNull(withTimeoutOrNull(timeMillis) { awaitAll() }) {
1403             "Cannot complete the Deferred within $timeMillis"
1404         }
1405 
1406     /**
1407      * Advances TestScope coroutine to idle state (i.e. all tasks completed) before trying to
1408      * acquire semaphore immediately.
1409      *
1410      * This saves time by not having to explicitly wait for a semaphore status to be updated.
1411      */
1412     private fun Semaphore.tryAcquire(testScope: TestScope): Boolean {
1413         testScope.advanceUntilIdle()
1414         return tryAcquire()
1415     }
1416 }
1417