1 /*
2  * 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.compat
18 
19 import android.hardware.camera2.CameraCaptureSession.StateCallback
20 import android.hardware.camera2.CameraDevice
21 import android.hardware.camera2.CameraExtensionSession
22 import android.hardware.camera2.CaptureRequest
23 import android.hardware.camera2.TotalCaptureResult
24 import android.hardware.camera2.params.InputConfiguration
25 import android.hardware.camera2.params.OutputConfiguration
26 import android.os.Build
27 import android.view.Surface
28 import androidx.annotation.GuardedBy
29 import androidx.annotation.RequiresApi
30 import androidx.camera.camera2.pipe.AudioRestrictionMode
31 import androidx.camera.camera2.pipe.CameraId
32 import androidx.camera.camera2.pipe.CameraMetadata
33 import androidx.camera.camera2.pipe.RequestTemplate
34 import androidx.camera.camera2.pipe.UnsafeWrapper
35 import androidx.camera.camera2.pipe.core.Debug
36 import androidx.camera.camera2.pipe.core.Log
37 import androidx.camera.camera2.pipe.core.Threads
38 import androidx.camera.camera2.pipe.internal.CameraErrorListener
39 import androidx.camera.camera2.pipe.writeParameter
40 import kotlin.reflect.KClass
41 import kotlinx.atomicfu.atomic
42 
43 /**
44  * Interface around a [CameraDevice] with minor modifications.
45  *
46  * This interface has been modified to correct nullness, adjust exceptions, and to return or produce
47  * wrapper interfaces instead of the native Camera2 types.
48  */
49 internal interface CameraDeviceWrapper : UnsafeWrapper, AudioRestrictionController.Listener {
50     /** @see [CameraDevice.getId] */
51     val cameraId: CameraId
52 
53     /** @see CameraDevice.createCaptureRequest */
createCaptureRequestnull54     fun createCaptureRequest(template: RequestTemplate): CaptureRequest.Builder?
55 
56     /** @see CameraDevice.createReprocessCaptureRequest */
57     @RequiresApi(23)
58     fun createReprocessCaptureRequest(inputResult: TotalCaptureResult): CaptureRequest.Builder?
59 
60     /** @see CameraDevice.createCaptureSession */
61     fun createCaptureSession(
62         outputs: List<Surface>,
63         stateCallback: CameraCaptureSessionWrapper.StateCallback
64     ): Boolean
65 
66     /** @see CameraDevice.createReprocessableCaptureSession */
67     @RequiresApi(23)
68     fun createReprocessableCaptureSession(
69         input: InputConfiguration,
70         outputs: List<Surface>,
71         stateCallback: CameraCaptureSessionWrapper.StateCallback
72     ): Boolean
73 
74     /** @see CameraDevice.createConstrainedHighSpeedCaptureSession */
75     @RequiresApi(23)
76     fun createConstrainedHighSpeedCaptureSession(
77         outputs: List<Surface>,
78         stateCallback: CameraCaptureSessionWrapper.StateCallback
79     ): Boolean
80 
81     /** @see CameraDevice.createCaptureSessionByOutputConfigurations */
82     @RequiresApi(24)
83     fun createCaptureSessionByOutputConfigurations(
84         outputConfigurations: List<OutputConfigurationWrapper>,
85         stateCallback: CameraCaptureSessionWrapper.StateCallback
86     ): Boolean
87 
88     /** @see CameraDevice.createReprocessableCaptureSessionByConfigurations */
89     @RequiresApi(24)
90     fun createReprocessableCaptureSessionByConfigurations(
91         inputConfig: InputConfigData,
92         outputs: List<OutputConfigurationWrapper>,
93         stateCallback: CameraCaptureSessionWrapper.StateCallback
94     ): Boolean
95 
96     /** @see CameraDevice.createCaptureSession */
97     @RequiresApi(28) fun createCaptureSession(config: SessionConfigData): Boolean
98 
99     /** @see CameraDevice.createExtensionSession */
100     @RequiresApi(31) fun createExtensionSession(config: ExtensionSessionConfigData): Boolean
101 
102     /** Invoked when the [CameraDevice] has been closed */
103     fun onDeviceClosed()
104 
105     /** @see CameraDevice.getCameraAudioRestriction */
106     @RequiresApi(30) fun getCameraAudioRestriction(): AudioRestrictionMode
107 }
108 
109 internal fun CameraDevice?.closeWithTrace() {
110     this?.let {
111         Log.info { "Closing Camera ${it.id}" }
112         Debug.instrument("CXCP#CameraDevice-${it.id}#close") { it.close() }
113     }
114 }
115 
116 internal class AndroidCameraDevice(
117     private val cameraMetadata: CameraMetadata,
118     private val cameraDevice: CameraDevice,
119     override val cameraId: CameraId,
120     private val cameraErrorListener: CameraErrorListener,
121     private val interopSessionStateCallback: StateCallback? = null,
122     private val interopExtensionSessionStateCallback: CameraExtensionSession.StateCallback? = null,
123     private val threads: Threads
124 ) : CameraDeviceWrapper {
125     private val closed = atomic(false)
126     private val _lastStateCallback = atomic<SessionStateCallback?>(null)
127 
createCaptureSessionnull128     override fun createCaptureSession(
129         outputs: List<Surface>,
130         stateCallback: CameraCaptureSessionWrapper.StateCallback
131     ): Boolean {
132         val (success, previousStateCallback) = checkAndSetStateCallback(stateCallback)
133         if (!success) return false
134         previousStateCallback?.onSessionDisconnectedWithTrace()
135         val result =
136             instrumentAndCatch("createCaptureSession") {
137                 // This function was deprecated in Android Q, but is required for some
138                 // configurations when running on older versions of the OS.
139                 @Suppress("deprecation")
140                 cameraDevice.createCaptureSession(
141                     outputs,
142                     AndroidCaptureSessionStateCallback(
143                         this,
144                         stateCallback,
145                         previousStateCallback,
146                         cameraErrorListener,
147                         interopSessionStateCallback,
148                         threads.camera2Handler
149                     ),
150                     threads.camera2Handler
151                 )
152             }
153         if (result == null) {
154             // CameraCaptureSession.StateCallback.onConfigureFailed isn't called in certain
155             // situations, such as when the camera is closed, or when it encounters an error. As
156             // such, we need to make sure we finalize the previous session too.
157             Log.warn {
158                 "Failed to create capture session from $cameraDevice. Finalizing previous session"
159             }
160             previousStateCallback?.onSessionFinalized()
161         }
162         return result != null
163     }
164 
165     @RequiresApi(31)
createExtensionSessionnull166     override fun createExtensionSession(config: ExtensionSessionConfigData): Boolean {
167         val stateCallback = config.extensionStateCallback
168         checkNotNull(stateCallback) {
169             "extensionStateCallback must be set to create Extension session"
170         }
171         checkNotNull(config.extensionMode) {
172             "extensionMode must be set to create Extension session"
173         }
174         val (success, previousStateCallback) = checkAndSetStateCallback(stateCallback)
175         if (!success) return false
176         previousStateCallback?.onSessionDisconnectedWithTrace()
177         val result =
178             instrumentAndCatch("createExtensionSession") {
179                 val sessionConfig =
180                     Api31Compat.newExtensionSessionConfiguration(
181                         config.extensionMode,
182                         config.outputConfigurations.map { it.unwrapAs(OutputConfiguration::class) },
183                         config.executor,
184                         AndroidExtensionSessionStateCallback(
185                             this,
186                             stateCallback,
187                             previousStateCallback,
188                             cameraErrorListener,
189                             interopExtensionSessionStateCallback,
190                             config.executor
191                         ),
192                     )
193 
194                 if (
195                     config.postviewOutputConfiguration != null &&
196                         Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
197                 ) {
198                     val postviewOutput =
199                         config.postviewOutputConfiguration.unwrapAs(OutputConfiguration::class)
200                     checkNotNull(postviewOutput) { "Failed to unwrap Postview OutputConfiguration" }
201                     Api34Compat.setPostviewOutputConfiguration(sessionConfig, postviewOutput)
202                 }
203 
204                 Api31Compat.createExtensionCaptureSession(cameraDevice, sessionConfig)
205             }
206         if (result == null) {
207             // CameraCaptureSession.StateCallback.onConfigureFailed isn't called in certain
208             // situations, such as when the camera is closed, or when it encounters an error. As
209             // such, we need to make sure we finalize the previous session too.
210             Log.warn {
211                 "Failed to create extension session from $cameraDevice. Finalizing previous session"
212             }
213             previousStateCallback?.onSessionFinalized()
214         }
215         return result != null
216     }
217 
218     @RequiresApi(23)
createReprocessableCaptureSessionnull219     override fun createReprocessableCaptureSession(
220         input: InputConfiguration,
221         outputs: List<Surface>,
222         stateCallback: CameraCaptureSessionWrapper.StateCallback
223     ): Boolean {
224         val (success, previousStateCallback) = checkAndSetStateCallback(stateCallback)
225         if (!success) return false
226         previousStateCallback?.onSessionDisconnectedWithTrace()
227         val result =
228             instrumentAndCatch("createReprocessableCaptureSession") {
229                 // This function was deprecated in Android Q, but is required for some
230                 // configurations when running on older versions of the OS.
231                 Api23Compat.createReprocessableCaptureSession(
232                     cameraDevice,
233                     input,
234                     outputs,
235                     AndroidCaptureSessionStateCallback(
236                         this,
237                         stateCallback,
238                         previousStateCallback,
239                         cameraErrorListener,
240                         interopSessionStateCallback,
241                         threads.camera2Handler
242                     ),
243                     threads.camera2Handler
244                 )
245             }
246         if (result == null) {
247             // CameraCaptureSession.StateCallback.onConfigureFailed isn't called in certain
248             // situations, such as when the camera is closed, or when it encounters an error. As
249             // such, we need to make sure we finalize the previous session too.
250             Log.warn {
251                 "Failed to create reprocess session from $cameraDevice. Finalizing previous session"
252             }
253             previousStateCallback?.onSessionFinalized()
254         }
255         return result != null
256     }
257 
258     @RequiresApi(23)
createConstrainedHighSpeedCaptureSessionnull259     override fun createConstrainedHighSpeedCaptureSession(
260         outputs: List<Surface>,
261         stateCallback: CameraCaptureSessionWrapper.StateCallback
262     ): Boolean {
263         val (success, previousStateCallback) = checkAndSetStateCallback(stateCallback)
264         if (!success) return false
265         previousStateCallback?.onSessionDisconnectedWithTrace()
266         val result =
267             instrumentAndCatch("createConstrainedHighSpeedCaptureSession") {
268                 // This function was deprecated in Android Q, but is required for some
269                 // configurations
270                 // when running on older versions of the OS.
271                 Api23Compat.createConstrainedHighSpeedCaptureSession(
272                     cameraDevice,
273                     outputs,
274                     AndroidCaptureSessionStateCallback(
275                         this,
276                         stateCallback,
277                         previousStateCallback,
278                         cameraErrorListener,
279                         interopSessionStateCallback,
280                         threads.camera2Handler
281                     ),
282                     threads.camera2Handler
283                 )
284             }
285         if (result == null) {
286             // CameraCaptureSession.StateCallback.onConfigureFailed isn't called in certain
287             // situations, such as when the camera is closed, or when it encounters an error. As
288             // such, we need to make sure we finalize the previous session too.
289             Log.warn {
290                 "Failed to create capture session from $cameraDevice. Finalizing previous session"
291             }
292             previousStateCallback?.onSessionFinalized()
293         }
294         return result != null
295     }
296 
297     @RequiresApi(24)
createCaptureSessionByOutputConfigurationsnull298     override fun createCaptureSessionByOutputConfigurations(
299         outputConfigurations: List<OutputConfigurationWrapper>,
300         stateCallback: CameraCaptureSessionWrapper.StateCallback
301     ): Boolean {
302         val (success, previousStateCallback) = checkAndSetStateCallback(stateCallback)
303         if (!success) return false
304         previousStateCallback?.onSessionDisconnectedWithTrace()
305         val result =
306             instrumentAndCatch("createCaptureSessionByOutputConfigurations") {
307                 // This function was deprecated in Android Q, but is required for some
308                 // configurations
309                 // when running on older versions of the OS.
310                 Api24Compat.createCaptureSessionByOutputConfigurations(
311                     cameraDevice,
312                     outputConfigurations.map { it.unwrapAs(OutputConfiguration::class) },
313                     AndroidCaptureSessionStateCallback(
314                         this,
315                         stateCallback,
316                         previousStateCallback,
317                         cameraErrorListener,
318                         interopSessionStateCallback,
319                         threads.camera2Handler
320                     ),
321                     threads.camera2Handler
322                 )
323             }
324         if (result == null) {
325             // CameraCaptureSession.StateCallback.onConfigureFailed isn't called in certain
326             // situations, such as when the camera is closed, or when it encounters an error. As
327             // such, we need to make sure we finalize the previous session too.
328             Log.warn {
329                 "Failed to create capture session from $cameraDevice. Finalizing previous session"
330             }
331             previousStateCallback?.onSessionFinalized()
332         }
333         return result != null
334     }
335 
336     @RequiresApi(24)
createReprocessableCaptureSessionByConfigurationsnull337     override fun createReprocessableCaptureSessionByConfigurations(
338         inputConfig: InputConfigData,
339         outputs: List<OutputConfigurationWrapper>,
340         stateCallback: CameraCaptureSessionWrapper.StateCallback
341     ): Boolean {
342         val (success, previousStateCallback) = checkAndSetStateCallback(stateCallback)
343         if (!success) return false
344         previousStateCallback?.onSessionDisconnectedWithTrace()
345         val result =
346             instrumentAndCatch("createReprocessableCaptureSessionByConfigurations") {
347                 // This function was deprecated in Android Q, but is required for some
348                 // configurations when running on older versions of the OS.
349                 Api24Compat.createReprocessableCaptureSessionByConfigurations(
350                     cameraDevice,
351                     Api23Compat.newInputConfiguration(
352                         inputConfig.width,
353                         inputConfig.height,
354                         inputConfig.format
355                     ),
356                     outputs.map { it.unwrapAs(OutputConfiguration::class) },
357                     AndroidCaptureSessionStateCallback(
358                         this,
359                         stateCallback,
360                         previousStateCallback,
361                         cameraErrorListener,
362                         interopSessionStateCallback,
363                         threads.camera2Handler
364                     ),
365                     threads.camera2Handler
366                 )
367             }
368         if (result == null) {
369             // CameraCaptureSession.StateCallback.onConfigureFailed isn't called in certain
370             // situations, such as when the camera is closed, or when it encounters an error. As
371             // such, we need to make sure we finalize the previous session too.
372             Log.warn {
373                 "Failed to create reprocess session from $cameraDevice. Finalizing previous session"
374             }
375             previousStateCallback?.onSessionFinalized()
376         }
377         return result != null
378     }
379 
380     @RequiresApi(28)
createCaptureSessionnull381     override fun createCaptureSession(config: SessionConfigData): Boolean {
382         val (success, previousStateCallback) = checkAndSetStateCallback(config.stateCallback)
383         if (!success) return false
384         previousStateCallback?.onSessionDisconnectedWithTrace()
385         val result =
386             instrumentAndCatch("createCaptureSession") {
387                 val sessionConfig =
388                     Api28Compat.newSessionConfiguration(
389                         config.sessionType,
390                         config.outputConfigurations.map { it.unwrapAs(OutputConfiguration::class) },
391                         config.executor,
392                         AndroidCaptureSessionStateCallback(
393                             this,
394                             config.stateCallback,
395                             previousStateCallback,
396                             cameraErrorListener,
397                             interopSessionStateCallback,
398                             threads.camera2Handler
399                         )
400                     )
401 
402                 if (config.inputConfiguration != null) {
403                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
404                         Api28Compat.setInputConfiguration(
405                             sessionConfig,
406                             Api31Compat.newInputConfiguration(
407                                 config.inputConfiguration,
408                                 cameraId.value
409                             )
410                         )
411                     } else {
412                         Api28Compat.setInputConfiguration(
413                             sessionConfig,
414                             Api23Compat.newInputConfiguration(
415                                 config.inputConfiguration.single().width,
416                                 config.inputConfiguration.single().height,
417                                 config.inputConfiguration.single().format
418                             )
419                         )
420                     }
421                 }
422 
423                 val requestBuilder =
424                     Debug.trace("createCaptureRequest") {
425                         cameraDevice.createCaptureRequest(config.sessionTemplateId)
426                     }
427 
428                 // This compares and sets ONLY the session keys for this camera. Setting parameters
429                 // that are not listed in availableSessionKeys can cause an unusual amount of extra
430                 // latency.
431                 val sessionKeyNames = cameraMetadata.sessionKeys.map { it.name }
432 
433                 // Iterate template parameters and CHECK BY NAME, as there have been cases where
434                 // equality checks did not pass.
435                 for ((key, value) in config.sessionParameters) {
436                     if (key !is CaptureRequest.Key<*>) continue
437                     if (sessionKeyNames.contains(key.name)) {
438                         requestBuilder.writeParameter(key, value)
439                     }
440                 }
441                 Api28Compat.setSessionParameters(sessionConfig, requestBuilder.build())
442                 Debug.trace("Api28Compat.createCaptureSession") {
443                     Api28Compat.createCaptureSession(cameraDevice, sessionConfig)
444                 }
445             }
446         if (result == null) {
447             // CameraCaptureSession.StateCallback.onConfigureFailed isn't called in certain
448             // situations, such as when the camera is closed, or when it encounters an error. As
449             // such, we need to make sure we finalize the previous session too.
450             Log.warn {
451                 "Failed to create capture session from $cameraDevice. Finalizing previous session"
452             }
453             previousStateCallback?.onSessionFinalized()
454         }
455         return result != null
456     }
457 
createCaptureRequestnull458     override fun createCaptureRequest(template: RequestTemplate): CaptureRequest.Builder? =
459         instrumentAndCatch("createCaptureRequest") {
460             cameraDevice.createCaptureRequest(template.value)
461         }
462 
463     @RequiresApi(23)
createReprocessCaptureRequestnull464     override fun createReprocessCaptureRequest(
465         inputResult: TotalCaptureResult
466     ): CaptureRequest.Builder? =
467         instrumentAndCatch("createReprocessCaptureRequest") {
468             Api23Compat.createReprocessCaptureRequest(cameraDevice, inputResult)
469         }
470 
471     @RequiresApi(30)
getCameraAudioRestrictionnull472     override fun getCameraAudioRestriction(): AudioRestrictionMode =
473         Debug.trace("getCameraAudioRestriction") {
474             AudioRestrictionMode(Api30Compat.getCameraAudioRestriction(cameraDevice))
475         }
476 
477     @RequiresApi(30)
onCameraAudioRestrictionUpdatednull478     override fun onCameraAudioRestrictionUpdated(mode: AudioRestrictionMode) {
479         Debug.trace("setCameraAudioRestriction") {
480             catchAndReportCameraExceptions(cameraId, cameraErrorListener) {
481                 Api30Compat.setCameraAudioRestriction(cameraDevice, mode.value)
482             }
483         }
484     }
485 
onDeviceClosednull486     override fun onDeviceClosed() {
487         if (closed.compareAndSet(expect = false, update = true)) {
488             val lastStateCallback = _lastStateCallback.getAndSet(null)
489             lastStateCallback?.onSessionFinalized()
490         }
491     }
492 
493     @Suppress("UNCHECKED_CAST")
unwrapAsnull494     override fun <T : Any> unwrapAs(type: KClass<T>): T? =
495         when (type) {
496             CameraDevice::class -> cameraDevice as T
497             else -> null
498         }
499 
toStringnull500     override fun toString(): String = "AndroidCameraDevice(camera=$cameraId)"
501 
502     /** Utility function to trace, measure, and suppress exceptions for expensive method calls. */
503     @Throws(ObjectUnavailableException::class)
504     private inline fun <T> instrumentAndCatch(fnName: String, crossinline block: () -> T) =
505         Debug.instrument("CXCP#$fnName-${cameraId.value}") {
506             catchAndReportCameraExceptions(cameraId, cameraErrorListener, block)
507         }
508 
checkAndSetStateCallbacknull509     private fun checkAndSetStateCallback(
510         stateCallback: SessionStateCallback
511     ): Pair<Boolean, SessionStateCallback?> {
512         if (closed.value) {
513             stateCallback.onSessionFinalized()
514             return Pair(false, null)
515         }
516         return Pair(true, _lastStateCallback.getAndSet(stateCallback))
517     }
518 
onSessionDisconnectedWithTracenull519     private fun SessionStateCallback.onSessionDisconnectedWithTrace() {
520         Debug.trace("${this@AndroidCameraDevice}#onSessionDisconnected") { onSessionDisconnected() }
521     }
522 }
523 
524 /**
525  * VirtualAndroidCameraDevice creates a simple wrapper around a [AndroidCameraDevice], augmenting it
526  * by enabling it to reject further capture session/request calls when it is "disconnected'.
527  */
528 internal class VirtualAndroidCameraDevice(
529     internal val androidCameraDevice: AndroidCameraDevice,
530 ) : CameraDeviceWrapper {
531     private val lock = Any()
532 
533     @GuardedBy("lock") private var disconnected = false
534 
535     override val cameraId: CameraId
536         get() = androidCameraDevice.cameraId
537 
createCaptureSessionnull538     override fun createCaptureSession(
539         outputs: List<Surface>,
540         stateCallback: CameraCaptureSessionWrapper.StateCallback
541     ) =
542         synchronized(lock) {
543             if (disconnected) {
544                 Log.warn { "createCaptureSession failed: Virtual device disconnected" }
545                 stateCallback.onSessionFinalized()
546                 false
547             } else {
548                 androidCameraDevice.createCaptureSession(outputs, stateCallback)
549             }
550         }
551 
552     @RequiresApi(23)
createReprocessableCaptureSessionnull553     override fun createReprocessableCaptureSession(
554         input: InputConfiguration,
555         outputs: List<Surface>,
556         stateCallback: CameraCaptureSessionWrapper.StateCallback
557     ) =
558         synchronized(lock) {
559             if (disconnected) {
560                 Log.warn { "createReprocessableCaptureSession failed: Virtual device disconnected" }
561                 stateCallback.onSessionFinalized()
562                 false
563             } else {
564                 androidCameraDevice.createReprocessableCaptureSession(input, outputs, stateCallback)
565             }
566         }
567 
568     @RequiresApi(23)
createConstrainedHighSpeedCaptureSessionnull569     override fun createConstrainedHighSpeedCaptureSession(
570         outputs: List<Surface>,
571         stateCallback: CameraCaptureSessionWrapper.StateCallback
572     ) =
573         synchronized(lock) {
574             if (disconnected) {
575                 Log.warn {
576                     "createConstrainedHighSpeedCaptureSession failed: Virtual device disconnected"
577                 }
578                 stateCallback.onSessionFinalized()
579                 false
580             } else {
581                 androidCameraDevice.createConstrainedHighSpeedCaptureSession(outputs, stateCallback)
582             }
583         }
584 
585     @RequiresApi(24)
createCaptureSessionByOutputConfigurationsnull586     override fun createCaptureSessionByOutputConfigurations(
587         outputConfigurations: List<OutputConfigurationWrapper>,
588         stateCallback: CameraCaptureSessionWrapper.StateCallback
589     ) =
590         synchronized(lock) {
591             if (disconnected) {
592                 Log.warn {
593                     "createCaptureSessionByOutputConfigurations failed: Virtual device disconnected"
594                 }
595                 stateCallback.onSessionFinalized()
596                 false
597             } else {
598                 androidCameraDevice.createCaptureSessionByOutputConfigurations(
599                     outputConfigurations,
600                     stateCallback
601                 )
602             }
603         }
604 
605     @RequiresApi(24)
createReprocessableCaptureSessionByConfigurationsnull606     override fun createReprocessableCaptureSessionByConfigurations(
607         inputConfig: InputConfigData,
608         outputs: List<OutputConfigurationWrapper>,
609         stateCallback: CameraCaptureSessionWrapper.StateCallback
610     ) =
611         synchronized(lock) {
612             if (disconnected) {
613                 Log.warn {
614                     "createReprocessableCaptureSessionByConfigurations failed: " +
615                         "Virtual device disconnected"
616                 }
617                 stateCallback.onSessionFinalized()
618                 false
619             } else {
620                 androidCameraDevice.createReprocessableCaptureSessionByConfigurations(
621                     inputConfig,
622                     outputs,
623                     stateCallback
624                 )
625             }
626         }
627 
628     @RequiresApi(31)
createExtensionSessionnull629     override fun createExtensionSession(config: ExtensionSessionConfigData) =
630         synchronized(lock) {
631             if (disconnected) {
632                 Log.warn { "createExtensionSession failed: Virtual device disconnected" }
633                 config.extensionStateCallback!!.onSessionFinalized()
634                 false
635             } else {
636                 androidCameraDevice.createExtensionSession(config)
637             }
638         }
639 
640     @RequiresApi(28)
createCaptureSessionnull641     override fun createCaptureSession(config: SessionConfigData) =
642         synchronized(lock) {
643             if (disconnected) {
644                 Log.warn { "createCaptureSession failed: Virtual device disconnected" }
645                 config.stateCallback.onSessionFinalized()
646                 false
647             } else {
648                 androidCameraDevice.createCaptureSession(config)
649             }
650         }
651 
createCaptureRequestnull652     override fun createCaptureRequest(template: RequestTemplate) =
653         synchronized(lock) {
654             if (disconnected) {
655                 Log.warn { "createCaptureRequest failed: Virtual device disconnected" }
656                 null
657             } else {
658                 androidCameraDevice.createCaptureRequest(template)
659             }
660         }
661 
662     @RequiresApi(23)
createReprocessCaptureRequestnull663     override fun createReprocessCaptureRequest(inputResult: TotalCaptureResult) =
664         synchronized(lock) {
665             if (disconnected) {
666                 Log.warn { "createReprocessCaptureRequest failed: Virtual device disconnected" }
667                 null
668             } else {
669                 androidCameraDevice.createReprocessCaptureRequest(inputResult)
670             }
671         }
672 
onDeviceClosednull673     override fun onDeviceClosed() = androidCameraDevice.onDeviceClosed()
674 
675     override fun <T : Any> unwrapAs(type: KClass<T>): T? = androidCameraDevice.unwrapAs(type)
676 
677     internal fun disconnect() = synchronized(lock) { disconnected = true }
678 
679     @RequiresApi(30)
getCameraAudioRestrictionnull680     override fun getCameraAudioRestriction(): AudioRestrictionMode {
681         return androidCameraDevice.getCameraAudioRestriction()
682     }
683 
684     @RequiresApi(30)
onCameraAudioRestrictionUpdatednull685     override fun onCameraAudioRestrictionUpdated(mode: AudioRestrictionMode) {
686         androidCameraDevice.onCameraAudioRestrictionUpdated(mode)
687     }
688 }
689