1 /*
<lambda>null2  * Copyright 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.camera.integration.extensions
18 
19 import android.content.Context
20 import android.graphics.ImageFormat
21 import android.graphics.SurfaceTexture
22 import android.hardware.camera2.CameraCaptureSession
23 import android.hardware.camera2.CameraCharacteristics
24 import android.hardware.camera2.CameraDevice
25 import android.hardware.camera2.CameraManager
26 import android.hardware.camera2.CaptureRequest
27 import android.hardware.camera2.params.OutputConfiguration
28 import android.hardware.camera2.params.SessionConfiguration
29 import android.media.ImageReader
30 import android.util.Rational
31 import android.util.Size
32 import android.view.Surface
33 import androidx.annotation.RequiresApi
34 import androidx.camera.camera2.internal.compat.params.SessionConfigurationCompat
35 import androidx.camera.core.CameraXConfig
36 import androidx.camera.core.impl.CameraInfoInternal
37 import androidx.camera.core.impl.utils.AspectRatioUtil
38 import androidx.camera.core.impl.utils.executor.CameraXExecutors
39 import androidx.camera.core.internal.utils.SizeUtil
40 import androidx.camera.extensions.ExtensionsManager
41 import androidx.camera.extensions.impl.advanced.AdvancedExtenderImpl
42 import androidx.camera.extensions.impl.advanced.Camera2OutputConfigImpl
43 import androidx.camera.extensions.impl.advanced.Camera2SessionConfigImpl
44 import androidx.camera.extensions.impl.advanced.ImageReaderOutputConfigImpl
45 import androidx.camera.extensions.impl.advanced.MultiResolutionImageReaderOutputConfigImpl
46 import androidx.camera.extensions.impl.advanced.OutputSurfaceConfigurationImpl
47 import androidx.camera.extensions.impl.advanced.OutputSurfaceImpl
48 import androidx.camera.extensions.impl.advanced.SurfaceOutputConfigImpl
49 import androidx.camera.extensions.internal.ExtensionVersion
50 import androidx.camera.extensions.internal.ExtensionsUtils
51 import androidx.camera.extensions.internal.Version
52 import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
53 import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.getImageCaptureSupportedResolutions
54 import androidx.camera.integration.extensions.utils.CameraSelectorUtil
55 import androidx.camera.lifecycle.ProcessCameraProvider
56 import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
57 import androidx.test.core.app.ApplicationProvider
58 import androidx.test.filters.SdkSuppress
59 import com.google.common.truth.Truth.assertThat
60 import java.util.concurrent.TimeUnit
61 import kotlinx.coroutines.CompletableDeferred
62 import kotlinx.coroutines.Dispatchers
63 import kotlinx.coroutines.runBlocking
64 import kotlinx.coroutines.withContext
65 import org.junit.Assume
66 import org.junit.Assume.assumeTrue
67 
68 @RequiresApi(28)
69 class AdvancedExtenderValidation(
70     private val cameraXConfig: CameraXConfig,
71     private val cameraId: String,
72     private val extensionMode: Int
73 ) {
74     private val context = ApplicationProvider.getApplicationContext<Context>()
75     private lateinit var cameraProvider: ProcessCameraProvider
76     private lateinit var extensionsManager: ExtensionsManager
77     private lateinit var cameraCharacteristicsMap: Map<String, CameraCharacteristics>
78     private lateinit var advancedImpl: AdvancedExtenderImpl
79 
80     fun setUp(): Unit = runBlocking {
81         ProcessCameraProvider.configureInstance(cameraXConfig)
82         cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
83         extensionsManager =
84             ExtensionsManager.getInstanceAsync(context, cameraProvider)[
85                     10000, TimeUnit.MILLISECONDS]
86         assumeTrue(CameraXExtensionsTestUtil.isAdvancedExtenderImplemented())
87         val baseCameraSelector = CameraSelectorUtil.createCameraSelectorById(cameraId)
88         assumeTrue(extensionsManager.isExtensionAvailable(baseCameraSelector, extensionMode))
89         val extensionCameraSelector =
90             extensionsManager.getExtensionEnabledCameraSelector(baseCameraSelector, extensionMode)
91         val cameraInfo =
92             withContext(Dispatchers.Main) {
93                 cameraProvider
94                     .bindToLifecycle(FakeLifecycleOwner(), extensionCameraSelector)
95                     .cameraInfo
96             }
97         cameraCharacteristicsMap =
98             ExtensionsUtils.getCameraCharacteristicsMap(cameraInfo as CameraInfoInternal)
99         advancedImpl =
100             CameraXExtensionsTestUtil.createAdvancedExtenderImpl(
101                 extensionMode,
102                 cameraId,
103                 cameraInfo
104             )
105     }
106 
107     private val teardownFunctions = mutableListOf<() -> Unit>()
108 
109     // Adding block to be invoked when tearing down. The last added will be invoked the first.
110     private fun addTearDown(teardown: () -> Unit) {
111         synchronized(teardownFunctions) {
112             teardownFunctions.add(0, teardown) // added to the head
113         }
114     }
115 
116     fun tearDown(): Unit = runBlocking {
117         synchronized(teardownFunctions) {
118             for (teardownFunction in teardownFunctions) {
119                 teardownFunction()
120             }
121             teardownFunctions.clear()
122         }
123         withContext(Dispatchers.Main) {
124             extensionsManager.shutdown()[10000, TimeUnit.MILLISECONDS]
125             cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
126         }
127     }
128 
129     // Test
130     fun getSupportedPreviewOutputResolutions_returnValidData() {
131         val map = advancedImpl.getSupportedPreviewOutputResolutions(cameraId)
132 
133         assertThat(map[ImageFormat.PRIVATE]).isNotEmpty()
134     }
135 
136     // Test
137     fun getSupportedCaptureOutputResolutions_returnValidData() {
138         val map = advancedImpl.getSupportedCaptureOutputResolutions(cameraId)
139 
140         assertThat(map[ImageFormat.JPEG]).isNotEmpty()
141         assertThat(map[ImageFormat.YUV_420_888]).isNotEmpty()
142     }
143 
144     // Test
145     fun getAvailableCaptureRequestKeys_existAfter1_3() {
146         assumeTrue(ExtensionVersion.getRuntimeVersion()!! >= Version.VERSION_1_3)
147         advancedImpl.getAvailableCaptureRequestKeys()
148     }
149 
150     // Test
151     fun getAvailableCaptureResultKeys_existAfter1_3() {
152         assumeTrue(ExtensionVersion.getRuntimeVersion()!! >= Version.VERSION_1_3)
153         advancedImpl.getAvailableCaptureResultKeys()
154     }
155 
156     /**
157      * The following 1.4 interface methods are validated by this test.
158      * <ol>
159      * <li>AdvancedExtenderImpl#isPostviewAvailable()
160      * <li>AdvancedExtenderImpl#getSupportedPostviewResolutions()
161      * </ol>
162      */
163     // Test
164     fun validatePostviewSupport_sinceVersion_1_4() {
165         // Runs the test only when the vendor library implementation is 1.4 or above
166         assumeTrue(ExtensionVersion.getRuntimeVersion()!! >= Version.VERSION_1_4)
167 
168         // Runs the test only when postview is available
169         assumeTrue(advancedImpl.isPostviewAvailable)
170 
171         var anyPostViewSupported = false
172 
173         getImageCaptureSupportedResolutions(
174                 advancedImpl,
175                 cameraId,
176                 cameraCharacteristicsMap[cameraId]!!
177             )
178             .forEach { captureSize ->
179                 anyPostViewSupported = true
180                 var captureSizeSupported = false
181                 var yuvFormatSupported = false
182                 advancedImpl.getSupportedPostviewResolutions(captureSize).forEach { entry ->
183                     captureSizeSupported = true
184                     if (entry.key == ImageFormat.YUV_420_888) {
185                         yuvFormatSupported = true
186                     }
187 
188                     entry.value.forEach { postviewSize ->
189                         // The postview size be smaller than or equal to the provided capture size.
190                         assertThat(SizeUtil.getArea(postviewSize))
191                             .isAtMost(SizeUtil.getArea(captureSize))
192                         // The postview size must have the same aspect ratio as the given capture
193                         // size.
194                         assertThat(
195                                 AspectRatioUtil.hasMatchingAspectRatio(
196                                     postviewSize,
197                                     Rational(captureSize.width, captureSize.height)
198                                 )
199                             )
200                             .isTrue()
201                     }
202                 }
203                 // When postview is supported for the capture size, as the javadoc description,
204                 // YUV_420_888 format must be supported.
205                 if (captureSizeSupported) {
206                     assertThat(yuvFormatSupported).isTrue()
207                 }
208             }
209 
210         // At least one postview size must be supported when isPostviewAvailable returns true.
211         assertThat(anyPostViewSupported).isTrue()
212     }
213 
214     // Test
215     fun validateProcessProgressSupport_sinceVersion_1_4() {
216         // Runs the test only when the vendor library implementation is 1.4 or above
217         assumeTrue(ExtensionVersion.getRuntimeVersion()!! >= Version.VERSION_1_4)
218         // Makes sure isCaptureProcessProgressAvailable API can be called without any exception
219         // occurring when the vendor library implementation is version 1.4 or above
220         advancedImpl.isCaptureProcessProgressAvailable
221     }
222 
223     // Test
224     @SdkSuppress(minSdkVersion = 30)
225     fun validateAvailableCharacteristicsKeyValuesSupport_sinceVersion_1_5() {
226         // Runs the test only when the vendor library implementation is 1.5 or above
227         assumeTrue(ExtensionVersion.getRuntimeVersion()!! >= Version.VERSION_1_5)
228         var zoomRatioRangeFound = false
229         var afAvailableModesFound = false
230         advancedImpl.availableCharacteristicsKeyValues.forEach {
231             when (it.first) {
232                 CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE -> zoomRatioRangeFound = true
233                 CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES -> afAvailableModesFound = true
234             }
235         }
236         // Also checks that CONTROL_ZOOM_RATIO_RANGE and CONTROL_AF_AVAILABLE_MODES should be
237         // contained at least.
238         assertThat(zoomRatioRangeFound).isTrue()
239         assertThat(afAvailableModesFound).isTrue()
240     }
241 
242     enum class SizeCategory {
243         MAXIMUM,
244         MEDIAN,
245         MINIMUM
246     }
247 
248     private fun createPreviewOutput(
249         impl: AdvancedExtenderImpl,
250         sizeCategory: SizeCategory
251     ): OutputSurfaceImpl {
252         val previewSizeMap = impl.getSupportedPreviewOutputResolutions(cameraId)
253         assertThat(previewSizeMap[ImageFormat.PRIVATE]).isNotEmpty()
254 
255         val previewSizes = previewSizeMap[ImageFormat.PRIVATE]!!
256         val previewSize = getSizeByClass(previewSizes, sizeCategory)
257         val surfaceTexture = SurfaceTexture(0)
258         surfaceTexture.setDefaultBufferSize(previewSize.width, previewSize.height)
259         val previewSurface = Surface(surfaceTexture)
260         addTearDown { surfaceTexture.release() }
261         return OutputSurface(previewSurface, previewSize, ImageFormat.PRIVATE)
262     }
263 
264     private fun createCaptureOutput(
265         impl: AdvancedExtenderImpl,
266         sizeCategory: SizeCategory
267     ): OutputSurfaceImpl {
268         val captureSizeMap = impl.getSupportedCaptureOutputResolutions(cameraId)
269         assertThat(captureSizeMap[ImageFormat.JPEG]).isNotEmpty()
270 
271         val captureSizes = captureSizeMap[ImageFormat.JPEG]!!
272         var captureSize = getSizeByClass(captureSizes, sizeCategory)
273         val imageReader =
274             ImageReader.newInstance(captureSize.width, captureSize.height, ImageFormat.JPEG, 1)
275         addTearDown { imageReader.close() }
276         return OutputSurface(imageReader.surface, captureSize, ImageFormat.JPEG)
277     }
278 
279     private fun getSizeByClass(sizes: List<Size>, sizeCategory: SizeCategory): Size {
280         val sortedList = sizes.sortedByDescending { it.width * it.height }
281         var size =
282             when (sizeCategory) {
283                 SizeCategory.MAXIMUM -> {
284                     sortedList[0]
285                 }
286                 SizeCategory.MEDIAN -> {
287                     sortedList[sortedList.size / 2]
288                 }
289                 SizeCategory.MINIMUM -> {
290                     sortedList[sortedList.size - 1]
291                 }
292             }
293         return size
294     }
295 
296     private fun createAnalysisOutput(
297         impl: AdvancedExtenderImpl,
298         sizeCategory: SizeCategory
299     ): OutputSurfaceImpl? {
300         val analysisSizes = impl.getSupportedYuvAnalysisResolutions(cameraId) ?: return null
301         assertThat(analysisSizes).isNotEmpty()
302 
303         var analysisSize = getSizeByClass(analysisSizes, sizeCategory)
304         val imageReader =
305             ImageReader.newInstance(
306                 analysisSize.width,
307                 analysisSize.height,
308                 ImageFormat.YUV_420_888,
309                 1
310             )
311         addTearDown { imageReader.close() }
312         return OutputSurface(imageReader.surface, analysisSize, ImageFormat.YUV_420_888)
313     }
314 
315     private fun createPostviewOutput(
316         impl: AdvancedExtenderImpl,
317         captureSize: Size
318     ): OutputSurfaceImpl {
319         val postviewSize =
320             impl.getSupportedPostviewResolutions(captureSize)[ImageFormat.YUV_420_888]!![0]
321 
322         val postviewImageReader =
323             ImageReader.newInstance(
324                 postviewSize.width,
325                 postviewSize.height,
326                 ImageFormat.YUV_420_888,
327                 1
328             )
329         addTearDown { postviewImageReader.close() }
330         return OutputSurface(postviewImageReader.surface, captureSize, ImageFormat.YUV_420_888)
331     }
332 
333     // Test
334     fun initSession_maxSize_canConfigureSession() =
335         initSessionTest(
336             previewOutputSizeCategory = SizeCategory.MAXIMUM,
337             captureOutputSizeCategory = SizeCategory.MAXIMUM
338         )
339 
340     // Test
341     fun initSession_minSize_canConfigureSession() =
342         initSessionTest(
343             previewOutputSizeCategory = SizeCategory.MINIMUM,
344             captureOutputSizeCategory = SizeCategory.MINIMUM
345         )
346 
347     // Test
348     fun initSession_medianSize_canConfigureSession() =
349         initSessionTest(
350             previewOutputSizeCategory = SizeCategory.MEDIAN,
351             captureOutputSizeCategory = SizeCategory.MEDIAN
352         )
353 
354     // Test
355     fun initSessionWithAnalysis_maxSize_canConfigureSession() =
356         initSessionTest(
357             previewOutputSizeCategory = SizeCategory.MAXIMUM,
358             captureOutputSizeCategory = SizeCategory.MAXIMUM,
359             analysisOutputSizeCategory = SizeCategory.MAXIMUM
360         )
361 
362     // Test
363     fun initSessionWithAnalysis_minSize_canConfigureSession() =
364         initSessionTest(
365             previewOutputSizeCategory = SizeCategory.MINIMUM,
366             captureOutputSizeCategory = SizeCategory.MINIMUM,
367             analysisOutputSizeCategory = SizeCategory.MINIMUM
368         )
369 
370     // Test
371     fun initSessionWithAnalysis_medianSize_canConfigureSession() =
372         initSessionTest(
373             previewOutputSizeCategory = SizeCategory.MEDIAN,
374             captureOutputSizeCategory = SizeCategory.MEDIAN,
375             analysisOutputSizeCategory = SizeCategory.MEDIAN
376         )
377 
378     // Test
379     fun initSessionWithOutputSurfaceConfigurationImpl_maxSize_canConfigureSession() {
380         // Runs the test only when the vendor library implementation is 1.4 or above
381         assumeTrue(ExtensionVersion.getRuntimeVersion()!! >= Version.VERSION_1_4)
382         initSessionTest(
383             previewOutputSizeCategory = SizeCategory.MAXIMUM,
384             captureOutputSizeCategory = SizeCategory.MAXIMUM,
385             enablePostview = advancedImpl.isPostviewAvailable,
386             useOutputSurfaceConfigurationImpl = true
387         )
388     }
389 
390     // Test
391     fun validateSessionTypeSupport_sinceVersion_1_4() {
392         // Runs the test only when the vendor library implementation is 1.4 or above
393         assumeTrue(ExtensionVersion.getRuntimeVersion()!! >= Version.VERSION_1_4)
394         val camera2SessionConfigImpl =
395             initSession(
396                 previewOutputSizeCategory = SizeCategory.MAXIMUM,
397                 captureOutputSizeCategory = SizeCategory.MAXIMUM
398             )
399         // getSessionType is allowed to return any OEM customized session type, therefore, we can
400         // only try to invoke this method to make sure that this method correctly exists in the
401         // vendor library implementation.
402         camera2SessionConfigImpl.sessionType
403     }
404 
405     // Test
406     fun validateSessionTypeSupportWithOutputSurfaceConfigurationImpl_sinceVersion_1_4() {
407         // Runs the test only when the vendor library implementation is 1.4 or above
408         assumeTrue(ExtensionVersion.getRuntimeVersion()!! >= Version.VERSION_1_4)
409         val camera2SessionConfigImpl =
410             initSession(
411                 previewOutputSizeCategory = SizeCategory.MAXIMUM,
412                 captureOutputSizeCategory = SizeCategory.MAXIMUM,
413                 enablePostview = advancedImpl.isPostviewAvailable,
414                 useOutputSurfaceConfigurationImpl = true
415             )
416         // getSessionType is allowed to return any OEM customized session type, therefore, we can
417         // only try to invoke this method to make sure that this method correctly exists in the
418         // vendor library implementation.
419         camera2SessionConfigImpl.sessionType
420     }
421 
422     fun initSessionTest(
423         previewOutputSizeCategory: SizeCategory,
424         captureOutputSizeCategory: SizeCategory,
425         analysisOutputSizeCategory: SizeCategory? = null,
426         enablePostview: Boolean = false,
427         useOutputSurfaceConfigurationImpl: Boolean = false
428     ): Unit = runBlocking {
429         val camera2SessionConfigImpl =
430             initSession(
431                 previewOutputSizeCategory,
432                 captureOutputSizeCategory,
433                 analysisOutputSizeCategory,
434                 enablePostview,
435                 useOutputSurfaceConfigurationImpl
436             )
437 
438         verifyCamera2SessionConfig(camera2SessionConfigImpl)
439     }
440 
441     private fun initSession(
442         previewOutputSizeCategory: SizeCategory,
443         captureOutputSizeCategory: SizeCategory,
444         analysisOutputSizeCategory: SizeCategory? = null,
445         enablePostview: Boolean = false,
446         useOutputSurfaceConfigurationImpl: Boolean = false
447     ): Camera2SessionConfigImpl {
448         if (analysisOutputSizeCategory != null) {
449             Assume.assumeFalse(
450                 advancedImpl.getSupportedYuvAnalysisResolutions(cameraId).isNullOrEmpty()
451             )
452         }
453 
454         val sessionProcessor = advancedImpl.createSessionProcessor()
455         val previewOutput = createPreviewOutput(advancedImpl, previewOutputSizeCategory)
456         val captureOutput = createCaptureOutput(advancedImpl, captureOutputSizeCategory)
457         val analysisOutput =
458             analysisOutputSizeCategory?.let {
459                 createAnalysisOutput(advancedImpl, analysisOutputSizeCategory)
460             }
461 
462         addTearDown { sessionProcessor.deInitSession() }
463 
464         return if (!useOutputSurfaceConfigurationImpl) {
465             sessionProcessor.initSession(
466                 cameraId,
467                 cameraCharacteristicsMap,
468                 context,
469                 previewOutput,
470                 captureOutput,
471                 analysisOutput
472             )
473         } else {
474             val postviewOutput =
475                 if (enablePostview) {
476                     createPostviewOutput(advancedImpl, captureOutput.size)
477                 } else {
478                     null
479                 }
480             val outputSurfaceConfigurationImpl =
481                 OutputSurfaceConfigurationImplAdapter(
482                     previewOutput,
483                     captureOutput,
484                     analysisOutput,
485                     postviewOutput
486                 )
487             sessionProcessor.initSession(
488                 cameraId,
489                 cameraCharacteristicsMap,
490                 context,
491                 outputSurfaceConfigurationImpl
492             )
493         }
494     }
495 
496     private class OutputSurface(
497         private val surface: Surface,
498         private val size: Size,
499         private val imageFormat: Int
500     ) : OutputSurfaceImpl {
501         override fun getSurface() = surface
502 
503         override fun getSize() = size
504 
505         override fun getImageFormat() = imageFormat
506     }
507 
508     private fun getOutputConfiguration(
509         outputConfigImpl: Camera2OutputConfigImpl
510     ): OutputConfiguration {
511         var outputConfiguration: OutputConfiguration
512         when (outputConfigImpl) {
513             is SurfaceOutputConfigImpl -> {
514                 val surface = outputConfigImpl.surface
515                 outputConfiguration = OutputConfiguration(outputConfigImpl.surfaceGroupId, surface)
516             }
517             is ImageReaderOutputConfigImpl -> {
518                 val imageReader =
519                     ImageReader.newInstance(
520                         outputConfigImpl.size.width,
521                         outputConfigImpl.size.height,
522                         outputConfigImpl.imageFormat,
523                         outputConfigImpl.maxImages
524                     )
525                 val surface = imageReader.surface
526                 addTearDown { imageReader.close() }
527                 outputConfiguration = OutputConfiguration(outputConfigImpl.surfaceGroupId, surface)
528             }
529             is MultiResolutionImageReaderOutputConfigImpl ->
530                 throw java.lang.UnsupportedOperationException(
531                     "MultiResolutionImageReaderOutputConfigImpl not supported"
532                 )
533             else ->
534                 throw java.lang.UnsupportedOperationException(
535                     "Output configuration type not supported"
536                 )
537         }
538 
539         if (outputConfigImpl.physicalCameraId != null) {
540             outputConfiguration.setPhysicalCameraId(outputConfigImpl.physicalCameraId)
541         }
542 
543         outputConfigImpl.surfaceSharingOutputConfigs?.let {
544             for (surfaceSharingOutputConfig in it) {
545                 val sharingOutputConfiguration = getOutputConfiguration(surfaceSharingOutputConfig)
546                 outputConfiguration.addSurface(sharingOutputConfiguration.surface!!)
547                 outputConfiguration.enableSurfaceSharing()
548             }
549         }
550 
551         return outputConfiguration
552     }
553 
554     private suspend fun openCameraDevice(cameraId: String): CameraDevice {
555         val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
556         val deferred = CompletableDeferred<CameraDevice>()
557         cameraManager.openCamera(
558             cameraId,
559             CameraXExecutors.ioExecutor(),
560             object : CameraDevice.StateCallback() {
561                 override fun onOpened(cameraDevice: CameraDevice) {
562                     deferred.complete(cameraDevice)
563                 }
564 
565                 override fun onDisconnected(cameraDevice: CameraDevice) {
566                     deferred.completeExceptionally(RuntimeException("Camera Disconnected"))
567                 }
568 
569                 override fun onError(cameraDevice: CameraDevice, error: Int) {
570                     deferred.completeExceptionally(
571                         RuntimeException("Camera onError(error=$cameraDevice)")
572                     )
573                 }
574             }
575         )
576         return deferred.await()
577     }
578 
579     private suspend fun openCaptureSession(
580         cameraDevice: CameraDevice,
581         camera2SessionConfig: Camera2SessionConfigImpl
582     ): CameraCaptureSession {
583 
584         val outputConfigurationList = mutableListOf<OutputConfiguration>()
585         for (outputConfig in camera2SessionConfig.outputConfigs) {
586             val outputConfiguration = getOutputConfiguration(outputConfig)
587             outputConfigurationList.add(outputConfiguration)
588         }
589 
590         val sessionDeferred = CompletableDeferred<CameraCaptureSession>()
591         val sessionConfiguration =
592             SessionConfiguration(
593                 SessionConfigurationCompat.SESSION_REGULAR,
594                 outputConfigurationList,
595                 CameraXExecutors.ioExecutor(),
596                 object : CameraCaptureSession.StateCallback() {
597                     override fun onConfigured(session: CameraCaptureSession) {
598                         sessionDeferred.complete(session)
599                     }
600 
601                     override fun onConfigureFailed(session: CameraCaptureSession) {
602                         sessionDeferred.completeExceptionally(RuntimeException("onConfigureFailed"))
603                     }
604 
605                     override fun onReady(session: CameraCaptureSession) {}
606 
607                     override fun onActive(session: CameraCaptureSession) {}
608 
609                     override fun onCaptureQueueEmpty(session: CameraCaptureSession) {}
610                 }
611             )
612 
613         val requestBuilder =
614             cameraDevice.createCaptureRequest(camera2SessionConfig.sessionTemplateId)
615 
616         camera2SessionConfig.sessionParameters.forEach { (key, value) ->
617             @Suppress("UNCHECKED_CAST") requestBuilder.set(key as CaptureRequest.Key<Any>, value)
618         }
619         sessionConfiguration.sessionParameters = requestBuilder.build()
620 
621         cameraDevice.createCaptureSession(sessionConfiguration)
622 
623         return sessionDeferred.await()
624     }
625 
626     private suspend fun verifyCamera2SessionConfig(camera2SessionConfig: Camera2SessionConfigImpl) {
627         val cameraDevice = openCameraDevice(cameraId)
628         assertThat(cameraDevice).isNotNull()
629         addTearDown { cameraDevice.close() }
630         val captureSession = openCaptureSession(cameraDevice, camera2SessionConfig)
631         assertThat(captureSession).isNotNull()
632         addTearDown { captureSession.close() }
633     }
634 
635     private class OutputSurfaceConfigurationImplAdapter(
636         private val previewOutputSurface: OutputSurfaceImpl,
637         private val captureOutputSurface: OutputSurfaceImpl,
638         private val analysisOutputSurface: OutputSurfaceImpl?,
639         private val postviewOutputSurface: OutputSurfaceImpl?
640     ) : OutputSurfaceConfigurationImpl {
641         override fun getPreviewOutputSurface(): OutputSurfaceImpl {
642             return previewOutputSurface
643         }
644 
645         override fun getImageCaptureOutputSurface(): OutputSurfaceImpl {
646             return captureOutputSurface
647         }
648 
649         override fun getImageAnalysisOutputSurface(): OutputSurfaceImpl? {
650             return analysisOutputSurface
651         }
652 
653         override fun getPostviewOutputSurface(): OutputSurfaceImpl? {
654             return postviewOutputSurface
655         }
656     }
657 }
658