1 /*
<lambda>null2  * Copyright 2024 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.lifecycle
18 
19 import android.content.Context
20 import android.content.pm.PackageManager.FEATURE_CAMERA_CONCURRENT
21 import android.util.Range
22 import androidx.annotation.GuardedBy
23 import androidx.annotation.MainThread
24 import androidx.annotation.VisibleForTesting
25 import androidx.camera.core.Camera
26 import androidx.camera.core.CameraEffect
27 import androidx.camera.core.CameraFilter
28 import androidx.camera.core.CameraInfo
29 import androidx.camera.core.CameraInfoUnavailableException
30 import androidx.camera.core.CameraSelector
31 import androidx.camera.core.CameraX
32 import androidx.camera.core.CameraXConfig
33 import androidx.camera.core.CompositionSettings
34 import androidx.camera.core.ConcurrentCamera
35 import androidx.camera.core.ConcurrentCamera.SingleCameraConfig
36 import androidx.camera.core.ImageAnalysis
37 import androidx.camera.core.ImageCapture
38 import androidx.camera.core.Preview
39 import androidx.camera.core.UseCase
40 import androidx.camera.core.UseCaseGroup
41 import androidx.camera.core.ViewPort
42 import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_CONCURRENT
43 import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_SINGLE
44 import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_UNSPECIFIED
45 import androidx.camera.core.concurrent.CameraCoordinator.CameraOperatingMode
46 import androidx.camera.core.impl.AdapterCameraInfo
47 import androidx.camera.core.impl.CameraConfig
48 import androidx.camera.core.impl.CameraConfigs
49 import androidx.camera.core.impl.CameraInternal
50 import androidx.camera.core.impl.ExtendedCameraConfigProviderStore
51 import androidx.camera.core.impl.StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED
52 import androidx.camera.core.impl.UseCaseConfig
53 import androidx.camera.core.impl.UseCaseConfigFactory.CaptureType
54 import androidx.camera.core.impl.utils.ContextUtil
55 import androidx.camera.core.impl.utils.Threads
56 import androidx.camera.core.impl.utils.executor.CameraXExecutors
57 import androidx.camera.core.impl.utils.futures.FutureCallback
58 import androidx.camera.core.impl.utils.futures.FutureChain
59 import androidx.camera.core.impl.utils.futures.Futures
60 import androidx.camera.core.internal.CameraUseCaseAdapter
61 import androidx.core.util.Preconditions
62 import androidx.lifecycle.Lifecycle
63 import androidx.lifecycle.LifecycleOwner
64 import androidx.tracing.trace
65 import com.google.common.util.concurrent.ListenableFuture
66 import java.util.Objects
67 import java.util.Objects.requireNonNull
68 
69 /** Implementation of the [LifecycleCameraProvider] interface. */
70 internal class LifecycleCameraProviderImpl : LifecycleCameraProvider {
71     private val lock = Any()
72     @VisibleForTesting
73     @GuardedBy("mLock")
74     internal var cameraXConfigProvider: CameraXConfig.Provider? = null
75     @GuardedBy("mLock") private var cameraXInitializeFuture: ListenableFuture<Void>? = null
76     @GuardedBy("mLock") private var cameraXShutdownFuture = Futures.immediateFuture<Void>(null)
77     private val lifecycleCameraRepository = LifecycleCameraRepository.getInstance()
78     private var cameraX: CameraX? = null
79     @VisibleForTesting internal var context: Context? = null
80     @GuardedBy("mLock")
81     private val cameraInfoMap: MutableMap<CameraUseCaseAdapter.CameraId, AdapterCameraInfo> =
82         HashMap()
83     private val lifecycleCameraKeys = HashSet<LifecycleCameraRepository.Key>()
84     override var configImplType = CameraXConfig.CAMERAX_CONFIG_IMPL_TYPE_UNKNOWN
85 
86     internal fun initAsync(
87         context: Context,
88         cameraXConfig: CameraXConfig? = null
89     ): ListenableFuture<Void> {
90         synchronized(lock) {
91             if (cameraXInitializeFuture != null) {
92                 return cameraXInitializeFuture as ListenableFuture<Void>
93             }
94             cameraXConfig?.let { configure(it) }
95             val cameraX = CameraX(context, cameraXConfigProvider)
96             configImplType = cameraX.configImplType
97 
98             val initFuture =
99                 FutureChain.from(cameraXShutdownFuture)
100                     .transformAsync({ cameraX.initializeFuture }, CameraXExecutors.directExecutor())
101 
102             cameraXInitializeFuture = initFuture
103 
104             Futures.addCallback(
105                 initFuture,
106                 object : FutureCallback<Void?> {
107                     override fun onSuccess(void: Void?) {
108                         this@LifecycleCameraProviderImpl.cameraX = cameraX
109                         this@LifecycleCameraProviderImpl.context =
110                             ContextUtil.getApplicationContext(context)
111                     }
112 
113                     override fun onFailure(t: Throwable) {
114                         shutdownAsync(clearConfigProvider = false)
115                     }
116                 },
117                 CameraXExecutors.directExecutor()
118             )
119 
120             return Futures.nonCancellationPropagating(initFuture)
121         }
122     }
123 
124     /**
125      * Configures the camera provider.
126      *
127      * The camera provider can only be configured once. Trying to configure it multiple times will
128      * throw an [IllegalStateException].
129      *
130      * @param cameraXConfig The CameraX configuration.
131      */
132     internal fun configure(cameraXConfig: CameraXConfig) =
133         trace("CX:configureInstanceInternal") {
134             synchronized(lock) {
135                 Preconditions.checkNotNull(cameraXConfig)
136                 Preconditions.checkState(
137                     cameraXConfigProvider == null,
138                     "CameraX has already been configured. To use a different configuration, " +
139                         "shutdown() must be called."
140                 )
141                 cameraXConfigProvider = CameraXConfig.Provider { cameraXConfig }
142             }
143         }
144 
145     internal fun shutdownAsync(clearConfigProvider: Boolean = true): ListenableFuture<Void> {
146         Threads.runOnMainSync {
147             unbindAll()
148             lifecycleCameraRepository.removeLifecycleCameras(lifecycleCameraKeys)
149         }
150 
151         if (cameraX != null) {
152             cameraX!!.cameraFactory.cameraCoordinator.shutdown()
153         }
154 
155         val shutdownFuture =
156             if (cameraX != null) cameraX!!.shutdown() else Futures.immediateFuture<Void>(null)
157 
158         synchronized(lock) {
159             if (clearConfigProvider) {
160                 cameraXConfigProvider = null
161             }
162             cameraXInitializeFuture = null
163             cameraXShutdownFuture = shutdownFuture
164             cameraInfoMap.clear()
165             lifecycleCameraKeys.clear()
166         }
167         cameraX = null
168         context = null
169         return shutdownFuture
170     }
171 
172     override fun isBound(useCase: UseCase): Boolean {
173         for (lifecycleCamera: LifecycleCamera in lifecycleCameraRepository.lifecycleCameras) {
174             if (lifecycleCamera.isBound(useCase)) {
175                 return true
176             }
177         }
178 
179         return false
180     }
181 
182     @MainThread
183     override fun unbind(vararg useCases: UseCase?): Unit =
184         trace("CX:unbind") {
185             Threads.checkMainThread()
186 
187             if (cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT) {
188                 throw UnsupportedOperationException(
189                     "Unbind UseCase is not supported in concurrent camera mode, call unbindAll() first."
190                 )
191             }
192 
193             lifecycleCameraRepository.unbind(listOf(*useCases), lifecycleCameraKeys)
194         }
195 
196     @MainThread
197     override fun unbindAll(): Unit =
198         trace("CX:unbindAll") {
199             Threads.checkMainThread()
200             cameraOperatingMode = CAMERA_OPERATING_MODE_UNSPECIFIED
201             lifecycleCameraRepository.unbindAll(lifecycleCameraKeys)
202         }
203 
204     @Throws(CameraInfoUnavailableException::class)
205     override fun hasCamera(cameraSelector: CameraSelector): Boolean =
206         trace("CX:hasCamera") {
207             try {
208                 cameraSelector.select(cameraX!!.cameraRepository.cameras)
209             } catch (_: IllegalArgumentException) {
210                 return@trace false
211             }
212 
213             return@trace true
214         }
215 
216     @MainThread
217     override fun bindToLifecycle(
218         lifecycleOwner: LifecycleOwner,
219         cameraSelector: CameraSelector,
220         vararg useCases: UseCase?
221     ): Camera =
222         trace("CX:bindToLifecycle") {
223             if (cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT) {
224                 throw UnsupportedOperationException(
225                     "bindToLifecycle for single camera is not supported in concurrent camera mode, " +
226                         "call unbindAll() first"
227                 )
228             }
229             cameraOperatingMode = CAMERA_OPERATING_MODE_SINGLE
230             val camera =
231                 bindToLifecycle(lifecycleOwner, cameraSelector, useCases = useCases.filterNotNull())
232             return@trace camera
233         }
234 
235     @MainThread
236     override fun bindToLifecycle(
237         lifecycleOwner: LifecycleOwner,
238         cameraSelector: CameraSelector,
239         useCaseGroup: UseCaseGroup
240     ): Camera =
241         trace("CX:bindToLifecycle-UseCaseGroup") {
242             if (cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT) {
243                 throw UnsupportedOperationException(
244                     "bindToLifecycle for single camera is not supported in concurrent camera mode, " +
245                         "call unbindAll() first."
246                 )
247             }
248             cameraOperatingMode = CAMERA_OPERATING_MODE_SINGLE
249             val camera =
250                 bindToLifecycle(
251                     lifecycleOwner,
252                     cameraSelector,
253                     viewPort = useCaseGroup.viewPort,
254                     effects = useCaseGroup.effects,
255                     targetHighSpeedFrameRate = useCaseGroup.targetHighSpeedFrameRate,
256                     useCases = useCaseGroup.useCases
257                 )
258             return@trace camera
259         }
260 
261     @MainThread
262     override fun bindToLifecycle(singleCameraConfigs: List<SingleCameraConfig?>): ConcurrentCamera =
263         trace("CX:bindToLifecycle-Concurrent") {
264             if (singleCameraConfigs.size < 2) {
265                 throw IllegalArgumentException("Concurrent camera needs two camera configs.")
266             }
267 
268             if (singleCameraConfigs.size > 2) {
269                 throw IllegalArgumentException(
270                     "Concurrent camera is only supporting two cameras at maximum."
271                 )
272             }
273 
274             val firstCameraConfig = singleCameraConfigs[0]!!
275             val secondCameraConfig = singleCameraConfigs[1]!!
276 
277             val cameras: MutableList<Camera> = ArrayList()
278             if (
279                 firstCameraConfig.cameraSelector.lensFacing ==
280                     secondCameraConfig.cameraSelector.lensFacing
281             ) {
282                 if (cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT) {
283                     throw UnsupportedOperationException(
284                         "Camera is already running, call unbindAll() before binding more cameras."
285                     )
286                 }
287                 if (
288                     firstCameraConfig.lifecycleOwner != secondCameraConfig.lifecycleOwner ||
289                         firstCameraConfig.useCaseGroup.viewPort !=
290                             secondCameraConfig.useCaseGroup.viewPort ||
291                         firstCameraConfig.useCaseGroup.effects !=
292                             secondCameraConfig.useCaseGroup.effects
293                 ) {
294                     throw IllegalArgumentException(
295                         "Two camera configs need to have the same lifecycle owner, view port and " +
296                             "effects."
297                     )
298                 }
299                 val lifecycleOwner = firstCameraConfig.lifecycleOwner
300                 val cameraSelector = firstCameraConfig.cameraSelector
301                 val viewPort = firstCameraConfig.useCaseGroup.viewPort
302                 val effects = firstCameraConfig.useCaseGroup.effects
303                 val targetHighSpeedFrameRate =
304                     firstCameraConfig.useCaseGroup.targetHighSpeedFrameRate
305                 val useCases: MutableList<UseCase> = ArrayList()
306                 for (config: SingleCameraConfig? in singleCameraConfigs) {
307                     // Connect physical camera id with use case.
308                     for (useCase: UseCase in config!!.useCaseGroup.useCases) {
309                         config.cameraSelector.physicalCameraId?.let {
310                             useCase.setPhysicalCameraId(it)
311                         }
312                     }
313                     useCases.addAll(config.useCaseGroup.useCases)
314                 }
315 
316                 cameraOperatingMode = CAMERA_OPERATING_MODE_SINGLE
317                 val camera =
318                     bindToLifecycle(
319                         lifecycleOwner,
320                         cameraSelector,
321                         viewPort = viewPort,
322                         effects = effects,
323                         targetHighSpeedFrameRate = targetHighSpeedFrameRate,
324                         useCases = useCases
325                     )
326                 cameras.add(camera)
327             } else {
328                 if (!context!!.packageManager.hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) {
329                     throw UnsupportedOperationException(
330                         "Concurrent camera is not supported on the device."
331                     )
332                 }
333 
334                 if (cameraOperatingMode == CAMERA_OPERATING_MODE_SINGLE) {
335                     throw UnsupportedOperationException(
336                         "Camera is already running, call unbindAll() before binding more cameras."
337                     )
338                 }
339 
340                 val cameraInfosToBind: MutableList<CameraInfo> = ArrayList()
341                 val firstCameraInfo: CameraInfo
342                 val secondCameraInfo: CameraInfo
343                 try {
344                     firstCameraInfo = getCameraInfo(firstCameraConfig.cameraSelector)
345                     secondCameraInfo = getCameraInfo(secondCameraConfig.cameraSelector)
346                 } catch (_: IllegalArgumentException) {
347                     throw IllegalArgumentException("Invalid camera selectors in camera configs.")
348                 }
349                 cameraInfosToBind.add(firstCameraInfo)
350                 cameraInfosToBind.add(secondCameraInfo)
351                 if (
352                     activeConcurrentCameraInfos.isNotEmpty() &&
353                         cameraInfosToBind != activeConcurrentCameraInfos
354                 ) {
355                     throw UnsupportedOperationException(
356                         "Cameras are already running, call unbindAll() before binding more cameras."
357                     )
358                 }
359 
360                 cameraOperatingMode = CAMERA_OPERATING_MODE_CONCURRENT
361 
362                 // For dual camera video capture, we are only supporting two use cases:
363                 // Preview + VideoCapture. If ImageCapture support is added, the validation logic
364                 // will be updated accordingly.
365                 var isDualCameraVideoCapture = false
366                 if (
367                     Objects.equals(
368                         firstCameraConfig.useCaseGroup.useCases,
369                         secondCameraConfig.useCaseGroup.useCases
370                     ) && firstCameraConfig.useCaseGroup.useCases.size == 2
371                 ) {
372                     val useCase0 = firstCameraConfig.useCaseGroup.useCases[0]
373                     val useCase1 = firstCameraConfig.useCaseGroup.useCases[1]
374                     isDualCameraVideoCapture =
375                         (isVideoCapture(useCase0) && isPreview(useCase1)) ||
376                             (isPreview(useCase0) && isVideoCapture(useCase1))
377                 }
378 
379                 if (isDualCameraVideoCapture) {
380                     cameras.add(
381                         bindToLifecycle(
382                             firstCameraConfig.lifecycleOwner,
383                             firstCameraConfig.cameraSelector,
384                             secondCameraConfig.cameraSelector,
385                             firstCameraConfig.compositionSettings,
386                             secondCameraConfig.compositionSettings,
387                             firstCameraConfig.useCaseGroup.viewPort,
388                             firstCameraConfig.useCaseGroup.effects,
389                             firstCameraConfig.useCaseGroup.targetHighSpeedFrameRate,
390                             useCases = firstCameraConfig.useCaseGroup.useCases,
391                         )
392                     )
393                 } else {
394                     for (config: SingleCameraConfig? in singleCameraConfigs) {
395                         val camera =
396                             bindToLifecycle(
397                                 config!!.lifecycleOwner,
398                                 config.cameraSelector,
399                                 viewPort = config.useCaseGroup.viewPort,
400                                 effects = config.useCaseGroup.effects,
401                                 targetHighSpeedFrameRate =
402                                     config.useCaseGroup.targetHighSpeedFrameRate,
403                                 useCases = config.useCaseGroup.useCases
404                             )
405                         cameras.add(camera)
406                     }
407                 }
408                 activeConcurrentCameraInfos = cameraInfosToBind
409             }
410             return@trace ConcurrentCamera(cameras)
411         }
412 
413     override val availableCameraInfos: List<CameraInfo>
414         get() =
415             trace("CX:getAvailableCameraInfos") {
416                 val availableCameraInfos: MutableList<CameraInfo> = ArrayList()
417                 val cameras: Set<CameraInternal> = cameraX!!.cameraRepository.cameras
418                 for (camera: CameraInternal in cameras) {
419                     availableCameraInfos.add(camera.cameraInfo)
420                 }
421                 return@trace availableCameraInfos
422             }
423 
424     override val availableConcurrentCameraInfos: List<List<CameraInfo>>
425         get() =
426             trace("CX:getAvailableConcurrentCameraInfos") {
427                 requireNonNull(cameraX)
428                 requireNonNull(cameraX!!.cameraFactory.cameraCoordinator)
429                 val concurrentCameraSelectorLists =
430                     cameraX!!.cameraFactory.cameraCoordinator.concurrentCameraSelectors
431 
432                 val availableConcurrentCameraInfos: MutableList<List<CameraInfo>> = ArrayList()
433                 for (cameraSelectors in concurrentCameraSelectorLists) {
434                     val cameraInfos: MutableList<CameraInfo> = ArrayList()
435                     for (cameraSelector in cameraSelectors) {
436                         var cameraInfo: CameraInfo
437                         try {
438                             cameraInfo = getCameraInfo(cameraSelector)
439                         } catch (_: IllegalArgumentException) {
440                             continue
441                         }
442                         cameraInfos.add(cameraInfo)
443                     }
444                     availableConcurrentCameraInfos.add(cameraInfos)
445                 }
446                 return@trace availableConcurrentCameraInfos
447             }
448 
449     override val isConcurrentCameraModeOn: Boolean
450         /**
451          * Returns whether there is a [ConcurrentCamera] bound.
452          *
453          * @return `true` if there is a [ConcurrentCamera] bound, otherwise `false`.
454          */
455         @MainThread get() = cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT
456 
457     /**
458      * Binds [ViewPort] and a collection of [UseCase] to a [LifecycleOwner].
459      *
460      * The state of the lifecycle will determine when the cameras are open, started, stopped and
461      * closed. When started, the use cases receive camera data.
462      *
463      * Binding to a [LifecycleOwner] in state currently in [Lifecycle.State.STARTED] or greater will
464      * also initialize and start data capture. If the camera was already running this may cause a
465      * new initialization to occur temporarily stopping data from the camera before restarting it.
466      *
467      * Multiple use cases can be bound via adding them all to a single [bindToLifecycle] call, or by
468      * using multiple [bindToLifecycle] calls. Using a single call that includes all the use cases
469      * helps to set up a camera session correctly for all uses cases, such as by allowing
470      * determination of resolutions depending on all the use cases bound being bound. If the use
471      * cases are bound separately, it will find the supported resolution with the priority depending
472      * on the binding sequence. If the use cases are bound with a single call, it will find the
473      * supported resolution with the priority in sequence of [ImageCapture], [Preview] and then
474      * [ImageAnalysis]. The resolutions that can be supported depends on the camera device hardware
475      * level that there are some default guaranteed resolutions listed in
476      * [android.hardware.camera2.CameraDevice.createCaptureSession].
477      *
478      * Currently up to 3 use cases may be bound to a [Lifecycle] at any time. Exceeding capability
479      * of target camera device will throw an IllegalArgumentException.
480      *
481      * A [UseCase] should only be bound to a single lifecycle and camera selector a time. Attempting
482      * to bind a use case to a lifecycle when it is already bound to another lifecycle is an error,
483      * and the use case binding will not change. Attempting to bind the same use case to multiple
484      * camera selectors is also an error and will not change the binding.
485      *
486      * If different use cases are bound to different camera selectors that resolve to distinct
487      * cameras, but the same lifecycle, only one of the cameras will operate at a time. The
488      * non-operating camera will not become active until it is the only camera with use cases bound.
489      *
490      * The [Camera] returned is determined by the given camera selector, plus other internal
491      * requirements, possibly from use case configurations. The camera returned from
492      * [bindToLifecycle] may differ from the camera determined solely by a camera selector. If the
493      * camera selector can't resolve a camera under the requirements, an [IllegalArgumentException]
494      * will be thrown.
495      *
496      * Only [UseCase] bound to latest active [Lifecycle] can keep alive. [UseCase] bound to other
497      * [Lifecycle] will be stopped.
498      *
499      * @param lifecycleOwner The [LifecycleOwner] which controls the lifecycle transitions of the
500      *   use cases.
501      * @param primaryCameraSelector The primary camera selector which determines the camera to use
502      *   for set of use cases.
503      * @param secondaryCameraSelector The secondary camera selector in dual camera case.
504      * @param primaryCompositionSettings The composition settings for the primary camera.
505      * @param secondaryCompositionSettings The composition settings for the secondary camera.
506      * @param viewPort The viewPort which represents the visible camera sensor rect.
507      * @param effects The effects applied to the camera outputs.
508      * @param targetHighSpeedFrameRate The high speed frame rate applied to the camera output.
509      * @param useCases The use cases to bind to a lifecycle.
510      * @return The [Camera] instance which is determined by the camera selector and internal
511      *   requirements.
512      * @throws IllegalStateException If the use case has already been bound to another lifecycle or
513      *   method is not called on main thread.
514      * @throws IllegalArgumentException If the provided camera selector is unable to resolve a
515      *   camera to be used for the given use cases.
516      */
517     @Suppress("unused")
518     private fun bindToLifecycle(
519         lifecycleOwner: LifecycleOwner,
520         primaryCameraSelector: CameraSelector,
521         secondaryCameraSelector: CameraSelector? = null,
522         primaryCompositionSettings: CompositionSettings = CompositionSettings.DEFAULT,
523         secondaryCompositionSettings: CompositionSettings = CompositionSettings.DEFAULT,
524         viewPort: ViewPort? = null,
525         effects: List<CameraEffect?> = emptyList(),
526         targetHighSpeedFrameRate: Range<Int> = FRAME_RATE_RANGE_UNSPECIFIED,
527         useCases: List<UseCase> = emptyList()
528     ): Camera =
529         trace("CX:bindToLifecycle-internal") {
530             Threads.checkMainThread()
531             // TODO(b/153096869): override UseCase's target rotation.
532 
533             // Get the LifecycleCamera if existed.
534             val primaryCameraInternal =
535                 primaryCameraSelector.select(cameraX!!.cameraRepository.cameras)
536             primaryCameraInternal.setPrimary(true)
537             val primaryAdapterCameraInfo = getCameraInfo(primaryCameraSelector) as AdapterCameraInfo
538 
539             var secondaryCameraInternal: CameraInternal? = null
540             var secondaryAdapterCameraInfo: AdapterCameraInfo? = null
541             if (secondaryCameraSelector != null) {
542                 secondaryCameraInternal =
543                     secondaryCameraSelector.select(cameraX!!.cameraRepository.cameras)
544                 secondaryCameraInternal.setPrimary(false)
545                 secondaryAdapterCameraInfo =
546                     getCameraInfo(secondaryCameraSelector) as AdapterCameraInfo
547             }
548 
549             val cameraId =
550                 CameraUseCaseAdapter.generateCameraId(
551                     primaryAdapterCameraInfo,
552                     secondaryAdapterCameraInfo
553                 )
554             var lifecycleCameraToBind =
555                 lifecycleCameraRepository.getLifecycleCamera(lifecycleOwner, cameraId)
556 
557             // Check if there's another camera that has already been bound.
558             val lifecycleCameras = lifecycleCameraRepository.lifecycleCameras
559             useCases.forEach { useCase ->
560                 for (lifecycleCamera: LifecycleCamera in lifecycleCameras) {
561                     if (
562                         lifecycleCamera.isBound(useCase) && lifecycleCamera != lifecycleCameraToBind
563                     ) {
564                         throw IllegalStateException(
565                             String.format(
566                                 "Use case %s already bound to a different lifecycle.",
567                                 useCase
568                             )
569                         )
570                     }
571                 }
572             }
573 
574             // Create the LifecycleCamera if there's no existing one that can be used.
575             if (lifecycleCameraToBind == null) {
576                 lifecycleCameraToBind =
577                     lifecycleCameraRepository.createLifecycleCamera(
578                         lifecycleOwner,
579                         CameraUseCaseAdapter(
580                             primaryCameraInternal,
581                             secondaryCameraInternal,
582                             primaryAdapterCameraInfo,
583                             secondaryAdapterCameraInfo,
584                             primaryCompositionSettings,
585                             secondaryCompositionSettings,
586                             cameraX!!.cameraFactory.cameraCoordinator,
587                             cameraX!!.cameraDeviceSurfaceManager,
588                             cameraX!!.defaultConfigFactory
589                         )
590                     )
591             }
592 
593             if (useCases.isEmpty()) {
594                 return@trace lifecycleCameraToBind!!
595             }
596 
597             lifecycleCameraRepository.bindToLifecycleCamera(
598                 lifecycleCameraToBind!!,
599                 viewPort,
600                 effects,
601                 targetHighSpeedFrameRate,
602                 useCases,
603                 cameraX!!.cameraFactory.cameraCoordinator
604             )
605 
606             lifecycleCameraKeys.add(LifecycleCameraRepository.Key.create(lifecycleOwner, cameraId))
607 
608             return@trace lifecycleCameraToBind
609         }
610 
611     override fun getCameraInfo(cameraSelector: CameraSelector): CameraInfo =
612         trace("CX:getCameraInfo") {
613             val cameraInfoInternal =
614                 cameraSelector.select(cameraX!!.cameraRepository.cameras).cameraInfoInternal
615             val cameraConfig = getCameraConfig(cameraSelector, cameraInfoInternal)
616 
617             val key =
618                 CameraUseCaseAdapter.CameraId.create(
619                     cameraInfoInternal.cameraId,
620                     cameraConfig.compatibilityId
621                 )
622             var adapterCameraInfo: AdapterCameraInfo?
623             synchronized(lock) {
624                 adapterCameraInfo = cameraInfoMap[key]
625                 if (adapterCameraInfo == null) {
626                     adapterCameraInfo = AdapterCameraInfo(cameraInfoInternal, cameraConfig)
627                     cameraInfoMap[key] = adapterCameraInfo!!
628                 }
629             }
630 
631             return@trace adapterCameraInfo!!
632         }
633 
634     private fun isVideoCapture(useCase: UseCase): Boolean {
635         return useCase.currentConfig.containsOption(UseCaseConfig.OPTION_CAPTURE_TYPE) &&
636             useCase.currentConfig.captureType == CaptureType.VIDEO_CAPTURE
637     }
638 
639     private fun isPreview(useCase: UseCase): Boolean {
640         return useCase is Preview
641     }
642 
643     private fun getCameraConfig(
644         cameraSelector: CameraSelector,
645         cameraInfo: CameraInfo
646     ): CameraConfig {
647         var cameraConfig: CameraConfig? = null
648         for (cameraFilter: CameraFilter in cameraSelector.cameraFilterSet) {
649             if (cameraFilter.identifier != CameraFilter.DEFAULT_ID) {
650                 val extendedCameraConfig =
651                     ExtendedCameraConfigProviderStore.getConfigProvider(cameraFilter.identifier)
652                         .getConfig(cameraInfo, (context)!!)
653                 if (extendedCameraConfig == null) { // ignore IDs unrelated to camera configs.
654                     continue
655                 }
656 
657                 // Only allows one camera config now.
658                 if (cameraConfig != null) {
659                     throw IllegalArgumentException(
660                         "Cannot apply multiple extended camera configs at the same time."
661                     )
662                 }
663                 cameraConfig = extendedCameraConfig
664             }
665         }
666 
667         if (cameraConfig == null) {
668             cameraConfig = CameraConfigs.defaultConfig()
669         }
670         return cameraConfig
671     }
672 
673     @get:CameraOperatingMode
674     private var cameraOperatingMode: Int
675         get() {
676             if (cameraX == null) {
677                 return CAMERA_OPERATING_MODE_UNSPECIFIED
678             }
679             return cameraX!!.cameraFactory.cameraCoordinator.cameraOperatingMode
680         }
681         set(cameraOperatingMode) {
682             if (cameraX == null) {
683                 return
684             }
685             cameraX!!.cameraFactory.cameraCoordinator.cameraOperatingMode = cameraOperatingMode
686         }
687 
688     private var activeConcurrentCameraInfos: List<CameraInfo>
689         get() {
690             if (cameraX == null) {
691                 return java.util.ArrayList()
692             }
693             return cameraX!!.cameraFactory.cameraCoordinator.activeConcurrentCameraInfos
694         }
695         set(cameraInfos) {
696             if (cameraX == null) {
697                 return
698             }
699             cameraX!!.cameraFactory.cameraCoordinator.activeConcurrentCameraInfos = cameraInfos
700         }
701 }
702