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.integration.core 18 19 import android.content.Context 20 import android.hardware.camera2.CameraCaptureSession 21 import android.hardware.camera2.CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES 22 import android.hardware.camera2.CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES 23 import android.hardware.camera2.CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL 24 import android.hardware.camera2.CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY 25 import android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF 26 import android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON 27 import android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION 28 import android.hardware.camera2.CaptureRequest 29 import android.hardware.camera2.CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE 30 import android.hardware.camera2.TotalCaptureResult 31 import android.util.Range 32 import androidx.camera.camera2.Camera2Config 33 import androidx.camera.camera2.interop.Camera2Interop 34 import androidx.camera.camera2.pipe.integration.CameraPipeConfig 35 import androidx.camera.camera2.pipe.integration.adapter.awaitUntil 36 import androidx.camera.core.CameraSelector 37 import androidx.camera.core.CameraXConfig 38 import androidx.camera.core.ImageAnalysis 39 import androidx.camera.core.ImageCapture 40 import androidx.camera.core.Preview 41 import androidx.camera.core.UseCase 42 import androidx.camera.core.impl.CameraInfoInternal 43 import androidx.camera.core.impl.UseCaseConfig 44 import androidx.camera.core.impl.utils.executor.CameraXExecutors 45 import androidx.camera.core.internal.compat.quirk.AeFpsRangeQuirk 46 import androidx.camera.integration.core.util.Camera2InteropUtil 47 import androidx.camera.lifecycle.ProcessCameraProvider 48 import androidx.camera.testing.impl.CameraPipeConfigTestRule 49 import androidx.camera.testing.impl.CameraUtil 50 import androidx.camera.testing.impl.SurfaceTextureProvider 51 import androidx.camera.testing.impl.WakelockEmptyActivityRule 52 import androidx.camera.testing.impl.fakes.FakeLifecycleOwner 53 import androidx.camera.video.Recorder 54 import androidx.camera.video.VideoCapture 55 import androidx.test.core.app.ApplicationProvider 56 import androidx.test.filters.LargeTest 57 import androidx.test.filters.SdkSuppress 58 import com.google.common.truth.Truth.assertWithMessage 59 import java.util.concurrent.TimeUnit 60 import kotlinx.coroutines.CompletableDeferred 61 import kotlinx.coroutines.Deferred 62 import kotlinx.coroutines.Dispatchers 63 import kotlinx.coroutines.runBlocking 64 import kotlinx.coroutines.withContext 65 import org.junit.After 66 import org.junit.Assume 67 import org.junit.Assume.assumeFalse 68 import org.junit.Assume.assumeTrue 69 import org.junit.Before 70 import org.junit.Rule 71 import org.junit.Test 72 import org.junit.runner.RunWith 73 import org.junit.runners.Parameterized 74 75 /** 76 * Tests for checking if a capture option is submitted from end-to-end. 77 * 78 * Usually, these tests only check if the corresponding capture request is submitted properly by 79 * checking the [CaptureRequest] with `Camera2Interop` capture callback. This does not mean the 80 * framework will always honor these capture request options, so we don't usually care about the 81 * result. If the [TotalCaptureResult] or response by camera also need to be verified for some 82 * specific cases, we may need to have additional considerations and will probably vary in a 83 * case-to-case basis. 84 * 85 * TODO: Will probably be better to use CameraController whenever possible to increase the scope. 86 */ 87 @LargeTest 88 @RunWith(Parameterized::class) 89 @SdkSuppress(minSdkVersion = 21) 90 class CaptureOptionSubmissionTest( 91 private val selectorName: String, 92 private val cameraSelector: CameraSelector, 93 private val implName: String, 94 private val cameraConfig: CameraXConfig 95 ) { 96 @get:Rule 97 val cameraPipeConfigTestRule = 98 CameraPipeConfigTestRule( 99 active = implName == CameraPipeConfig::class.simpleName, 100 ) 101 102 @get:Rule 103 val cameraRule = 104 CameraUtil.grantCameraPermissionAndPreTestAndPostTest( 105 CameraUtil.PreTestCameraIdList(cameraConfig) 106 ) 107 108 @get:Rule val wakelockEmptyActivityRule = WakelockEmptyActivityRule() 109 110 private val context = ApplicationProvider.getApplicationContext<Context>() 111 private lateinit var cameraProvider: ProcessCameraProvider 112 private lateinit var fakeLifecycleOwner: FakeLifecycleOwner 113 114 // Capture callback added to session, so only a repeating capture callback, not non-repeating 115 private lateinit var sessionCaptureCallback: CaptureCallback 116 117 @Before 118 fun setUp(): Unit = runBlocking { 119 assumeTrue(CameraUtil.hasCameraWithLensFacing(cameraSelector.lensFacing!!)) 120 121 ProcessCameraProvider.configureInstance(cameraConfig) 122 cameraProvider = ProcessCameraProvider.getInstance(context)[10, TimeUnit.SECONDS] 123 sessionCaptureCallback = CaptureCallback() 124 125 withContext(Dispatchers.Main) { 126 fakeLifecycleOwner = FakeLifecycleOwner() 127 fakeLifecycleOwner.startAndResume() 128 } 129 } 130 131 @After 132 fun tearDown(): Unit = runBlocking { 133 if (::cameraProvider.isInitialized) { 134 withContext(Dispatchers.Main) { cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS] } 135 } 136 } 137 138 /* 139 * Only testing if a supported FPS range is submitted to camera in [CaptureRequest] without 140 * caring about the result. This test basically checks if [Preview.Builder.setTargetFrameRate] 141 * works properly. 142 */ 143 144 @Test 145 fun canSubmitSupportedAeTargetFpsRanges_whenTargetFrameRateSetToPreviewOnly() = runBlocking { 146 // At least 2 FPS ranges should be checked as the submitted range may just be from template 147 getSupportedFpsRanges().forEach { targetFpsRange -> 148 if (targetFpsRange.upper > 30) { 149 // TODO: b/332464740 - High FPS may not be supported as per stream config map 150 return@forEach 151 } 152 153 var lastSubmittedFpsRange: Range<Int>? = null 154 val result = 155 sessionCaptureCallback.verify { captureRequest, _ -> 156 captureRequest[CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE]?.let { 157 lastSubmittedFpsRange = it 158 } 159 captureRequest[CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE] == targetFpsRange 160 } 161 162 bindUseCases(listOf(Preview.Builder().setTargetFrameRate(targetFpsRange))) 163 164 val isCompleted = result.awaitUntil(timeoutMillis = 10000) 165 assertWithMessage( 166 "Test failed for targetFpsRange = $targetFpsRange" + 167 ", lastSubmittedFpsRange = $lastSubmittedFpsRange" 168 ) 169 .that(isCompleted) 170 .isTrue() 171 172 unbindAllUseCases() 173 } 174 } 175 176 @Test 177 fun canSubmitSupportedAeTargetFpsRanges_whenTargetFrameRateSetToVideoCaptureOnly() = 178 runBlocking { 179 // At least 2 FPS ranges should be checked as the submitted range may be from template 180 getSupportedFpsRanges().forEach { targetFpsRange -> 181 if (targetFpsRange.upper > 30) { 182 // TODO: b/332464740 - High FPS may not be supported as per stream config map 183 return@forEach 184 } 185 186 var lastSubmittedFpsRange: Range<Int>? = null 187 val result = 188 sessionCaptureCallback.verify { captureRequest, _ -> 189 captureRequest[CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE]?.let { 190 lastSubmittedFpsRange = it 191 } 192 captureRequest[CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE] == targetFpsRange 193 } 194 195 bindUseCases( 196 listOf( 197 // Binds Preview together to ensure that a repeating will be started 198 Preview.Builder(), 199 VideoCapture.Builder(Recorder.Builder().build()) 200 .setTargetFrameRate(targetFpsRange), 201 ) 202 ) 203 204 val isCompleted = result.awaitUntil(timeoutMillis = 10000) 205 assertWithMessage( 206 "Test failed for targetFpsRange = $targetFpsRange" + 207 ", lastSubmittedFpsRange = $lastSubmittedFpsRange" 208 ) 209 .that(isCompleted) 210 .isTrue() 211 212 unbindAllUseCases() 213 } 214 } 215 216 @Test 217 fun canApplyAeFpsRangeWorkaround() = runBlocking { 218 val targetFpsRange = getAeFpsRangeFromQuirks() 219 assumeFalse( 220 "AeFpsRange workaround is applied only on LEGACY level devices.", 221 targetFpsRange == null 222 ) 223 224 var lastSubmittedFpsRange: Range<Int>? = null 225 val result = 226 sessionCaptureCallback.verify { captureRequest, _ -> 227 captureRequest[CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE]?.let { 228 lastSubmittedFpsRange = it 229 } 230 captureRequest[CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE] == targetFpsRange 231 } 232 233 bindUseCases(listOf(Preview.Builder())) 234 235 val isCompleted = result.awaitUntil(timeoutMillis = 10000) 236 assertWithMessage( 237 "Test failed for targetFpsRange = $targetFpsRange" + 238 ", lastSubmittedFpsRange = $lastSubmittedFpsRange" 239 ) 240 .that(isCompleted) 241 .isTrue() 242 } 243 244 private fun getAeFpsRangeFromQuirks(): Range<Int>? = runBlocking { 245 val camera = 246 withContext(Dispatchers.Main) { 247 cameraProvider.bindToLifecycle(fakeLifecycleOwner, cameraSelector) 248 } 249 250 val quirks = (camera.cameraInfo as CameraInfoInternal).cameraQuirks 251 quirks.getAll(AeFpsRangeQuirk::class.java).firstOrNull()?.targetAeFpsRange 252 } 253 254 // TODO: b/332464991 - Add a FPS test adding different FPS ranges to Preview & VideoCapture 255 256 @Test 257 fun canSetAeTargetFpsRangeWithCamera2Interop() = runBlocking { 258 // At least 2 FPS ranges should be checked as the submitted range may just be from template 259 getSupportedFpsRanges().forEach { targetFpsRange -> 260 if (targetFpsRange.upper > 30) { 261 // TODO: b/332464740 - High FPS may not be supported as per stream config map 262 return@forEach 263 } 264 265 var lastSubmittedFpsRange: Range<Int>? = null 266 val result = 267 sessionCaptureCallback.verify { captureRequest, _ -> 268 captureRequest[CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE]?.let { 269 lastSubmittedFpsRange = it 270 } 271 captureRequest[CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE] == targetFpsRange 272 } 273 274 bindUseCases( 275 listOf( 276 // since Preview & VideoCapture already has FPS APIs, Camera2Interop isn't 277 // needed 278 // when they are bound. Also, ImageCapture-only is more complex due to 279 // MeteringRepeating and may pick up further issues. 280 ImageCapture.Builder().also { 281 Camera2Interop.Extender(it) 282 .setCaptureRequestOption( 283 CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, 284 targetFpsRange 285 ) 286 } 287 ) 288 ) 289 290 val isCompleted = result.awaitUntil(timeoutMillis = 10000) 291 assertWithMessage( 292 "Test failed for FPS range = $targetFpsRange" + 293 ", lastSubmittedFpsRange = $lastSubmittedFpsRange" 294 ) 295 .that(isCompleted) 296 .isTrue() 297 298 unbindAllUseCases() 299 300 // Checking for first supported & testable FPS range only 301 return@forEach 302 } 303 } 304 305 @Test 306 fun canOverwriteFpsRangeWithCamera2Interop_whenAnotherSetViaSetTargetFrameRate() = runBlocking { 307 val targetFpsRange = getSupportedFpsRanges().firstOrNull { it.upper <= 30 } 308 val interopFpsRange = getSupportedFpsRanges().lastOrNull { it.upper <= 30 } 309 assumeTrue( 310 "Run the test only when two different supported FPS ranges can be found.", 311 targetFpsRange != null && interopFpsRange != null && targetFpsRange != interopFpsRange 312 ) 313 314 var lastSubmittedFpsRange: Range<Int>? = null 315 val result = 316 sessionCaptureCallback.verify { captureRequest, _ -> 317 captureRequest[CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE]?.let { 318 lastSubmittedFpsRange = it 319 } 320 captureRequest[CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE] == interopFpsRange 321 } 322 323 bindUseCases( 324 listOf( 325 Preview.Builder().setTargetFrameRate(targetFpsRange!!), 326 // since Preview & VideoCapture already has FPS APIs, Camera2Interop isn't needed 327 // when they are bound. 328 ImageCapture.Builder().also { 329 Camera2Interop.Extender(it) 330 .setCaptureRequestOption( 331 CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, 332 interopFpsRange!! 333 ) 334 } 335 ) 336 ) 337 338 val isCompleted = result.awaitUntil(timeoutMillis = 10000) 339 assertWithMessage( 340 "Test failed for FPS range = $interopFpsRange" + 341 ", lastSubmittedFpsRange = $lastSubmittedFpsRange" 342 ) 343 .that(isCompleted) 344 .isTrue() 345 } 346 347 @Test 348 @SdkSuppress(minSdkVersion = 33) 349 fun canEnablePreviewStabilization() = runBlocking { 350 val targetStabilizationMode = CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION 351 352 assumeTrue( 353 "Preview stabilization not supported", 354 getSupportedStabilizationModes().contains(targetStabilizationMode) 355 ) 356 357 var lastSubmittedMode: Int? = null 358 val result = 359 sessionCaptureCallback.verify { captureRequest, _ -> 360 captureRequest[CONTROL_VIDEO_STABILIZATION_MODE]?.let { lastSubmittedMode = it } 361 captureRequest[CONTROL_VIDEO_STABILIZATION_MODE] == targetStabilizationMode 362 } 363 364 bindUseCases( 365 listOf( 366 Preview.Builder().setPreviewStabilizationEnabled(true), 367 VideoCapture.Builder(Recorder.Builder().build()) 368 ) 369 ) 370 371 val isCompleted = result.awaitUntil(timeoutMillis = 10000) 372 assertWithMessage( 373 "Test failed for stabilization mode = $targetStabilizationMode" + 374 ", lastSubmittedMode = $lastSubmittedMode" 375 ) 376 .that(isCompleted) 377 .isTrue() 378 } 379 380 @Test 381 fun canEnableVideoStabilization() = runBlocking { 382 val targetStabilizationMode = CONTROL_VIDEO_STABILIZATION_MODE_ON 383 384 assumeTrue( 385 "Video stabilization not supported", 386 getSupportedStabilizationModes().contains(targetStabilizationMode) 387 ) 388 389 var lastSubmittedMode: Int? = null 390 val result = 391 sessionCaptureCallback.verify { captureRequest, _ -> 392 captureRequest[CONTROL_VIDEO_STABILIZATION_MODE]?.let { lastSubmittedMode = it } 393 captureRequest[CONTROL_VIDEO_STABILIZATION_MODE] == targetStabilizationMode 394 } 395 396 bindUseCases( 397 listOf( 398 Preview.Builder(), 399 VideoCapture.Builder(Recorder.Builder().build()).setVideoStabilizationEnabled(true) 400 ) 401 ) 402 403 val isCompleted = result.awaitUntil(timeoutMillis = 10000) 404 assertWithMessage( 405 "Test failed for stabilization mode = $targetStabilizationMode" + 406 ", lastSubmittedMode = $lastSubmittedMode" 407 ) 408 .that(isCompleted) 409 .isTrue() 410 } 411 412 @Test 413 @SdkSuppress(minSdkVersion = 33) 414 fun canEnablePreviewStabilization_whenBothPreviewAndVideoStabilizationEnabled() = runBlocking { 415 val targetStabilizationMode = CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION 416 417 assumeTrue( 418 "Preview stabilization not supported", 419 getSupportedStabilizationModes().contains(targetStabilizationMode) 420 ) 421 422 assumeTrue( 423 "Video stabilization not supported", 424 getSupportedStabilizationModes().contains(CONTROL_VIDEO_STABILIZATION_MODE_ON) 425 ) 426 427 var lastSubmittedMode: Int? = null 428 val result = 429 sessionCaptureCallback.verify { captureRequest, _ -> 430 captureRequest[CONTROL_VIDEO_STABILIZATION_MODE]?.let { lastSubmittedMode = it } 431 captureRequest[CONTROL_VIDEO_STABILIZATION_MODE] == targetStabilizationMode 432 } 433 434 bindUseCases( 435 listOf( 436 Preview.Builder().setPreviewStabilizationEnabled(true), 437 VideoCapture.Builder(Recorder.Builder().build()).setVideoStabilizationEnabled(true) 438 ) 439 ) 440 441 val isCompleted = result.awaitUntil(timeoutMillis = 10000) 442 assertWithMessage( 443 "Test failed for stabilization mode = $targetStabilizationMode" + 444 ", lastSubmittedMode = $lastSubmittedMode" 445 ) 446 .that(isCompleted) 447 .isTrue() 448 } 449 450 @Test 451 fun canSetStabilizationModeWithCamera2Interop() = runBlocking { 452 val targetStabilizationMode = CONTROL_VIDEO_STABILIZATION_MODE_ON 453 454 assumeTrue( 455 "Video stabilization not supported", 456 getSupportedStabilizationModes().contains(targetStabilizationMode) 457 ) 458 459 var lastSubmittedMode: Int? = null 460 val result = 461 sessionCaptureCallback.verify { captureRequest, _ -> 462 captureRequest[CONTROL_VIDEO_STABILIZATION_MODE]?.let { lastSubmittedMode = it } 463 captureRequest[CONTROL_VIDEO_STABILIZATION_MODE] == targetStabilizationMode 464 } 465 466 bindUseCases( 467 listOf( 468 // since Preview & VideoCapture already has stabilization APIs, Camera2Interop isn't 469 // needed when they are bound. Also, ImageCapture-only is more complex due to 470 // MeteringRepeating and may pick up further issues. 471 ImageCapture.Builder().also { 472 Camera2Interop.Extender(it) 473 .setCaptureRequestOption( 474 CONTROL_VIDEO_STABILIZATION_MODE, 475 targetStabilizationMode 476 ) 477 } 478 ) 479 ) 480 481 val isCompleted = result.awaitUntil(timeoutMillis = 10000) 482 assertWithMessage( 483 "Test failed for stabilization mode = $targetStabilizationMode" + 484 ", lastSubmittedMode = $lastSubmittedMode" 485 ) 486 .that(isCompleted) 487 .isTrue() 488 } 489 490 @Test 491 fun canOverwriteStabilizationWithCamera2Interop_whenEnabledAtVideoCapture() = runBlocking { 492 val targetStabilizationMode = CONTROL_VIDEO_STABILIZATION_MODE_OFF 493 494 assumeTrue( 495 "Video stabilization not supported", 496 getSupportedStabilizationModes().contains(CONTROL_VIDEO_STABILIZATION_MODE_ON) 497 ) 498 499 var lastSubmittedMode: Int? = null 500 val result = 501 sessionCaptureCallback.verify { captureRequest, _ -> 502 captureRequest[CONTROL_VIDEO_STABILIZATION_MODE]?.let { lastSubmittedMode = it } 503 captureRequest[CONTROL_VIDEO_STABILIZATION_MODE] == targetStabilizationMode 504 } 505 506 bindUseCases( 507 listOf( 508 // since Preview & VideoCapture already has stabilization APIs, Camera2Interop isn't 509 // needed when they are bound. Also, ImageCapture-only is more complex due to 510 // MeteringRepeating and may pick up further issues. 511 ImageAnalysis.Builder().also { 512 Camera2Interop.Extender(it) 513 .setCaptureRequestOption( 514 CONTROL_VIDEO_STABILIZATION_MODE, 515 targetStabilizationMode 516 ) 517 }, 518 VideoCapture.Builder(Recorder.Builder().build()).setVideoStabilizationEnabled(true) 519 ) 520 ) 521 522 val isCompleted = result.awaitUntil(timeoutMillis = 10000) 523 assertWithMessage( 524 "Test failed for stabilization mode = $targetStabilizationMode" + 525 ", lastSubmittedMode = $lastSubmittedMode" 526 ) 527 .that(isCompleted) 528 .isTrue() 529 } 530 531 // TODO - Adds tests to check capture option is consistent for both non-repeating and repeating 532 // captures. E.g., FPS range is not submitted for non-repeating capture right now. But this 533 // will probably require us to add Camera2Interop callback for non-repeating captures as well, 534 // something that comes up every now and then, although low priority. 535 536 private fun getSupportedFpsRanges(): Array<Range<Int>> { 537 val cameraCharacteristics = CameraUtil.getCameraCharacteristics(cameraSelector.lensFacing!!) 538 Assume.assumeNotNull(cameraCharacteristics) 539 540 val fpsRanges = cameraCharacteristics!!.get(CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES) 541 Assume.assumeNotNull(fpsRanges) 542 543 return fpsRanges!! 544 } 545 546 private fun getSupportedStabilizationModes(): IntArray { 547 val cameraCharacteristics = CameraUtil.getCameraCharacteristics(cameraSelector.lensFacing!!) 548 Assume.assumeNotNull(cameraCharacteristics) 549 550 val modes = cameraCharacteristics!!.get(CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES) 551 Assume.assumeNotNull(modes) 552 553 return modes!! 554 } 555 556 private fun isHwLevelLegacy(): Boolean { 557 val cameraCharacteristics = CameraUtil.getCameraCharacteristics(cameraSelector.lensFacing!!) 558 Assume.assumeNotNull(cameraCharacteristics) 559 560 val hwLevel = cameraCharacteristics!!.get(INFO_SUPPORTED_HARDWARE_LEVEL) 561 Assume.assumeNotNull(hwLevel) 562 563 return hwLevel == INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY 564 } 565 566 private suspend fun bindUseCases( 567 useCaseBuilders: List<UseCaseConfig.Builder<*, *, *>> = listOf(Preview.Builder()) 568 ) { 569 if (useCaseBuilders.isEmpty()) { 570 return 571 } 572 573 withContext(Dispatchers.Main) { 574 val useCases = mutableListOf<UseCase>() 575 576 useCaseBuilders.forEachIndexed { index, builder -> 577 useCases.add( 578 builder 579 .also { 580 if (index == 0) { // adding to just one use case is enough 581 Camera2InteropUtil.setCameraCaptureSessionCallback( 582 implName, 583 it, 584 sessionCaptureCallback 585 ) 586 } 587 } 588 .build() 589 .apply { 590 if (this is Preview) { 591 setSurfaceProvider( 592 SurfaceTextureProvider.createSurfaceTextureProvider() 593 ) 594 } 595 if (this is ImageAnalysis) { 596 setAnalyzer(CameraXExecutors.directExecutor()) { imageProxy -> 597 imageProxy.close() 598 } 599 } 600 } 601 ) 602 } 603 604 cameraProvider.bindToLifecycle( 605 fakeLifecycleOwner, 606 cameraSelector, 607 *useCases.toTypedArray() 608 ) 609 } 610 } 611 612 private suspend fun unbindAllUseCases() { 613 withContext(Dispatchers.Main) { cameraProvider.unbindAll() } 614 } 615 616 class CaptureCallback : CameraCaptureSession.CaptureCallback() { 617 data class Verification( 618 val condition: 619 (captureRequest: CaptureRequest, captureResult: TotalCaptureResult) -> Boolean, 620 val isVerified: CompletableDeferred<Unit> 621 ) 622 623 private var pendingVerifications = mutableListOf<Verification>() 624 625 /** Returns a [Deferred] representing if verification has been completed */ 626 fun verify( 627 condition: 628 (captureRequest: CaptureRequest, captureResult: TotalCaptureResult) -> Boolean = 629 { _, _ -> 630 false 631 }, 632 ): Deferred<Unit> = 633 CompletableDeferred<Unit>().apply { 634 val verification = Verification(condition, this) 635 pendingVerifications.add(verification) 636 637 invokeOnCompletion { pendingVerifications.remove(verification) } 638 } 639 640 override fun onCaptureCompleted( 641 session: CameraCaptureSession, 642 request: CaptureRequest, 643 result: TotalCaptureResult 644 ) { 645 pendingVerifications.forEach { 646 if (it.condition(request, result)) { 647 it.isVerified.complete(Unit) 648 } 649 } 650 } 651 } 652 653 companion object { 654 @JvmStatic 655 @Parameterized.Parameters(name = "selector={0},config={2}") 656 fun data() = 657 listOf( 658 arrayOf( 659 "back", 660 CameraSelector.DEFAULT_BACK_CAMERA, 661 Camera2Config::class.simpleName, 662 Camera2Config.defaultConfig() 663 ), 664 arrayOf( 665 "back", 666 CameraSelector.DEFAULT_BACK_CAMERA, 667 CameraPipeConfig::class.simpleName, 668 CameraPipeConfig.defaultConfig() 669 ), 670 // front camera is not important with the current test, but may be required in 671 // future 672 ) 673 } 674 } 675