1 /*
<lambda>null2  * Copyright 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.camera.camera2.pipe.integration.impl
18 
19 import android.hardware.camera2.CameraDevice
20 import android.hardware.camera2.CaptureRequest
21 import android.hardware.camera2.params.MeteringRectangle
22 import androidx.annotation.AnyThread
23 import androidx.camera.camera2.pipe.AeMode
24 import androidx.camera.camera2.pipe.CameraGraph
25 import androidx.camera.camera2.pipe.CameraGraph.Constants3A.METERING_REGIONS_DEFAULT
26 import androidx.camera.camera2.pipe.Lock3ABehavior
27 import androidx.camera.camera2.pipe.Request
28 import androidx.camera.camera2.pipe.RequestTemplate
29 import androidx.camera.camera2.pipe.Result3A
30 import androidx.camera.camera2.pipe.StreamId
31 import androidx.camera.camera2.pipe.core.Log.debug
32 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
33 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
34 import androidx.camera.core.ImageCapture
35 import androidx.camera.core.ImageCaptureException
36 import androidx.camera.core.impl.CaptureConfig
37 import androidx.camera.core.impl.CaptureConfig.TEMPLATE_TYPE_NONE
38 import androidx.camera.core.impl.Config
39 import androidx.camera.core.impl.MutableTagBundle
40 import androidx.camera.core.impl.SessionConfig
41 import androidx.camera.core.impl.TagBundle
42 import dagger.Binds
43 import dagger.Module
44 import javax.inject.Inject
45 import kotlinx.coroutines.CancellationException
46 import kotlinx.coroutines.CompletableDeferred
47 import kotlinx.coroutines.Deferred
48 
49 internal const val DEFAULT_REQUEST_TEMPLATE = CameraDevice.TEMPLATE_PREVIEW
50 
51 /**
52  * Provides methods to update the configuration and parameters of the camera. It also stores the
53  * repeating request parameters associated with the configured [UseCaseCamera]. When parameters are
54  * updated, it triggers changes in the [UseCaseCameraState].
55  *
56  * Parameters can be stored and managed according to different configuration types. Each type can be
57  * modified or overridden independently without affecting other types.
58  *
59  * This class should be used as the entry point for submitting requests to the [UseCaseCameraScope]
60  * layer. This ensures that thread confinement are properly applied at a single place for the whole
61  * [UseCaseCameraScope] and reduces concurrency issues.
62  */
63 @JvmDefaultWithCompatibility
64 public interface UseCaseCameraRequestControl {
65     /** Defines the types or categories of configuration parameters. */
66     public enum class Type {
67         /** Parameters related to the overall session configuration. */
68         SESSION_CONFIG,
69         /** General, default parameters. */
70         DEFAULT,
71         /** Parameters specifically for interoperability with Camera2. */
72         CAMERA2_CAMERA_CONTROL
73     }
74 
75     // Repeating Request Parameters
76     /**
77      * Asynchronously sets or updates parameters for the repeating capture request.
78      *
79      * New values will overwrite any existing parameters with the same key for the given [type]. If
80      * no [type] is specified, it defaults to [Type.DEFAULT].
81      *
82      * @param type The category of parameters being set (default: [Type.DEFAULT]).
83      * @param values A map of [CaptureRequest.Key] to their new values.
84      * @param optionPriority The priority for resolving conflicts if the same parameter is set
85      *   multiple times.
86      * @return A [Deferred] object representing the asynchronous operation.
87      */
88     @AnyThread
89     public fun setParametersAsync(
90         type: Type = Type.DEFAULT,
91         values: Map<CaptureRequest.Key<*>, Any> = emptyMap(),
92         optionPriority: Config.OptionPriority = defaultOptionPriority,
93     ): Deferred<Unit>
94 
95     /**
96      * Asynchronously updates the repeating request with a new configuration.
97      *
98      * This method replaces any existing configuration, tags, and listeners associated with the
99      * specified [type]. The repeating request is then rebuilt by merging all configurations, tags,
100      * and listeners from all defined types.
101      *
102      * @param type The category of the configuration being updated (e.g., SESSION_CONFIG, DEFAULT).
103      * @param config The new configuration values to apply. If null, the existing configuration for
104      *   this type is cleared.
105      * @param tags Optional tags to append to the repeating request, similar to
106      *   [CaptureRequest.Builder.setTag].
107      * @param streams The specific streams to update. If empty, all previously specified streams are
108      *   updated. The update only proceeds if at least one valid stream is specified.
109      * @param template The [RequestTemplate] to use for the requests. If null, the previously
110      *   specified template is used.
111      * @param listeners Listeners to receive capture results.
112      * @param sessionConfig Optional [SessionConfig] to update if applicable to the configuration
113      *   type.
114      * @return A [Deferred] representing the asynchronous update operation.
115      */
116     @AnyThread
117     public fun setConfigAsync(
118         type: Type,
119         config: Config? = null,
120         tags: Map<String, Any> = emptyMap(),
121         streams: Set<StreamId>? = null,
122         template: RequestTemplate? = null,
123         listeners: Set<Request.Listener> = emptySet(),
124         sessionConfig: SessionConfig? = null,
125     ): Deferred<Unit>
126 
127     // 3A
128     /**
129      * Asynchronously sets the torch (flashlight) to ON state.
130      *
131      * @return A [Deferred] representing the asynchronous operation and its result ([Result3A]).
132      */
133     @AnyThread public fun setTorchOnAsync(): Deferred<Result3A>
134 
135     /**
136      * Asynchronously sets the torch (flashlight) state to OFF state.
137      *
138      * @param aeMode The [AeMode] to set while setting the torch value. See
139      *   [CameraGraph.Session.setTorchOff] for details.
140      * @return A [Deferred] representing the asynchronous operation and its result ([Result3A]).
141      */
142     @AnyThread public fun setTorchOffAsync(aeMode: AeMode): Deferred<Result3A>
143 
144     /**
145      * Asynchronously starts a 3A (Auto Exposure, Auto Focus, Auto White Balance) operation with the
146      * specified regions and locking behaviors.
147      *
148      * @param aeRegions The auto-exposure regions.
149      * @param afRegions The auto-focus regions.
150      * @param awbRegions The auto-white balance regions.
151      * @param aeLockBehavior The behavior for locking auto-exposure.
152      * @param afLockBehavior The behavior for locking auto-focus.
153      * @param awbLockBehavior The behavior for locking auto-white balance.
154      * @param afTriggerStartAeMode The AE mode to use when triggering AF.
155      * @param timeLimitNs The time limit for the 3A operation in nanoseconds. Defaults to
156      *   [CameraGraph.Constants3A.DEFAULT_TIME_LIMIT_NS].
157      * @return A [Deferred] representing the asynchronous operation and its result ([Result3A]).
158      */
159     @AnyThread
160     public fun startFocusAndMeteringAsync(
161         aeRegions: List<MeteringRectangle>? = null,
162         afRegions: List<MeteringRectangle>? = null,
163         awbRegions: List<MeteringRectangle>? = null,
164         aeLockBehavior: Lock3ABehavior? = null,
165         afLockBehavior: Lock3ABehavior? = null,
166         awbLockBehavior: Lock3ABehavior? = null,
167         afTriggerStartAeMode: AeMode? = null,
168         timeLimitNs: Long = CameraGraph.Constants3A.DEFAULT_TIME_LIMIT_NS,
169     ): Deferred<Result3A>
170 
171     /**
172      * Asynchronously cancels any ongoing focus and metering operations.
173      *
174      * @return A [Deferred] representing the asynchronous operation and its result ([Result3A]).
175      */
176     @AnyThread public fun cancelFocusAndMeteringAsync(): Deferred<Result3A>
177 
178     // Capture
179     /**
180      * Asynchronously issues a single capture request.
181      *
182      * @param captureSequence A list of [CaptureConfig] objects defining the capture settings.
183      * @param captureMode The capture mode (from [ImageCapture.CaptureMode]).
184      * @param flashType The flash type (from [ImageCapture.FlashType]).
185      * @param flashMode The flash mode (from [ImageCapture.FlashMode]).
186      * @return A list of [Deferred] objects, one for each capture in the sequence.
187      */
188     @AnyThread
189     public fun issueSingleCaptureAsync(
190         captureSequence: List<CaptureConfig>,
191         @ImageCapture.CaptureMode captureMode: Int,
192         @ImageCapture.FlashType flashType: Int,
193         @ImageCapture.FlashMode flashMode: Int,
194     ): List<Deferred<Void?>>
195 
196     /**
197      * Updates the 3A regions and applies to the repeating request.
198      *
199      * Note that camera-pipe may invalidate the CameraGraph and update the repeating request
200      * parameters for this operations.
201      *
202      * @see [CameraGraph.Session.update3A]
203      */
204     @AnyThread
205     public fun update3aRegions(
206         aeRegions: List<MeteringRectangle>? = null,
207         afRegions: List<MeteringRectangle>? = null,
208         awbRegions: List<MeteringRectangle>? = null,
209     ): Deferred<Result3A>
210 
211     /**
212      * Waits for any ongoing surface setup to be completed and returns a boolean value to indicate
213      * if a successful setup exists.
214      *
215      * @see UseCaseSurfaceManager.awaitSetupCompletion
216      */
217     public suspend fun awaitSurfaceSetup(): Boolean
218 
219     public fun close()
220 }
221 
222 @UseCaseCameraScope
223 public class UseCaseCameraRequestControlImpl
224 @Inject
225 constructor(
226     private val capturePipeline: CapturePipeline,
227     private val state: UseCaseCameraState,
228     private val useCaseGraphConfig: UseCaseGraphConfig,
229     private val useCaseSurfaceManager: UseCaseSurfaceManager,
230     private val threads: UseCaseThreads,
231 ) : UseCaseCameraRequestControl {
232     private val graph = useCaseGraphConfig.graph
233 
234     @Volatile private var closed = false
235 
236     private data class InfoBundle(
237         val options: Camera2ImplConfig.Builder = Camera2ImplConfig.Builder(),
238         val tags: MutableMap<String, Any> = mutableMapOf(),
239         val listeners: MutableSet<Request.Listener> = mutableSetOf(),
240         var template: RequestTemplate? = null,
241     )
242 
243     private val infoBundleMap = mutableMapOf<UseCaseCameraRequestControl.Type, InfoBundle>()
244 
setParametersAsyncnull245     override fun setParametersAsync(
246         type: UseCaseCameraRequestControl.Type,
247         values: Map<CaptureRequest.Key<*>, Any>,
248         optionPriority: Config.OptionPriority,
249     ): Deferred<Unit> =
250         runIfNotClosed {
251             threads.confineDeferred {
252                 debug {
253                     "UseCaseCameraRequestControlImpl#setParametersAsync: [$type] values = $values" +
254                         ", optionPriority = $optionPriority"
255                 }
256                 infoBundleMap
257                     .getOrPut(type) { InfoBundle() }
258                     .options
259                     .addAllCaptureRequestOptionsWithPriority(values, optionPriority)
260                 infoBundleMap.merge().updateCameraStateAsync()
261             }
262         } ?: canceledResult
263 
setConfigAsyncnull264     override fun setConfigAsync(
265         type: UseCaseCameraRequestControl.Type,
266         config: Config?,
267         tags: Map<String, Any>,
268         streams: Set<StreamId>?,
269         template: RequestTemplate?,
270         listeners: Set<Request.Listener>,
271         sessionConfig: SessionConfig?,
272     ): Deferred<Unit> =
273         runIfNotClosed {
274             threads.confineDeferred {
275                 debug {
276                     "UseCaseCameraRequestControlImpl#setConfigAsync:" +
277                         " [$type] config params = ${config?.toParameters()}"
278                 }
279                 infoBundleMap[type] =
280                     InfoBundle(
281                         Camera2ImplConfig.Builder().apply { config?.let { insertAllOptions(it) } },
282                         tags.toMutableMap(),
283                         listeners.toMutableSet(),
284                         template,
285                     )
286                 infoBundleMap
287                     .merge()
288                     .updateCameraStateAsync(
289                         streams = streams,
290                         sessionConfig = sessionConfig,
291                     )
292             }
293         } ?: canceledResult
294 
setTorchOnAsyncnull295     override fun setTorchOnAsync(): Deferred<Result3A> =
296         runIfNotClosed {
297             threads.confineDeferredSuspend {
298                 debug { "UseCaseCameraRequestControlImpl#setTorchOnAsync" }
299                 useGraphSessionOrFailed { it.setTorchOn() }
300             }
301         } ?: submitFailedResult
302 
setTorchOffAsyncnull303     override fun setTorchOffAsync(aeMode: AeMode): Deferred<Result3A> =
304         runIfNotClosed {
305             threads.confineDeferredSuspend {
306                 debug { "UseCaseCameraRequestControlImpl#setTorchOffAsync" }
307                 useGraphSessionOrFailed {
308                     it.setTorchOff(
309                         aeMode = aeMode,
310                     )
311                 }
312             }
313         } ?: submitFailedResult
314 
startFocusAndMeteringAsyncnull315     override fun startFocusAndMeteringAsync(
316         aeRegions: List<MeteringRectangle>?,
317         afRegions: List<MeteringRectangle>?,
318         awbRegions: List<MeteringRectangle>?,
319         aeLockBehavior: Lock3ABehavior?,
320         afLockBehavior: Lock3ABehavior?,
321         awbLockBehavior: Lock3ABehavior?,
322         afTriggerStartAeMode: AeMode?,
323         timeLimitNs: Long,
324     ): Deferred<Result3A> =
325         runIfNotClosed {
326             threads.confineDeferredSuspend {
327                 debug { "UseCaseCameraRequestControlImpl#startFocusAndMeteringAsync" }
328                 useGraphSessionOrFailed {
329                     it.lock3A(
330                         aeRegions = aeRegions,
331                         afRegions = afRegions,
332                         awbRegions = awbRegions,
333                         aeLockBehavior = aeLockBehavior,
334                         afLockBehavior = afLockBehavior,
335                         awbLockBehavior = awbLockBehavior,
336                         afTriggerStartAeMode = afTriggerStartAeMode,
337                         convergedTimeLimitNs = timeLimitNs,
338                         lockedTimeLimitNs = timeLimitNs
339                     )
340                 }
341             }
342         } ?: submitFailedResult
343 
cancelFocusAndMeteringAsyncnull344     override fun cancelFocusAndMeteringAsync(): Deferred<Result3A> =
345         runIfNotClosed {
346             threads.confineDeferredSuspend {
347                 debug { "UseCaseCameraRequestControlImpl#cancelFocusAndMeteringAsync" }
348 
349                 useGraphSessionOrFailed { it.unlock3A(ae = true, af = true, awb = true) }.await()
350 
351                 useGraphSessionOrFailed {
352                     it.update3A(
353                         aeRegions = METERING_REGIONS_DEFAULT.asList(),
354                         afRegions = METERING_REGIONS_DEFAULT.asList(),
355                         awbRegions = METERING_REGIONS_DEFAULT.asList()
356                     )
357                 }
358             }
359         } ?: submitFailedResult
360 
issueSingleCaptureAsyncnull361     override fun issueSingleCaptureAsync(
362         captureSequence: List<CaptureConfig>,
363         @ImageCapture.CaptureMode captureMode: Int,
364         @ImageCapture.FlashType flashType: Int,
365         @ImageCapture.FlashMode flashMode: Int,
366     ): List<Deferred<Void?>> =
367         runIfNotClosed {
368             threads.confineDeferredListSuspend(captureSequence.size) {
369                 debug { "UseCaseCameraRequestControlImpl#issueSingleCaptureAsync" }
370 
371                 if (captureSequence.hasInvalidSurface()) {
372                     failedResults(
373                         captureSequence.size,
374                         "Capture request failed due to invalid surface"
375                     )
376                 }
377 
378                 infoBundleMap.merge().let { infoBundle ->
379                     debug {
380                         "UseCaseCameraRequestControl: Submitting still captures to capture pipeline"
381                     }
382                     capturePipeline.submitStillCaptures(
383                         configs = captureSequence,
384                         requestTemplate = infoBundle.template!!,
385                         sessionConfigOptions = infoBundle.options.build(),
386                         captureMode = captureMode,
387                         flashType = flashType,
388                         flashMode = flashMode,
389                     )
390                 }
391             }
392         }
393             ?: failedResults(
394                 captureSequence.size,
395                 "Capture request is cancelled on closed CameraGraph"
396             )
397 
update3aRegionsnull398     override fun update3aRegions(
399         aeRegions: List<MeteringRectangle>?,
400         afRegions: List<MeteringRectangle>?,
401         awbRegions: List<MeteringRectangle>?
402     ): Deferred<Result3A> =
403         runIfNotClosed {
404             threads.confineDeferredSuspend {
405                 debug { "UseCaseCameraRequestControlImpl#update3aRegions" }
406                 useGraphSessionOrFailed {
407                     it.update3A(
408                         aeRegions = aeRegions ?: METERING_REGIONS_DEFAULT.asList(),
409                         afRegions = afRegions ?: METERING_REGIONS_DEFAULT.asList(),
410                         awbRegions = awbRegions ?: METERING_REGIONS_DEFAULT.asList()
411                     )
412                 }
413             }
414         } ?: submitFailedResult
415 
awaitSurfaceSetupnull416     override suspend fun awaitSurfaceSetup(): Boolean = useCaseSurfaceManager.awaitSetupCompletion()
417 
418     override fun close() {
419         closed = true
420         debug { "UseCaseCameraRequestControl: closed" }
421     }
422 
failedResultsnull423     private fun failedResults(count: Int, message: String): List<Deferred<Void?>> =
424         List(count) {
425             CompletableDeferred<Void>().apply {
426                 completeExceptionally(
427                     ImageCaptureException(ImageCapture.ERROR_CAPTURE_FAILED, message, null)
428                 )
429             }
430         }
431 
hasInvalidSurfacenull432     private fun List<CaptureConfig>.hasInvalidSurface(): Boolean {
433         forEach { captureConfig ->
434             if (captureConfig.surfaces.isEmpty()) {
435                 return true
436             }
437             captureConfig.surfaces.forEach {
438                 if (useCaseGraphConfig.surfaceToStreamMap[it] == null) {
439                     return true
440                 }
441             }
442         }
443         return false
444     }
445 
446     /**
447      * The merge order is the same as the [UseCaseCameraRequestControl.Type] declaration order.
448      *
449      * Option merge: The earlier merged option in [Config.OptionPriority.OPTIONAL] could be
450      * overridden by later merged options. Tag merge: If there is the same tagKey but tagValue is
451      * different, the later merge would override the earlier one. Listener merge: merge the
452      * listeners into a set.
453      */
mergenull454     private fun Map<UseCaseCameraRequestControl.Type, InfoBundle>.merge(): InfoBundle =
455         InfoBundle(template = RequestTemplate(DEFAULT_REQUEST_TEMPLATE)).apply {
456             UseCaseCameraRequestControl.Type.values().forEach { type ->
457                 getOrElse(type) { InfoBundle() }
458                     .let { infoBundleInType ->
459                         options.insertAllOptions(infoBundleInType.options.mutableConfig)
460                         tags.putAll(infoBundleInType.tags)
461                         listeners.addAll(infoBundleInType.listeners)
462                         infoBundleInType.template?.let { template = it }
463                     }
464             }
465         }
466 
toTagBundlenull467     private fun InfoBundle.toTagBundle(): TagBundle =
468         MutableTagBundle.create().also { tagBundle ->
469             tags.forEach { (tagKey, tagValue) -> tagBundle.putTag(tagKey, tagValue) }
470         }
471 
updateCameraStateAsyncnull472     private fun InfoBundle.updateCameraStateAsync(
473         streams: Set<StreamId>? = null,
474         sessionConfig: SessionConfig? = null,
475     ): Deferred<Unit> =
476         runIfNotClosed {
477             capturePipeline.template =
478                 if (template != null && template!!.value != TEMPLATE_TYPE_NONE) {
479                     template!!.value
480                 } else {
481                     DEFAULT_REQUEST_TEMPLATE
482                 }
483 
484             state.updateAsync(
485                 parameters = options.build().toParameters(),
486                 appendParameters = false,
487                 internalParameters = mapOf(CAMERAX_TAG_BUNDLE to toTagBundle()),
488                 appendInternalParameters = false,
489                 streams = streams,
490                 template = template,
491                 listeners = listeners,
492                 sessionConfig = sessionConfig,
493             )
494         } ?: canceledResult
495 
runIfNotClosednull496     private inline fun <R> runIfNotClosed(block: () -> R): R? {
497         return if (!closed) block() else null
498     }
499 
useGraphSessionOrFailednull500     private suspend inline fun useGraphSessionOrFailed(
501         crossinline block: suspend (CameraGraph.Session) -> Deferred<Result3A>
502     ): Deferred<Result3A> =
503         try {
504             graph.acquireSession().use { block(it) }
505         } catch (e: CancellationException) {
<lambda>null506             debug(e) { "Cannot acquire the CameraGraph.Session" }
507             submitFailedResult
508         }
509 
510     @Module
511     public abstract class Bindings {
512         @UseCaseCameraScope
513         @Binds
provideRequestControlsnull514         public abstract fun provideRequestControls(
515             requestControl: UseCaseCameraRequestControlImpl
516         ): UseCaseCameraRequestControl
517     }
518 
519     public companion object {
520         private val submitFailedResult =
521             CompletableDeferred(Result3A(Result3A.Status.SUBMIT_FAILED))
522         private val canceledResult = CompletableDeferred<Unit>().apply { cancel() }
523     }
524 }
525 
toMapnull526 public fun TagBundle.toMap(): Map<String, Any> =
527     mutableMapOf<String, Any>().also {
528         listKeys().forEach { tagKey -> it[tagKey] = getTag(tagKey) as Any }
529     }
530