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 /*
18  * Copyright 2021 The Android Open Source Project
19  *
20  * Licensed under the Apache License, Version 2.0 (the "License");
21  * you may not use this file except in compliance with the License.
22  * You may obtain a copy of the License at
23  *
24  *      http://www.apache.org/licenses/LICENSE-2.0
25  *
26  * Unless required by applicable law or agreed to in writing, software
27  * distributed under the License is distributed on an "AS IS" BASIS,
28  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29  * See the License for the specific language governing permissions and
30  * limitations under the License.
31  */
32 
33 package androidx.camera.camera2.pipe.integration.impl
34 
35 import android.hardware.camera2.CameraCharacteristics.CONTROL_AE_STATE_FLASH_REQUIRED
36 import android.hardware.camera2.CameraDevice
37 import android.hardware.camera2.CaptureRequest
38 import android.hardware.camera2.CaptureResult
39 import android.view.Surface
40 import androidx.annotation.VisibleForTesting
41 import androidx.camera.camera2.pipe.CameraGraph
42 import androidx.camera.camera2.pipe.CameraId
43 import androidx.camera.camera2.pipe.FrameInfo
44 import androidx.camera.camera2.pipe.FrameMetadata
45 import androidx.camera.camera2.pipe.FrameNumber
46 import androidx.camera.camera2.pipe.Lock3ABehavior
47 import androidx.camera.camera2.pipe.Metadata
48 import androidx.camera.camera2.pipe.Request
49 import androidx.camera.camera2.pipe.RequestFailure
50 import androidx.camera.camera2.pipe.RequestMetadata
51 import androidx.camera.camera2.pipe.RequestNumber
52 import androidx.camera.camera2.pipe.RequestTemplate
53 import androidx.camera.camera2.pipe.Result3A
54 import androidx.camera.camera2.pipe.StreamId
55 import androidx.camera.camera2.pipe.core.Log.debug
56 import androidx.camera.camera2.pipe.core.Log.info
57 import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter
58 import androidx.camera.camera2.pipe.integration.adapter.CaptureResultAdapter
59 import androidx.camera.camera2.pipe.integration.adapter.future
60 import androidx.camera.camera2.pipe.integration.compat.workaround.UseTorchAsFlash
61 import androidx.camera.camera2.pipe.integration.compat.workaround.isFlashAvailable
62 import androidx.camera.camera2.pipe.integration.compat.workaround.shouldStopRepeatingBeforeCapture
63 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
64 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
65 import androidx.camera.camera2.pipe.integration.impl.CapturePipelineImpl.PipelineTask.MAIN_CAPTURE
66 import androidx.camera.camera2.pipe.integration.impl.CapturePipelineImpl.PipelineTask.POST_CAPTURE
67 import androidx.camera.camera2.pipe.integration.impl.CapturePipelineImpl.PipelineTask.PRE_CAPTURE
68 import androidx.camera.camera2.pipe.integration.impl.TorchControl.TorchMode
69 import androidx.camera.core.ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
70 import androidx.camera.core.ImageCapture.CaptureMode
71 import androidx.camera.core.ImageCapture.ERROR_CAMERA_CLOSED
72 import androidx.camera.core.ImageCapture.ERROR_CAPTURE_FAILED
73 import androidx.camera.core.ImageCapture.FLASH_MODE_AUTO
74 import androidx.camera.core.ImageCapture.FLASH_MODE_OFF
75 import androidx.camera.core.ImageCapture.FLASH_MODE_ON
76 import androidx.camera.core.ImageCapture.FLASH_MODE_SCREEN
77 import androidx.camera.core.ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH
78 import androidx.camera.core.ImageCapture.FlashMode
79 import androidx.camera.core.ImageCapture.FlashType
80 import androidx.camera.core.ImageCaptureException
81 import androidx.camera.core.TorchState
82 import androidx.camera.core.imagecapture.CameraCapturePipeline
83 import androidx.camera.core.impl.CameraCaptureFailure
84 import androidx.camera.core.impl.CameraCaptureResult
85 import androidx.camera.core.impl.CameraCaptureResult.EmptyCameraCaptureResult
86 import androidx.camera.core.impl.CaptureConfig
87 import androidx.camera.core.impl.Config
88 import androidx.camera.core.impl.ConvergenceUtils
89 import androidx.camera.core.impl.SessionProcessor.CaptureCallback
90 import com.google.common.util.concurrent.ListenableFuture
91 import java.util.concurrent.TimeUnit
92 import javax.inject.Inject
93 import kotlin.reflect.KClass
94 import kotlinx.coroutines.CancellationException
95 import kotlinx.coroutines.CompletableDeferred
96 import kotlinx.coroutines.Deferred
97 import kotlinx.coroutines.joinAll
98 import kotlinx.coroutines.launch
99 import kotlinx.coroutines.withTimeoutOrNull
100 
101 private val CHECK_FLASH_REQUIRED_TIMEOUT_IN_NS = TimeUnit.SECONDS.toNanos(1)
102 private val CHECK_3A_TIMEOUT_IN_NS = TimeUnit.SECONDS.toNanos(1)
103 private val CHECK_3A_WITH_FLASH_TIMEOUT_IN_NS = TimeUnit.SECONDS.toNanos(5)
104 private val CHECK_3A_WITH_SCREEN_FLASH_TIMEOUT_IN_NS = TimeUnit.SECONDS.toNanos(2)
105 
106 public interface CapturePipeline {
107 
108     public var template: Int
109 
110     public suspend fun submitStillCaptures(
111         configs: List<CaptureConfig>,
112         requestTemplate: RequestTemplate,
113         sessionConfigOptions: Config,
114         @CaptureMode captureMode: Int,
115         @FlashType flashType: Int,
116         @FlashMode flashMode: Int,
117     ): List<Deferred<Void?>>
118 
119     /** Gets the [CameraCapturePipeline] instance corresponding to a [CapturePipeline] instance. */
120     public suspend fun getCameraCapturePipeline(
121         @CaptureMode captureMode: Int,
122         @FlashMode flashMode: Int,
123         @FlashType flashType: Int
124     ): CameraCapturePipeline
125 }
126 
127 /** Implementations for the single capture. */
128 @UseCaseCameraScope
129 public class CapturePipelineImpl
130 @Inject
131 constructor(
132     private val configAdapter: CaptureConfigAdapter,
133     private val flashControl: FlashControl,
134     private val torchControl: TorchControl,
135     private val videoUsageControl: VideoUsageControl,
136     private val threads: UseCaseThreads,
137     private val requestListener: ComboRequestListener,
138     private val useTorchAsFlash: UseTorchAsFlash,
139     cameraProperties: CameraProperties,
140     private val useCaseCameraState: UseCaseCameraState,
141     useCaseGraphConfig: UseCaseGraphConfig,
142     private val sessionProcessorManager: SessionProcessorManager?,
143 ) : CapturePipeline {
144     private val graph = useCaseGraphConfig.graph
145 
146     // If there is no flash unit, skip the flash related task instead of failing the pipeline.
147     private val hasFlashUnit = cameraProperties.isFlashAvailable()
148 
149     override var template: Int = CameraDevice.TEMPLATE_PREVIEW
150 
151     private enum class PipelineTask {
152         PRE_CAPTURE,
153         MAIN_CAPTURE,
154         POST_CAPTURE,
155     }
156 
157     private data class MainCaptureParams(
158         val configs: List<CaptureConfig>,
159         val requestTemplate: RequestTemplate,
160         val sessionConfigOptions: Config,
161     )
162 
163     /**
164      * Invokes various capture pipelines (e.g. pre-capture or main capture or post-capture).
165      *
166      * @param pipelineTasks List of [PipelineTask] to invoke.
167      * @param captureMode [CaptureMode] integer for the capture.
168      * @param flashMode [FlashMode] integer for the capture.
169      * @param flashType [FlashType] integer for the capture.
170      * @param mainCaptureParams Parameters required for the main capture, must not be null if
171      *   [pipelineTasks] contain [PipelineTask.MAIN_CAPTURE].
172      */
invokeCaptureTasksnull173     private suspend fun invokeCaptureTasks(
174         pipelineTasks: List<PipelineTask>,
175         @CaptureMode captureMode: Int,
176         @FlashMode flashMode: Int,
177         @FlashType flashType: Int,
178         mainCaptureParams: MainCaptureParams?,
179     ): List<Deferred<Void?>> {
180         debug {
181             "CapturePipeline#invokeCaptureTasks: tasks = $pipelineTasks" +
182                 ", captureMode = $captureMode, flashMode = $flashMode, flashType = $flashType"
183         }
184 
185         if (pipelineTasks.contains(MAIN_CAPTURE)) {
186             checkNotNull(mainCaptureParams) { "Must not be null for PipelineType.MAIN_CAPTURE" }
187         }
188 
189         return if (flashMode == FLASH_MODE_SCREEN) {
190             screenFlashCapture(
191                 mainCaptureParams,
192                 captureMode,
193                 pipelineTasks,
194             )
195         } else if (isTorchAsFlash(flashType)) {
196             torchAsFlashCapture(
197                 mainCaptureParams,
198                 captureMode,
199                 flashMode,
200                 pipelineTasks,
201             )
202         } else {
203             defaultCapture(
204                 mainCaptureParams,
205                 captureMode,
206                 flashMode,
207                 pipelineTasks,
208             )
209         }
210     }
211 
submitStillCapturesnull212     override suspend fun submitStillCaptures(
213         configs: List<CaptureConfig>,
214         requestTemplate: RequestTemplate,
215         sessionConfigOptions: Config,
216         @CaptureMode captureMode: Int,
217         @FlashType flashType: Int,
218         @FlashMode flashMode: Int,
219     ): List<Deferred<Void?>> =
220         invokeCaptureTasks(
221             pipelineTasks = listOf(PRE_CAPTURE, MAIN_CAPTURE, POST_CAPTURE),
222             captureMode = captureMode,
223             flashMode = flashMode,
224             flashType = flashType,
225             mainCaptureParams = MainCaptureParams(configs, requestTemplate, sessionConfigOptions),
226         )
227 
228     override suspend fun getCameraCapturePipeline(
229         captureMode: Int,
230         flashMode: Int,
231         flashType: Int
232     ): CameraCapturePipeline {
233         return object : CameraCapturePipeline {
234             override fun invokePreCapture(): ListenableFuture<Void?> {
235                 return threads.scope.future {
236                     invokeCaptureTasks(
237                             pipelineTasks = listOf(PRE_CAPTURE),
238                             captureMode = captureMode,
239                             flashMode = flashMode,
240                             flashType = flashType,
241                             mainCaptureParams = null,
242                         )
243                         .joinAll()
244                     null // Since the joinAll earlier returns Unit type mismatching with Void? type
245                 }
246             }
247 
248             override fun invokePostCapture(): ListenableFuture<Void?> {
249                 return threads.scope.future {
250                     invokeCaptureTasks(
251                             pipelineTasks = listOf(POST_CAPTURE),
252                             captureMode = captureMode,
253                             flashMode = flashMode,
254                             flashType = flashType,
255                             mainCaptureParams = null,
256                         )
257                         .joinAll()
258                     null // Since the joinAll earlier returns Unit type mismatching with Void? type
259                 }
260             }
261         }
262     }
263 
264     /**
265      * Invokes a capture pipeline with the sequence of pre-capture -> main capture -> post-capture
266      * based on the pipeline tasks in the receiver list.
267      *
268      * @param mainCaptureParams Parameters required for the main capture, must not be null if the
269      *   receiver list contains [PipelineTask.MAIN_CAPTURE].
270      * @param preCapture A function invoked during pre-capture.
271      * @param postCapture A function invoked during post-capture.
272      * @receiver A list of [PipelineTask].
273      */
invokenull274     private suspend inline fun List<PipelineTask>.invoke(
275         mainCaptureParams: MainCaptureParams?,
276         crossinline preCapture: suspend () -> Unit,
277         crossinline postCapture: suspend () -> Unit,
278     ): List<Deferred<Void?>> {
279         debug { "CapturePipeline#List<PipelineTask>.invoke: tasks = $this" }
280         if (contains(PRE_CAPTURE)) {
281             preCapture()
282         }
283         return if (contains(MAIN_CAPTURE)) {
284                 submitRequestInternal(
285                     checkNotNull(mainCaptureParams),
286                 )
287             } else {
288                 listOf(CompletableDeferred(value = null))
289             }
290             .also { captureSignal ->
291                 if (contains(POST_CAPTURE)) {
292                     threads.sequentialScope.launch {
293                         debug {
294                             "CapturePipeline#List<PipelineTask>.invoke: Waiting for capture signal"
295                         }
296                         captureSignal.joinAll()
297                         debug {
298                             "CapturePipeline#List<PipelineTask>.invoke:" +
299                                 " Waiting for capture signal done"
300                         }
301                         postCapture()
302                     }
303                 }
304             }
305     }
306 
torchAsFlashCapturenull307     private suspend fun torchAsFlashCapture(
308         mainCaptureParams: MainCaptureParams?,
309         @CaptureMode captureMode: Int,
310         @FlashMode flashMode: Int,
311         pipelineTasks: List<PipelineTask>,
312     ): List<Deferred<Void?>> {
313         debug { "CapturePipeline#torchAsFlashCapture" }
314         return if (hasFlashUnit && isPhysicalFlashRequired(flashMode)) {
315             torchApplyCapture(
316                 mainCaptureParams,
317                 captureMode,
318                 CHECK_3A_WITH_FLASH_TIMEOUT_IN_NS,
319                 pipelineTasks,
320                 // TODO: b/339846763 - Disable AE precap only for the quirks where AE precapture
321                 //  is problematic, instead of all TorchAsFlash quirks.
322                 !useTorchAsFlash.shouldUseTorchAsFlash() && !videoUsageControl.isInVideoUsage(),
323             )
324         } else {
325             defaultNoFlashCapture(mainCaptureParams, captureMode, pipelineTasks)
326         }
327     }
328 
defaultCapturenull329     private suspend fun defaultCapture(
330         mainCaptureParams: MainCaptureParams?,
331         @CaptureMode captureMode: Int,
332         @FlashMode flashMode: Int,
333         pipelineTasks: List<PipelineTask>,
334     ): List<Deferred<Void?>> {
335         return if (hasFlashUnit) {
336             val isFlashRequired = isPhysicalFlashRequired(flashMode)
337             val timeout =
338                 if (isFlashRequired) CHECK_3A_WITH_FLASH_TIMEOUT_IN_NS else CHECK_3A_TIMEOUT_IN_NS
339 
340             if (isFlashRequired || captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY) {
341                 aePreCaptureApplyCapture(
342                     mainCaptureParams,
343                     timeout,
344                     captureMode,
345                     pipelineTasks,
346                 )
347             } else {
348                 defaultNoFlashCapture(
349                     mainCaptureParams,
350                     captureMode,
351                     pipelineTasks,
352                 )
353             }
354         } else {
355             defaultNoFlashCapture(
356                 mainCaptureParams,
357                 captureMode,
358                 pipelineTasks,
359             )
360         }
361     }
362 
defaultNoFlashCapturenull363     private suspend fun defaultNoFlashCapture(
364         mainCaptureParams: MainCaptureParams?,
365         @CaptureMode captureMode: Int,
366         pipelineTasks: List<PipelineTask>,
367     ): List<Deferred<Void?>> {
368         debug { "CapturePipeline#defaultNoFlashCapture" }
369         val lock3ARequired = captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY
370         return pipelineTasks.invoke(
371             mainCaptureParams = mainCaptureParams,
372             preCapture = {
373                 if (lock3ARequired) {
374                     debug { "CapturePipeline#defaultNoFlashCapture: Locking 3A" }
375                     lockAf(CHECK_3A_TIMEOUT_IN_NS, isTorchAsFlash = false)
376                     debug { "CapturePipeline#defaultNoFlashCapture: Locking 3A done" }
377                 }
378             },
379             postCapture = {
380                 if (lock3ARequired) {
381                     debug { "CapturePipeline#defaultNoFlashCapture: Unlocking 3A" }
382                     unlockAf(CHECK_3A_TIMEOUT_IN_NS)
383                     debug { "CapturePipeline#defaultNoFlashCapture: Unlocking 3A done" }
384                 }
385             }
386         )
387     }
388 
torchApplyCapturenull389     private suspend fun torchApplyCapture(
390         mainCaptureParams: MainCaptureParams?,
391         @CaptureMode captureMode: Int,
392         timeLimitNs: Long,
393         pipelineTasks: List<PipelineTask>,
394         triggerAePreCapture: Boolean,
395     ): List<Deferred<Void?>> {
396         debug { "CapturePipeline#torchApplyCapture" }
397         val torchOnRequired = torchControl.torchStateLiveData.value == TorchState.OFF
398         val lock3ARequired = torchOnRequired || captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY
399 
400         return pipelineTasks.invoke(
401             mainCaptureParams = mainCaptureParams,
402             preCapture = {
403                 if (torchOnRequired) {
404                     debug { "CapturePipeline#torchApplyCapture: Setting torch" }
405                     torchControl.setTorchAsync(TorchMode.USED_AS_FLASH).join()
406                     debug { "CapturePipeline#torchApplyCapture: Setting torch done" }
407                 }
408 
409                 if (triggerAePreCapture) {
410                     debug { "CapturePipeline#torchApplyCapture: Locking 3A for capture" }
411                     val result3A =
412                         graph.acquireSession().use {
413                             it.lock3AForCapture(
414                                     timeLimitNs = timeLimitNs,
415                                     triggerAf = captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY,
416                                     waitForAwb = captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY,
417                                 )
418                                 .await()
419                         }
420                     debug {
421                         "CapturePipeline#torchApplyCapture: Locking 3A for capture done" +
422                             ", result3A = $result3A"
423                     }
424                 } else {
425                     // TODO: b/339846763 - When triggerAePreCapture is false, AE pre-capture may
426                     //  cause issues in some devices and thus should not be used here. When capture
427                     //  mode is not max quality, we should only wait for 3A convergence without any
428                     //  additional locking. In case of max quality, only AF should be locked, not
429                     //  AE/AWB too.
430                     if (lock3ARequired) {
431                         if (captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY) {
432                             debug { "CapturePipeline#torchApplyCapture: Locking 3A" }
433                             lockAf(timeLimitNs, isTorchAsFlash = true)
434                             debug { "CapturePipeline#torchApplyCapture: Locking 3A done" }
435                         } else {
436                             debug { "CapturePipeline#torchApplyCapture: Awaiting 3A convergence" }
437                             waitForResult(waitTimeoutNanos = timeLimitNs) {
438                                 ConvergenceUtils.is3AConverged(
439                                     it.metadata.toCameraCaptureResult(),
440                                     /* isTorchAsFlash = */ true
441                                 )
442                             }
443                             debug {
444                                 "CapturePipeline#torchApplyCapture: 3A convergence waiting done"
445                             }
446                         }
447                     }
448                 }
449             },
450             postCapture = {
451                 if (torchOnRequired) {
452                     debug { "CapturePipeline#torchApplyCapture: Unsetting torch" }
453                     @Suppress("DeferredResultUnused") torchControl.setTorchAsync(TorchMode.OFF)
454                     debug { "CapturePipeline#torchApplyCapture: Unsetting torch done" }
455                 }
456                 if (triggerAePreCapture) {
457                     debug { "CapturePipeline#torchApplyCapture: Unlocking 3A for capture" }
458                     @Suppress("DeferredResultUnused")
459                     graph.acquireSession().use {
460                         it.unlock3APostCapture(
461                             cancelAf = captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY,
462                         )
463                     }
464                 } else {
465                     if (lock3ARequired && captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY) {
466                         debug { "CapturePipeline#torchApplyCapture: Unlocking 3A" }
467                         unlockAf(CHECK_3A_TIMEOUT_IN_NS)
468                         debug { "CapturePipeline#torchApplyCapture: Unlocking 3A done" }
469                     }
470                 }
471             }
472         )
473     }
474 
aePreCaptureApplyCapturenull475     private suspend fun aePreCaptureApplyCapture(
476         mainCaptureParams: MainCaptureParams?,
477         timeLimitNs: Long,
478         @CaptureMode captureMode: Int,
479         pipelineTasks: List<PipelineTask>,
480     ): List<Deferred<Void?>> {
481         debug { "CapturePipeline#aePreCaptureApplyCapture" }
482 
483         return pipelineTasks.invoke(
484             mainCaptureParams = mainCaptureParams,
485             preCapture = {
486                 debug {
487                     "CapturePipeline#aePreCaptureApplyCapture: Acquiring session for locking 3A"
488                 }
489                 graph.acquireSession().use {
490                     debug { "CapturePipeline#aePreCaptureApplyCapture: Locking 3A for capture" }
491                     it.lock3AForCapture(
492                             timeLimitNs = timeLimitNs,
493                             triggerAf = captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY,
494                             waitForAwb = captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY,
495                         )
496                         .join()
497                     debug {
498                         "CapturePipeline#aePreCaptureApplyCapture: Locking 3A for capture done"
499                     }
500                 }
501             },
502             postCapture = {
503                 debug {
504                     "CapturePipeline#aePreCaptureApplyCapture: Acquiring session for unlocking 3A"
505                 }
506                 graph.acquireSession().use {
507                     debug { "CapturePipeline#aePreCaptureApplyCapture: Unlocking 3A" }
508                     @Suppress("DeferredResultUnused")
509                     it.unlock3APostCapture(cancelAf = captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY)
510                     debug { "CapturePipeline#aePreCaptureApplyCapture: Unlocking 3A done" }
511                 }
512             }
513         )
514     }
515 
screenFlashCapturenull516     private suspend fun screenFlashCapture(
517         mainCaptureParams: MainCaptureParams?,
518         @CaptureMode captureMode: Int,
519         pipelineTasks: List<PipelineTask>,
520     ): List<Deferred<Void?>> {
521         debug { "CapturePipeline#screenFlashCapture" }
522 
523         return pipelineTasks.invoke(
524             mainCaptureParams = mainCaptureParams,
525             preCapture = { invokeScreenFlashPreCaptureTasks(captureMode) },
526             postCapture = { invokeScreenFlashPostCaptureTasks(captureMode) }
527         )
528     }
529 
530     /**
531      * Invokes the pre-capture tasks required for a screen flash capture.
532      *
533      * This method may modify the preferred AE mode in [State3AControl] to enable external flash AE
534      * mode. [invokeScreenFlashPostCaptureTasks] should be used to restore the previous AE mode in
535      * such case.
536      *
537      * @return The previous preferred AE mode in [State3AControl], null if not modified.
538      */
539     @VisibleForTesting
invokeScreenFlashPreCaptureTasksnull540     public suspend fun invokeScreenFlashPreCaptureTasks(@CaptureMode captureMode: Int) {
541         flashControl.startScreenFlashCaptureTasks()
542 
543         graph.acquireSession().use { session ->
544             // Trigger AE precapture & wait for 3A converge
545             debug { "screenFlashPreCapture: Locking 3A for capture" }
546             val result3A =
547                 session
548                     .lock3AForCapture(
549                         timeLimitNs = CHECK_3A_WITH_SCREEN_FLASH_TIMEOUT_IN_NS,
550                         triggerAf = captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY,
551                         waitForAwb = true,
552                     )
553                     .await()
554             debug { "screenFlashPreCapture: Locking 3A for capture done, result3A = $result3A" }
555         }
556     }
557 
558     @VisibleForTesting
invokeScreenFlashPostCaptureTasksnull559     public suspend fun invokeScreenFlashPostCaptureTasks(@CaptureMode captureMode: Int) {
560         flashControl.stopScreenFlashCaptureTasks()
561 
562         // Unlock 3A
563         debug { "screenFlashPostCapture: Acquiring session for unlocking 3A" }
564         graph.acquireSession().use { session ->
565             debug { "screenFlashPostCapture: Unlocking 3A" }
566             @Suppress("DeferredResultUnused")
567             session.unlock3APostCapture(cancelAf = captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY)
568             debug { "screenFlashPostCapture: Unlocking 3A done" }
569         }
570     }
571 
572     /**
573      * Locks AF by triggering a new AF scan and awaits 3A convergence.
574      *
575      * This function uses [CameraGraph.Session.lock3A] with `aeLockBehavior` and `awbLockBehavior`
576      * set to null to avoid redundant AE/AWB locking. For 3A convergence condition, CameraX custom
577      * condition is used (i.e. [ConvergenceUtils.is3AConverged]).
578      */
lockAfnull579     private suspend fun lockAf(convergedTimeLimitNs: Long, isTorchAsFlash: Boolean): Result3A =
580         graph
581             .acquireSession()
582             .use {
583                 it.lock3A(
584                     aeLockBehavior = null,
585                     afLockBehavior = Lock3ABehavior.AFTER_CURRENT_SCAN,
586                     awbLockBehavior = null,
587                     convergedCondition = getConvergeCondition(isTorchAsFlash),
588                     convergedTimeLimitNs = convergedTimeLimitNs,
589                     lockedTimeLimitNs = CHECK_3A_TIMEOUT_IN_NS
590                 )
591             }
592             .await()
593 
getConvergeConditionnull594     private fun getConvergeCondition(
595         isTorchAsFlash: Boolean
596     ): (frameMetadata: FrameMetadata) -> Boolean = convergeCondition@{ frameMetadata ->
597         ConvergenceUtils.is3AConverged(frameMetadata.toCameraCaptureResult(), isTorchAsFlash)
598     }
599 
FrameMetadatanull600     private fun FrameMetadata.toCameraCaptureResult(): CameraCaptureResult {
601         val frameInfo =
602             object : FrameInfo {
603                 private val frameMetadata = this@toCameraCaptureResult
604                 override val metadata: FrameMetadata = frameMetadata
605 
606                 override fun get(camera: CameraId): FrameMetadata? = frameMetadata
607 
608                 override val camera: CameraId = frameMetadata.camera
609                 override val frameNumber: FrameNumber = frameMetadata.frameNumber
610                 override val requestMetadata: RequestMetadata = emptyRequestMetadata
611 
612                 @Suppress("UNCHECKED_CAST")
613                 override fun <T : Any> unwrapAs(type: KClass<T>): T? = null
614             }
615 
616         return CaptureResultAdapter(
617             emptyRequestMetadata,
618             /** RequestMetadata not to be used here */
619             frameNumber,
620             frameInfo,
621         )
622     }
623 
624     private val emptyRequestMetadata =
625         object : RequestMetadata {
getnull626             override fun <T> get(key: CaptureRequest.Key<T>): T? = null
627 
628             override fun <T> getOrDefault(key: CaptureRequest.Key<T>, default: T): T = default
629 
630             override val template: RequestTemplate = RequestTemplate(0)
631             override val streams: Map<StreamId, Surface> = mapOf()
632             override val repeating: Boolean = true
633             override val request: Request = Request(listOf())
634             override val requestNumber: RequestNumber = RequestNumber(0)
635 
636             override fun <T> get(key: Metadata.Key<T>): T? = null
637 
638             override fun <T> getOrDefault(key: Metadata.Key<T>, default: T): T = default
639 
640             override fun <T : Any> unwrapAs(type: KClass<T>): T? = null
641         }
642 
643     /** Unlocks any active AF lock by triggering an AF cancel. */
644     private suspend fun unlockAf(timeLimitNs: Long): Result3A =
645         graph
646             .acquireSession()
647             .use {
648                 it.unlock3A(
649                     af = true,
650                     timeLimitNs = timeLimitNs,
651                 )
652             }
653             .await()
654 
submitRequestInternalnull655     private fun submitRequestInternal(
656         params: MainCaptureParams,
657     ): List<Deferred<Void?>> {
658         if (sessionProcessorManager != null) {
659             return submitRequestInternalWithSessionProcessor(params.configs)
660         }
661         debug {
662             "CapturePipeline#submitRequestInternal; Submitting ${params.configs} with CameraPipe"
663         }
664         val deferredList = mutableListOf<CompletableDeferred<Void?>>()
665         val requests =
666             params.configs.mapNotNull {
667                 val completeSignal = CompletableDeferred<Void?>().also { deferredList.add(it) }
668                 try {
669                     configAdapter.mapToRequest(
670                         it,
671                         params.requestTemplate,
672                         params.sessionConfigOptions,
673                         listOf(
674                             object : Request.Listener {
675                                 override fun onAborted(request: Request) {
676                                     completeSignal.completeExceptionally(
677                                         ImageCaptureException(
678                                             ERROR_CAMERA_CLOSED,
679                                             "Capture request is cancelled because camera is closed",
680                                             null
681                                         )
682                                     )
683                                 }
684 
685                                 override fun onTotalCaptureResult(
686                                     requestMetadata: RequestMetadata,
687                                     frameNumber: FrameNumber,
688                                     totalCaptureResult: FrameInfo,
689                                 ) {
690                                     completeSignal.complete(null)
691                                 }
692 
693                                 override fun onFailed(
694                                     requestMetadata: RequestMetadata,
695                                     frameNumber: FrameNumber,
696                                     requestFailure: RequestFailure
697                                 ) {
698                                     completeSignal.completeExceptionally(
699                                         ImageCaptureException(
700                                             ERROR_CAPTURE_FAILED,
701                                             "Capture request failed with reason " +
702                                                 requestFailure.reason,
703                                             null
704                                         )
705                                     )
706                                 }
707                             }
708                         )
709                     )
710                 } catch (e: IllegalStateException) {
711                     info(e) {
712                         "CapturePipeline#submitRequestInternal: configAdapter.mapToRequest failed!"
713                     }
714                     completeSignal.completeExceptionally(
715                         ImageCaptureException(
716                             ERROR_CAPTURE_FAILED,
717                             "Capture request failed with reason " + e.message,
718                             e
719                         )
720                     )
721                     null
722                 }
723             }
724 
725         if (requests.isEmpty()) {
726             // requests can be empty due to configAdapter.mapToRequest throwing exception, all the
727             // deferred instances in the list should already be completed exceptionally.
728             return deferredList
729         }
730 
731         threads.sequentialScope.launch {
732             debug {
733                 "CapturePipeline#submitRequestInternal: Acquiring session for submitting requests"
734             }
735             // graph.acquireSession may fail if camera has entered closing stage
736             var cameraGraphSession: CameraGraph.Session? = null
737             try {
738                 cameraGraphSession = graph.acquireSession()
739             } catch (_: CancellationException) {
740                 info {
741                     "CapturePipeline#submitRequestInternal: " +
742                         "CameraGraph.Session could not be acquired, requests may need re-submission"
743                 }
744 
745                 // completing the requests exceptionally so that they are retried with next camera
746                 deferredList.forEach {
747                     it.completeExceptionally(
748                         ImageCaptureException(
749                             ERROR_CAMERA_CLOSED,
750                             "Capture request is cancelled because camera is closed",
751                             null
752                         )
753                     )
754                 }
755             }
756 
757             cameraGraphSession?.use {
758                 val requiresStopRepeating = requests.shouldStopRepeatingBeforeCapture()
759                 if (requiresStopRepeating) {
760                     it.stopRepeating()
761                 }
762 
763                 debug { "CapturePipeline#submitRequestInternal: Submitting $requests" }
764                 it.submit(requests)
765 
766                 if (requiresStopRepeating) {
767                     deferredList.joinAll()
768                     useCaseCameraState.tryStartRepeating()
769                 }
770             }
771         }
772 
773         return deferredList
774     }
775 
submitRequestInternalWithSessionProcessornull776     private fun submitRequestInternalWithSessionProcessor(
777         configs: List<CaptureConfig>
778     ): List<Deferred<Void?>> {
779         debug {
780             "CapturePipeline#submitRequestInternal: Submitting $configs using SessionProcessor"
781         }
782         val deferredList = mutableListOf<CompletableDeferred<Void?>>()
783         val callbacks =
784             configs.map {
785                 val completeSignal = CompletableDeferred<Void?>().also { deferredList.add(it) }
786                 object : CaptureCallback {
787                     private var cameraCaptureResult: CameraCaptureResult? = null
788 
789                     override fun onCaptureStarted(captureSequenceId: Int, timestamp: Long) {
790                         for (captureCallback in it.cameraCaptureCallbacks) {
791                             captureCallback.onCaptureStarted(it.id)
792                         }
793                     }
794 
795                     override fun onCaptureFailed(captureSequenceId: Int) {
796                         completeSignal.completeExceptionally(
797                             ImageCaptureException(
798                                 ERROR_CAPTURE_FAILED,
799                                 "Capture request failed",
800                                 null
801                             )
802                         )
803                         for (captureCallback in it.cameraCaptureCallbacks) {
804                             captureCallback.onCaptureFailed(
805                                 it.id,
806                                 CameraCaptureFailure(CameraCaptureFailure.Reason.ERROR)
807                             )
808                         }
809                     }
810 
811                     override fun onCaptureCompleted(
812                         timestamp: Long,
813                         captureSequenceId: Int,
814                         captureResult: CameraCaptureResult
815                     ) {
816                         cameraCaptureResult = captureResult
817                     }
818 
819                     override fun onCaptureSequenceCompleted(captureSequenceId: Int) {
820                         completeSignal.complete(null)
821                         val captureResult = cameraCaptureResult ?: EmptyCameraCaptureResult()
822                         for (captureCallback in it.cameraCaptureCallbacks) {
823                             captureCallback.onCaptureCompleted(it.id, captureResult)
824                         }
825                     }
826 
827                     override fun onCaptureProcessProgressed(progress: Int) {
828                         for (captureCallback in it.cameraCaptureCallbacks) {
829                             captureCallback.onCaptureProcessProgressed(it.id, progress)
830                         }
831                     }
832 
833                     override fun onCaptureSequenceAborted(captureSequenceId: Int) {
834                         completeSignal.completeExceptionally(
835                             ImageCaptureException(
836                                 ERROR_CAMERA_CLOSED,
837                                 "Capture request is cancelled because camera is closed",
838                                 null
839                             )
840                         )
841                     }
842                 }
843             }
844         sessionProcessorManager!!.submitCaptureConfigs(configs, callbacks)
845         return deferredList
846     }
847 
isPhysicalFlashRequirednull848     private suspend fun isPhysicalFlashRequired(@FlashMode flashMode: Int): Boolean =
849         when (flashMode) {
850             FLASH_MODE_ON -> true
851             FLASH_MODE_AUTO -> {
852                 waitForResult(CHECK_FLASH_REQUIRED_TIMEOUT_IN_NS)
853                     ?.metadata
854                     ?.get(CaptureResult.CONTROL_AE_STATE) == CONTROL_AE_STATE_FLASH_REQUIRED
855             }
856             FLASH_MODE_OFF -> false
857             FLASH_MODE_SCREEN -> false
858             else -> throw AssertionError(flashMode)
859         }
860 
waitForResultnull861     private suspend fun waitForResult(
862         waitTimeoutNanos: Long,
863         checker: (totalCaptureResult: FrameInfo) -> Boolean = { _ -> true }
864     ): FrameInfo? {
865         val resultListener =
listenernull866             ResultListener(waitTimeoutNanos, checker).also { listener ->
867                 requestListener.addListener(listener, threads.sequentialExecutor)
868                 threads.sequentialScope.launch {
869                     listener.result.join()
870                     requestListener.removeListener(listener)
871                 }
872             }
873 
<lambda>null874         return withTimeoutOrNull(TimeUnit.NANOSECONDS.toMillis(waitTimeoutNanos)) {
875                 // ResultListener timeout is checked only when there is a captured frame, so it
876                 // might get stuck indefinitely without withTimeOrNull
877                 resultListener.result.await()
878             }
frameInfonull879             .also { frameInfo ->
880                 if (frameInfo == null) {
881                     requestListener.removeListener(resultListener)
882                 }
883             }
884     }
885 
isTorchAsFlashnull886     private fun isTorchAsFlash(@FlashType flashType: Int): Boolean {
887         return template == CameraDevice.TEMPLATE_RECORD ||
888             flashType == FLASH_TYPE_USE_TORCH_AS_FLASH ||
889             useTorchAsFlash.shouldUseTorchAsFlash()
890     }
891 }
892 
893 /**
894  * A listener receives the result from the repeating request, and sends it to the [checker] to
895  * determine if the [completeSignal] can be completed.
896  *
897  * @param timeLimitNs timeout threshold in Nanos, set 0 for no timeout case.
898  * @param checker the checker to define the condition to complete the [completeSignal]. Return true
899  *   will complete the [completeSignal], otherwise it will continue to receive the results until the
900  *   timeLimitNs is reached.
901  * @constructor
902  */
903 public class ResultListener(
904     private val timeLimitNs: Long,
905     private val checker: (totalCaptureResult: FrameInfo) -> Boolean,
906 ) : Request.Listener {
907 
908     private val completeSignal = CompletableDeferred<FrameInfo?>()
909     public val result: Deferred<FrameInfo?>
910         get() = completeSignal
911 
912     @Volatile private var timestampOfFirstUpdateNs: Long? = null
913 
onTotalCaptureResultnull914     override fun onTotalCaptureResult(
915         requestMetadata: RequestMetadata,
916         frameNumber: FrameNumber,
917         totalCaptureResult: FrameInfo,
918     ) {
919         // Save some compute if the task is already complete or has been canceled.
920         if (completeSignal.isCompleted || completeSignal.isCancelled) {
921             return
922         }
923 
924         val currentTimestampNs: Long? = totalCaptureResult.metadata[CaptureResult.SENSOR_TIMESTAMP]
925 
926         if (currentTimestampNs != null && timestampOfFirstUpdateNs == null) {
927             timestampOfFirstUpdateNs = currentTimestampNs
928         }
929 
930         val timestampOfFirstUpdateNs = timestampOfFirstUpdateNs
931         if (
932             timeLimitNs != 0L &&
933                 timestampOfFirstUpdateNs != null &&
934                 currentTimestampNs != null &&
935                 currentTimestampNs - timestampOfFirstUpdateNs > timeLimitNs
936         ) {
937             completeSignal.complete(null)
938             debug {
939                 "Wait for capture result timeout, current: $currentTimestampNs " +
940                     "first: $timestampOfFirstUpdateNs"
941             }
942             return
943         }
944         if (!checker(totalCaptureResult)) {
945             return
946         }
947 
948         completeSignal.complete(totalCaptureResult)
949     }
950 }
951