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.camera2.pipe.integration.impl 18 19 import android.graphics.SurfaceTexture 20 import android.hardware.camera2.CameraCharacteristics 21 import android.hardware.camera2.CameraDevice 22 import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH 23 import android.hardware.camera2.CaptureRequest 24 import android.hardware.camera2.CaptureRequest.CONTROL_AE_MODE 25 import android.hardware.camera2.CaptureResult 26 import android.hardware.camera2.params.MeteringRectangle 27 import android.media.Image 28 import android.os.Build 29 import android.os.Looper 30 import android.view.Surface 31 import androidx.camera.camera2.pipe.AeMode 32 import androidx.camera.camera2.pipe.AfMode 33 import androidx.camera.camera2.pipe.AwbMode 34 import androidx.camera.camera2.pipe.FrameInfo 35 import androidx.camera.camera2.pipe.FrameMetadata 36 import androidx.camera.camera2.pipe.FrameNumber 37 import androidx.camera.camera2.pipe.Lock3ABehavior 38 import androidx.camera.camera2.pipe.Request 39 import androidx.camera.camera2.pipe.RequestTemplate 40 import androidx.camera.camera2.pipe.Result3A 41 import androidx.camera.camera2.pipe.StreamId 42 import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter 43 import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter 44 import androidx.camera.camera2.pipe.integration.adapter.CaptureResultAdapter 45 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner 46 import androidx.camera.camera2.pipe.integration.adapter.ZslControl 47 import androidx.camera.camera2.pipe.integration.adapter.asListenableFuture 48 import androidx.camera.camera2.pipe.integration.compat.workaround.CapturePipelineTorchCorrection 49 import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpAutoFlashAEModeDisabler 50 import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpTemplateParamsOverride 51 import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseFlashModeTorchFor3aUpdate 52 import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseTorchAsFlash 53 import androidx.camera.camera2.pipe.integration.compat.workaround.UseTorchAsFlash 54 import androidx.camera.camera2.pipe.integration.compat.workaround.UseTorchAsFlashImpl 55 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig 56 import androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions 57 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop 58 import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraph 59 import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraphSession 60 import androidx.camera.camera2.pipe.integration.testing.FakeCameraProperties 61 import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCameraRequestControl 62 import androidx.camera.camera2.pipe.testing.FakeCameraMetadata 63 import androidx.camera.camera2.pipe.testing.FakeFrameInfo 64 import androidx.camera.camera2.pipe.testing.FakeFrameMetadata 65 import androidx.camera.camera2.pipe.testing.FakeRequestFailure 66 import androidx.camera.camera2.pipe.testing.FakeRequestMetadata 67 import androidx.camera.core.ImageCapture 68 import androidx.camera.core.ImageCaptureException 69 import androidx.camera.core.ImageProxy 70 import androidx.camera.core.impl.CaptureConfig 71 import androidx.camera.core.impl.DeferrableSurface 72 import androidx.camera.core.impl.ImmediateSurface 73 import androidx.camera.core.impl.MutableOptionsBundle 74 import androidx.camera.core.impl.SessionConfig 75 import androidx.camera.core.impl.utils.futures.Futures 76 import androidx.camera.core.internal.CameraCaptureResultImageInfo 77 import androidx.camera.testing.impl.mocks.MockScreenFlash 78 import androidx.testutils.MainDispatcherRule 79 import com.google.common.truth.Truth.assertThat 80 import java.util.concurrent.ExecutionException 81 import java.util.concurrent.Semaphore 82 import java.util.concurrent.TimeUnit 83 import kotlin.collections.removeFirst as removeFirstKt 84 import kotlin.test.assertFailsWith 85 import kotlinx.coroutines.CompletableDeferred 86 import kotlinx.coroutines.Deferred 87 import kotlinx.coroutines.ExperimentalCoroutinesApi 88 import kotlinx.coroutines.Job 89 import kotlinx.coroutines.asExecutor 90 import kotlinx.coroutines.awaitAll 91 import kotlinx.coroutines.delay 92 import kotlinx.coroutines.joinAll 93 import kotlinx.coroutines.launch 94 import kotlinx.coroutines.test.StandardTestDispatcher 95 import kotlinx.coroutines.test.TestScope 96 import kotlinx.coroutines.test.advanceUntilIdle 97 import kotlinx.coroutines.test.currentTime 98 import kotlinx.coroutines.test.runTest 99 import kotlinx.coroutines.withTimeoutOrNull 100 import org.junit.After 101 import org.junit.Assert 102 import org.junit.Assume.assumeTrue 103 import org.junit.Before 104 import org.junit.Rule 105 import org.junit.Test 106 import org.junit.runner.RunWith 107 import org.mockito.kotlin.mock 108 import org.mockito.kotlin.whenever 109 import org.robolectric.annotation.Config 110 import org.robolectric.annotation.internal.DoNotInstrument 111 import org.robolectric.util.ReflectionHelpers 112 113 @OptIn(ExperimentalCoroutinesApi::class, ExperimentalCamera2Interop::class) 114 @RunWith(RobolectricCameraPipeTestRunner::class) 115 @DoNotInstrument 116 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP) 117 class CapturePipelineTest { 118 private val testScope = TestScope() 119 private val testDispatcher = StandardTestDispatcher(testScope.testScheduler) 120 121 @get:Rule val mainDispatcherRule = MainDispatcherRule(testDispatcher) 122 123 private val fakeUseCaseThreads by lazy { 124 UseCaseThreads(testScope, testDispatcher.asExecutor(), testDispatcher) 125 } 126 127 private val fakeRequestControl = 128 object : FakeUseCaseCameraRequestControl() { 129 val torchUpdateEventList = mutableListOf<Boolean>() 130 val setTorchSemaphore = Semaphore(0) 131 132 override fun setTorchOnAsync(): Deferred<Result3A> { 133 torchUpdateEventList.add(true) 134 setTorchSemaphore.release() 135 return CompletableDeferred(Result3A(Result3A.Status.OK)) 136 } 137 138 override fun setTorchOffAsync(aeMode: AeMode): Deferred<Result3A> { 139 torchUpdateEventList.add(false) 140 setTorchSemaphore.release() 141 return CompletableDeferred(Result3A(Result3A.Status.OK)) 142 } 143 } 144 private val comboRequestListener = ComboRequestListener() 145 private val fakeCameraGraphSession = 146 object : FakeCameraGraphSession() { 147 var requestHandler: (List<Request>) -> Unit = { requests -> requests.complete() } 148 val lock3ASemaphore = Semaphore(0) 149 val unlock3ASemaphore = Semaphore(0) 150 val lock3AForCaptureSemaphore = Semaphore(0) 151 val unlock3APostCaptureSemaphore = Semaphore(0) 152 val submitSemaphore = Semaphore(0) 153 154 var virtualTimeAtLock3AForCapture: Long = -1 155 var triggerAfAtLock3AForCapture: Boolean = false 156 var waitForAwbAtLock3AForCapture: Boolean = false 157 158 var cancelAfAtUnlock3AForCapture: Boolean = false 159 160 override suspend fun lock3A( 161 aeMode: AeMode?, 162 afMode: AfMode?, 163 awbMode: AwbMode?, 164 aeRegions: List<MeteringRectangle>?, 165 afRegions: List<MeteringRectangle>?, 166 awbRegions: List<MeteringRectangle>?, 167 aeLockBehavior: Lock3ABehavior?, 168 afLockBehavior: Lock3ABehavior?, 169 awbLockBehavior: Lock3ABehavior?, 170 afTriggerStartAeMode: AeMode?, 171 convergedCondition: ((FrameMetadata) -> Boolean)?, 172 lockedCondition: ((FrameMetadata) -> Boolean)?, 173 frameLimit: Int, 174 convergedTimeLimitNs: Long, 175 lockedTimeLimitNs: Long 176 ): Deferred<Result3A> { 177 lock3ASemaphore.release() 178 return CompletableDeferred(Result3A(Result3A.Status.OK)) 179 } 180 181 override suspend fun unlock3A( 182 ae: Boolean?, 183 af: Boolean?, 184 awb: Boolean?, 185 unlockedCondition: ((FrameMetadata) -> Boolean)?, 186 frameLimit: Int, 187 timeLimitNs: Long 188 ): Deferred<Result3A> { 189 unlock3ASemaphore.release() 190 return CompletableDeferred(Result3A(Result3A.Status.OK)) 191 } 192 193 override suspend fun lock3AForCapture( 194 lockedCondition: ((FrameMetadata) -> Boolean)?, 195 frameLimit: Int, 196 timeLimitNs: Long 197 ): Deferred<Result3A> { 198 lock3AForCaptureSemaphore.release() 199 return CompletableDeferred(Result3A(Result3A.Status.OK)) 200 } 201 202 override suspend fun lock3AForCapture( 203 triggerAf: Boolean, 204 waitForAwb: Boolean, 205 frameLimit: Int, 206 timeLimitNs: Long 207 ): Deferred<Result3A> { 208 virtualTimeAtLock3AForCapture = testScope.currentTime 209 triggerAfAtLock3AForCapture = triggerAf 210 waitForAwbAtLock3AForCapture = waitForAwb 211 lock3AForCaptureSemaphore.release() 212 return CompletableDeferred(Result3A(Result3A.Status.OK)) 213 } 214 215 override fun submit(requests: List<Request>) { 216 requestHandler(requests) 217 submitSemaphore.release() 218 } 219 220 override suspend fun unlock3APostCapture(cancelAf: Boolean): Deferred<Result3A> { 221 cancelAfAtUnlock3AForCapture = cancelAf 222 unlock3APostCaptureSemaphore.release() 223 return CompletableDeferred(Result3A(Result3A.Status.OK)) 224 } 225 } 226 private val fakeStreamId = StreamId(0) 227 private val fakeSurfaceTexture = SurfaceTexture(0).apply { setDefaultBufferSize(640, 480) } 228 private val fakeSurface = Surface(fakeSurfaceTexture) 229 private val fakeDeferrableSurface = ImmediateSurface(fakeSurface) 230 private val singleConfig = 231 CaptureConfig.Builder().apply { addSurface(fakeDeferrableSurface) }.build() 232 private val singleRequest = 233 Request( 234 streams = emptyList(), 235 listeners = emptyList(), 236 parameters = emptyMap(), 237 extras = emptyMap(), 238 template = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 239 ) 240 private val fakeCameraProperties = 241 FakeCameraProperties( 242 FakeCameraMetadata( 243 mapOf(CameraCharacteristics.FLASH_INFO_AVAILABLE to true), 244 ) 245 ) 246 private val fakeUseCaseGraphConfig = 247 UseCaseGraphConfig( 248 graph = FakeCameraGraph(fakeCameraGraphSession = fakeCameraGraphSession), 249 surfaceToStreamMap = mapOf(fakeDeferrableSurface to fakeStreamId), 250 cameraStateAdapter = CameraStateAdapter(), 251 ) 252 private val fakeZslControl = 253 object : ZslControl { 254 var _isZslDisabledByUseCaseConfig = false 255 var _isZslDisabledByFlashMode = false 256 var imageProxyToDequeue: ImageProxy? = null 257 258 override fun addZslConfig(sessionConfigBuilder: SessionConfig.Builder) { 259 // Do nothing 260 } 261 262 override fun clearZslConfig() { 263 // Do nothing 264 } 265 266 override fun isZslSurface( 267 surface: DeferrableSurface, 268 sessionConfig: SessionConfig 269 ): Boolean { 270 return false 271 } 272 273 override fun setZslDisabledByUserCaseConfig(disabled: Boolean) { 274 _isZslDisabledByUseCaseConfig = disabled 275 } 276 277 override fun isZslDisabledByUserCaseConfig(): Boolean { 278 return _isZslDisabledByUseCaseConfig 279 } 280 281 override fun setZslDisabledByFlashMode(disabled: Boolean) { 282 _isZslDisabledByFlashMode = disabled 283 } 284 285 override fun isZslDisabledByFlashMode(): Boolean { 286 return _isZslDisabledByFlashMode 287 } 288 289 override fun dequeueImageFromBuffer(): ImageProxy? { 290 return imageProxyToDequeue 291 } 292 } 293 private val fakeCaptureConfigAdapter = 294 CaptureConfigAdapter( 295 fakeCameraProperties, 296 fakeUseCaseGraphConfig, 297 fakeZslControl, 298 fakeUseCaseThreads, 299 NoOpTemplateParamsOverride, 300 ) 301 private var runningRepeatingJob: Job? = null 302 set(value) { 303 runningRepeatingJob?.cancel() 304 field = value 305 } 306 307 private lateinit var flashControl: FlashControl 308 private lateinit var state3AControl: State3AControl 309 private lateinit var torchControl: TorchControl 310 private lateinit var capturePipeline: CapturePipelineImpl 311 312 private lateinit var fakeUseCaseCameraState: UseCaseCameraState 313 314 private val screenFlash = MockScreenFlash() 315 316 @Before 317 fun setUp() { 318 state3AControl = 319 State3AControl( 320 fakeCameraProperties, 321 NoOpAutoFlashAEModeDisabler, 322 ) 323 .apply { requestControl = fakeRequestControl } 324 325 torchControl = 326 TorchControl( 327 fakeCameraProperties, 328 state3AControl, 329 fakeUseCaseThreads, 330 ) 331 .also { 332 it.requestControl = fakeRequestControl 333 334 // Ensure the control is updated after the UseCaseCamera been set. 335 assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(testScope)).isTrue() 336 fakeRequestControl.torchUpdateEventList.clear() 337 } 338 339 flashControl = 340 FlashControl( 341 cameraProperties = fakeCameraProperties, 342 state3AControl = state3AControl, 343 threads = fakeUseCaseThreads, 344 torchControl = torchControl, 345 useFlashModeTorchFor3aUpdate = NotUseFlashModeTorchFor3aUpdate, 346 ) 347 .apply { setScreenFlash(this@CapturePipelineTest.screenFlash) } 348 349 fakeUseCaseCameraState = 350 UseCaseCameraState( 351 fakeUseCaseGraphConfig, 352 fakeUseCaseThreads, 353 sessionProcessorManager = null, 354 templateParamsOverride = NoOpTemplateParamsOverride, 355 ) 356 357 capturePipeline = createCapturePipeline() 358 } 359 360 @After 361 fun tearDown() { 362 runningRepeatingJob = null 363 fakeSurface.release() 364 fakeSurfaceTexture.release() 365 } 366 367 @Test 368 fun miniLatency_flashOn_shouldTriggerAePreCapture(): Unit = runTest { 369 flashOn_shouldTriggerAePreCapture(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) 370 } 371 372 @Test 373 fun maxQuality_flashOn_shouldTriggerAePreCapture(): Unit = runTest { 374 flashOn_shouldTriggerAePreCapture(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) 375 } 376 377 private suspend fun TestScope.flashOn_shouldTriggerAePreCapture(imageCaptureMode: Int) { 378 // Arrange. 379 val requestList = mutableListOf<Request>() 380 fakeCameraGraphSession.requestHandler = { requests -> requestList.addAll(requests) } 381 382 // Act. 383 capturePipeline.submitStillCaptures( 384 configs = listOf(singleConfig), 385 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 386 sessionConfigOptions = MutableOptionsBundle.create(), 387 captureMode = imageCaptureMode, 388 flashMode = ImageCapture.FLASH_MODE_ON, 389 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH, 390 ) 391 392 // Assert. 393 assertThat(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this)).isTrue() 394 395 // Complete the capture request. 396 assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue() 397 requestList.complete() 398 399 // Assert 2, unlock3APostCapture should be called. 400 assertThat(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this)).isTrue() 401 } 402 403 @Test 404 fun miniLatency_flashAutoFlashRequired_shouldTriggerAePreCapture(): Unit = runTest { 405 flashAutoFlashRequired_shouldTriggerAePreCapture(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) 406 } 407 408 @Test 409 fun maxQuality_flashAutoFlashRequired_shouldTriggerAePreCapture(): Unit = runTest { 410 flashAutoFlashRequired_shouldTriggerAePreCapture(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) 411 } 412 413 private suspend fun TestScope.flashAutoFlashRequired_shouldTriggerAePreCapture( 414 imageCaptureMode: Int 415 ) { 416 // Arrange. 417 comboRequestListener.simulateRepeatingResult( 418 resultParameters = 419 mapOf( 420 CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED, 421 ) 422 ) 423 val requestList = mutableListOf<Request>() 424 fakeCameraGraphSession.requestHandler = { requests -> requestList.addAll(requests) } 425 426 // Act. 427 capturePipeline.submitStillCaptures( 428 configs = listOf(singleConfig), 429 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 430 sessionConfigOptions = MutableOptionsBundle.create(), 431 captureMode = imageCaptureMode, 432 flashMode = ImageCapture.FLASH_MODE_AUTO, 433 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH, 434 ) 435 436 // Assert 1, lock3AForCapture should be called, but not call unlock3APostCapture 437 // (before capturing is finished). 438 assertThat(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this)).isTrue() 439 assertThat(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this)).isFalse() 440 441 // Complete the capture request. 442 assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue() 443 requestList.complete() 444 445 // Assert 2, unlock3APostCapture should be called. 446 assertThat(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this)).isTrue() 447 } 448 449 @Test 450 fun miniLatency_withTorchAsFlashQuirk_shouldOpenTorch(): Unit = runTest { 451 withTorchAsFlashQuirk_shouldOpenTorch(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) 452 } 453 454 @Test 455 fun maxQuality_withTorchAsFlashQuirk_shouldOpenTorch(): Unit = runTest { 456 withTorchAsFlashQuirk_shouldOpenTorch(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) 457 } 458 459 private suspend fun TestScope.withTorchAsFlashQuirk_shouldOpenTorch(imageCaptureMode: Int) { 460 // Arrange. 461 capturePipeline = createCapturePipeline(useTorchAsFlash = UseTorchAsFlashImpl) 462 463 val requestList = mutableListOf<Request>() 464 fakeCameraGraphSession.requestHandler = { requests -> requestList.addAll(requests) } 465 466 comboRequestListener.simulate3aConvergence() 467 468 // Act. 469 capturePipeline.submitStillCaptures( 470 configs = listOf(singleConfig), 471 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 472 sessionConfigOptions = MutableOptionsBundle.create(), 473 captureMode = imageCaptureMode, 474 flashMode = ImageCapture.FLASH_MODE_ON, 475 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH, 476 ) 477 478 // Assert 1, torch should be turned on. 479 assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isTrue() 480 assertThat(fakeRequestControl.torchUpdateEventList.size).isEqualTo(1) 481 assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isTrue() 482 483 // Complete the capture request. 484 assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue() 485 requestList.complete() 486 487 // Assert 2, torch should be turned off. 488 assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isTrue() 489 assertThat(fakeRequestControl.torchUpdateEventList.size).isEqualTo(1) 490 assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isFalse() 491 } 492 493 @Test 494 fun miniLatency_withTemplateRecord_shouldOpenTorch(): Unit = runTest { 495 withTemplateRecord_shouldOpenTorch(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) 496 } 497 498 @Test 499 fun maxQuality_withTemplateRecord_shouldOpenTorch(): Unit = runTest { 500 withTemplateRecord_shouldOpenTorch(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) 501 } 502 503 private suspend fun TestScope.withTemplateRecord_shouldOpenTorch(imageCaptureMode: Int) { 504 // Arrange. 505 capturePipeline.template = CameraDevice.TEMPLATE_RECORD 506 507 val requestList = mutableListOf<Request>() 508 fakeCameraGraphSession.requestHandler = { requests -> requestList.addAll(requests) } 509 510 // Act. 511 capturePipeline.submitStillCaptures( 512 configs = listOf(singleConfig), 513 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 514 sessionConfigOptions = MutableOptionsBundle.create(), 515 captureMode = imageCaptureMode, 516 flashMode = ImageCapture.FLASH_MODE_ON, 517 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH, 518 ) 519 520 // Assert 1, torch should be turned on. 521 assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isTrue() 522 assertThat(fakeRequestControl.torchUpdateEventList.size).isEqualTo(1) 523 assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isTrue() 524 525 // Complete the capture request. 526 assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue() 527 requestList.complete() 528 529 // Assert 2, torch should be turned off. 530 assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isTrue() 531 assertThat(fakeRequestControl.torchUpdateEventList.size).isEqualTo(1) 532 assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isFalse() 533 } 534 535 @Test 536 fun miniLatency_withFlashTypeTorch_shouldOpenTorch(): Unit = runTest { 537 withFlashTypeTorch_shouldOpenTorch(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) 538 } 539 540 @Test 541 fun maxQuality_withFlashTypeTorch_shouldOpenTorch(): Unit = runTest { 542 withFlashTypeTorch_shouldOpenTorch(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) 543 } 544 545 private suspend fun TestScope.withFlashTypeTorch_shouldOpenTorch(imageCaptureMode: Int) { 546 // Arrange. 547 val requestList = mutableListOf<Request>() 548 fakeCameraGraphSession.requestHandler = { requests -> requestList.addAll(requests) } 549 550 // Act. 551 capturePipeline.submitStillCaptures( 552 configs = listOf(singleConfig), 553 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 554 sessionConfigOptions = MutableOptionsBundle.create(), 555 captureMode = imageCaptureMode, 556 flashMode = ImageCapture.FLASH_MODE_ON, 557 flashType = ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH, 558 ) 559 560 // Assert 1, torch should be turned on. 561 assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isTrue() 562 assertThat(fakeRequestControl.torchUpdateEventList.size).isEqualTo(1) 563 assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isTrue() 564 565 // Complete the capture request. 566 assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue() 567 requestList.complete() 568 569 // Assert 2, torch should be turned off. 570 assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isTrue() 571 assertThat(fakeRequestControl.torchUpdateEventList.size).isEqualTo(1) 572 assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isFalse() 573 } 574 575 @Test 576 fun miniLatency_flashRequired_withFlashTypeTorchAndNoQuirk_shouldLock3AForCapture(): Unit = 577 runTest { 578 withFlashTypeTorch_shouldLock3AAsNeeded( 579 capturePipeline, 580 ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 581 ImageCapture.FLASH_MODE_ON, 582 shouldLock3AForCapture = true, 583 ) 584 } 585 586 @Test 587 fun miniLatency_flashRequired_withFlashTypeTorchAndQuirk_shouldNotLock3A(): Unit = runTest { 588 capturePipeline = createCapturePipeline(useTorchAsFlash = UseTorchAsFlashImpl) 589 590 withFlashTypeTorch_shouldLock3AAsNeeded( 591 capturePipeline, 592 ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 593 ImageCapture.FLASH_MODE_ON, 594 shouldLock3A = false, 595 ) 596 } 597 598 @Test 599 fun miniLatency_flashRequired_withFlashTypeTorchAndQuirk_worksViaTimeoutWithout3aConverge(): 600 Unit = runTest { 601 capturePipeline = createCapturePipeline(useTorchAsFlash = UseTorchAsFlashImpl) 602 603 withFlashTypeTorch_shouldLock3AAsNeeded( 604 capturePipeline, 605 ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 606 ImageCapture.FLASH_MODE_ON, 607 shouldLock3A = false, 608 simulate3aConvergence = false, 609 ) 610 } 611 612 @Test 613 fun miniLatency_flashRequired_withFlashTypeTorchAndQuirk_listenerRemovedAfterTimeout(): Unit = 614 runTest { 615 capturePipeline = createCapturePipeline(useTorchAsFlash = UseTorchAsFlashImpl) 616 val initialListenerSize = comboRequestListener.listeners.size 617 618 withFlashTypeTorch_shouldLock3AAsNeeded( 619 capturePipeline, 620 ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 621 ImageCapture.FLASH_MODE_ON, 622 shouldLock3A = false, 623 simulate3aConvergence = false, 624 ) 625 626 assertThat(comboRequestListener.listeners.size).isEqualTo(initialListenerSize) 627 } 628 629 @Test 630 fun maxQuality_flashOffWithFlashTypeTorchAndNoQuirk_shouldLock3A(): Unit = runTest { 631 withFlashTypeTorch_shouldLock3AAsNeeded( 632 capturePipeline, 633 ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY, 634 ImageCapture.FLASH_MODE_OFF, 635 shouldLock3AForCapture = false, 636 ) 637 } 638 639 @Test 640 fun maxQuality_withFlashTypeTorch_shouldLock3AForCapture(): Unit = runTest { 641 withFlashTypeTorch_shouldLock3AAsNeeded( 642 capturePipeline, 643 ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY, 644 ImageCapture.FLASH_MODE_ON, 645 shouldLock3AForCapture = true, 646 ) 647 } 648 649 @Test 650 fun maxQuality_withFlashTypeTorchAndQuirk_shouldLock3A(): Unit = runTest { 651 capturePipeline = createCapturePipeline(useTorchAsFlash = UseTorchAsFlashImpl) 652 653 withFlashTypeTorch_shouldLock3AAsNeeded( 654 capturePipeline, 655 ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY, 656 ImageCapture.FLASH_MODE_ON, 657 ) 658 } 659 660 /** 661 * Tests whether 3A is locked or AE pre-capture is triggered (i.e. lock3AForCapture) for image 662 * captures with flash type torch based on requirements. 663 * 664 * @param shouldLock3AForCapture Whether capture-specific 3A locking should be used which uses 665 * AE pre-capture. This is not used in most workaround cases (thus false by default) while 666 * still used in a few safer cases. 667 * @param shouldLock3A Whether 3A should be locked. No 3A locking may be required for minimum 668 * latency image capture mode. By default, this value depends on [imageCaptureMode] and 669 * [shouldLock3AForCapture]. 670 * @param simulate3aConvergence Whether 3A convergence should be simulated, true by default. 671 */ 672 private suspend fun TestScope.withFlashTypeTorch_shouldLock3AAsNeeded( 673 capturePipeline: CapturePipeline, 674 imageCaptureMode: Int, 675 flashMode: Int, 676 shouldLock3AForCapture: Boolean = false, 677 shouldLock3A: Boolean = 678 imageCaptureMode == ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY && 679 !shouldLock3AForCapture, 680 simulate3aConvergence: Boolean = true, 681 ) { 682 // Arrange. 683 val requestList = mutableListOf<Request>() 684 fakeCameraGraphSession.requestHandler = { requests -> requestList.addAll(requests) } 685 686 if (simulate3aConvergence) { 687 comboRequestListener.simulate3aConvergence() 688 } 689 690 // Act. 691 capturePipeline.submitStillCaptures( 692 configs = listOf(singleConfig), 693 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 694 sessionConfigOptions = MutableOptionsBundle.create(), 695 captureMode = imageCaptureMode, 696 flashMode = flashMode, 697 flashType = ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH, 698 ) 699 700 // Assert 1, should call lock3A, but not call unlock3A (before capturing is finished). 701 if (shouldLock3A) { 702 assertThat(fakeCameraGraphSession.lock3ASemaphore.tryAcquire(this)).isTrue() 703 } else { 704 assertThat(fakeCameraGraphSession.lock3ASemaphore.tryAcquire(this)).isFalse() 705 } 706 707 if (shouldLock3AForCapture) { 708 assertThat(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this)).isTrue() 709 } else { 710 assertThat(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this)).isFalse() 711 } 712 713 // Shouldn't unlock util the capture request is submitted. 714 assertThat(fakeCameraGraphSession.unlock3ASemaphore.tryAcquire(this)).isFalse() 715 assertThat(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this)).isFalse() 716 717 // Complete the capture request. 718 assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue() 719 requestList.complete() 720 721 advanceUntilIdle() 722 723 // Assert 2, should call unlock3A as required. 724 if (shouldLock3A) { 725 assertThat(fakeCameraGraphSession.unlock3ASemaphore.tryAcquire(this)).isTrue() 726 } else { 727 assertThat(fakeCameraGraphSession.unlock3ASemaphore.tryAcquire(this)).isFalse() 728 } 729 730 if (shouldLock3AForCapture) { 731 assertThat(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this)) 732 .isTrue() 733 } else { 734 assertThat(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this)) 735 .isFalse() 736 } 737 } 738 739 @Test 740 fun miniLatency_withFlashTypeTorch_shouldNotLock3A(): Unit = runTest { 741 // Act. 742 capturePipeline 743 .submitStillCaptures( 744 configs = listOf(singleConfig), 745 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 746 sessionConfigOptions = MutableOptionsBundle.create(), 747 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 748 flashMode = ImageCapture.FLASH_MODE_OFF, 749 flashType = ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH, 750 ) 751 .awaitAllWithTimeout() 752 753 // Assert, there is no invocation on lock3A(). 754 assertThat(fakeCameraGraphSession.lock3ASemaphore.tryAcquire(this)).isFalse() 755 } 756 757 @Test 758 fun withFlashTypeTorch_torchAlreadyOn_skipTurnOnTorch(): Unit = runTest { 759 // Arrange. 760 // Ensure the torch is already turned on before capturing. 761 torchControl.setTorchAsync(true) 762 assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isTrue() 763 764 // Act. 765 capturePipeline 766 .submitStillCaptures( 767 configs = listOf(singleConfig), 768 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 769 sessionConfigOptions = MutableOptionsBundle.create(), 770 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 771 flashMode = ImageCapture.FLASH_MODE_ON, 772 flashType = ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH, 773 ) 774 .awaitAllWithTimeout() 775 776 // Assert, there is no invocation on setTorch(). 777 assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isFalse() 778 } 779 780 @Test 781 fun miniLatency_shouldNotAePreCapture(): Unit = runTest { 782 // Act. 783 capturePipeline 784 .submitStillCaptures( 785 configs = listOf(singleConfig), 786 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 787 sessionConfigOptions = MutableOptionsBundle.create(), 788 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 789 flashMode = ImageCapture.FLASH_MODE_OFF, 790 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH, 791 ) 792 .awaitAllWithTimeout() 793 794 // Assert, there is only 1 single capture request. 795 assertThat(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this)).isFalse() 796 } 797 798 @Config(minSdk = 23) 799 @Test 800 fun submitZslCaptureRequests_withZslTemplate_templateZeroShutterLagSent(): Unit = runTest { 801 // Arrange. 802 val requestList = mutableListOf<Request>() 803 fakeCameraGraphSession.requestHandler = { requests -> 804 requestList.addAll(requests) 805 requests.complete() 806 } 807 val imageCaptureConfig = 808 CaptureConfig.Builder().let { 809 it.addSurface(fakeDeferrableSurface) 810 it.templateType = CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG 811 it.build() 812 } 813 configureZslControl() 814 815 // Act. 816 capturePipeline 817 .submitStillCaptures( 818 listOf(imageCaptureConfig), 819 RequestTemplate(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG), 820 MutableOptionsBundle.create(), 821 captureMode = ImageCapture.CAPTURE_MODE_ZERO_SHUTTER_LAG, 822 flashMode = ImageCapture.FLASH_MODE_OFF, 823 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH, 824 ) 825 .awaitAllWithTimeout() 826 advanceUntilIdle() 827 828 // Assert. 829 val request = requestList.single() 830 assertThat(request.streams.single()).isEqualTo(fakeStreamId) 831 assertThat(request.template) 832 .isEqualTo(RequestTemplate(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG)) 833 } 834 835 @Config(minSdk = 23) 836 @Test 837 fun submitZslCaptureRequests_withNoTemplate_templateStillPictureSent(): Unit = runTest { 838 // Arrange. 839 val requestList = mutableListOf<Request>() 840 fakeCameraGraphSession.requestHandler = { requests -> 841 requestList.addAll(requests) 842 requests.complete() 843 } 844 val imageCaptureConfig = 845 CaptureConfig.Builder().let { 846 it.addSurface(fakeDeferrableSurface) 847 it.build() 848 } 849 configureZslControl() 850 851 // Act. 852 capturePipeline 853 .submitStillCaptures( 854 listOf(imageCaptureConfig), 855 RequestTemplate(CameraDevice.TEMPLATE_PREVIEW), 856 MutableOptionsBundle.create(), 857 captureMode = ImageCapture.CAPTURE_MODE_ZERO_SHUTTER_LAG, 858 flashMode = ImageCapture.FLASH_MODE_OFF, 859 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH, 860 ) 861 .awaitAllWithTimeout() 862 863 // Assert. 864 val request = requestList.single() 865 assertThat(request.streams.single()).isEqualTo(fakeStreamId) 866 assertThat(request.template).isEqualTo(RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE)) 867 } 868 869 @Config(minSdk = 23) 870 @Test 871 fun submitZslCaptureRequests_withZslDisabledByUseCaseConfig_templateStillPictureSent(): Unit = 872 runTest { 873 // Arrange. 874 val requestList = mutableListOf<Request>() 875 fakeCameraGraphSession.requestHandler = { requests -> 876 requestList.addAll(requests) 877 requests.complete() 878 } 879 val imageCaptureConfig = 880 CaptureConfig.Builder().let { 881 it.addSurface(fakeDeferrableSurface) 882 it.templateType = CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG 883 it.build() 884 } 885 configureZslControl() 886 fakeZslControl.setZslDisabledByUserCaseConfig(true) 887 888 // Act. 889 capturePipeline 890 .submitStillCaptures( 891 listOf(imageCaptureConfig), 892 RequestTemplate(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG), 893 MutableOptionsBundle.create(), 894 captureMode = ImageCapture.CAPTURE_MODE_ZERO_SHUTTER_LAG, 895 flashMode = ImageCapture.FLASH_MODE_OFF, 896 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH, 897 ) 898 .awaitAllWithTimeout() 899 900 // Assert. 901 val request = requestList.single() 902 assertThat(request.streams.single()).isEqualTo(fakeStreamId) 903 assertThat(request.template) 904 .isEqualTo(RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE)) 905 } 906 907 @Config(minSdk = 23) 908 @Test 909 fun submitZslCaptureRequests_withZslDisabledByFlashMode_templateStillPictureSent(): Unit = 910 runTest { 911 // Arrange. 912 val requestList = mutableListOf<Request>() 913 fakeCameraGraphSession.requestHandler = { requests -> 914 requestList.addAll(requests) 915 requests.complete() 916 } 917 val imageCaptureConfig = 918 CaptureConfig.Builder().let { 919 it.addSurface(fakeDeferrableSurface) 920 it.templateType = CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG 921 it.build() 922 } 923 configureZslControl() 924 fakeZslControl.setZslDisabledByFlashMode(true) 925 926 // Act. 927 capturePipeline 928 .submitStillCaptures( 929 listOf(imageCaptureConfig), 930 RequestTemplate(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG), 931 MutableOptionsBundle.create(), 932 captureMode = ImageCapture.CAPTURE_MODE_ZERO_SHUTTER_LAG, 933 flashMode = ImageCapture.FLASH_MODE_OFF, 934 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH, 935 ) 936 .awaitAllWithTimeout() 937 938 // Assert. 939 val request = requestList.single() 940 assertThat(request.streams.single()).isEqualTo(fakeStreamId) 941 assertThat(request.template) 942 .isEqualTo(RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE)) 943 } 944 945 private fun configureZslControl() { 946 val fakeImageProxy: ImageProxy = mock() 947 val fakeCaptureResult = 948 CaptureResultAdapter(FakeRequestMetadata(), FrameNumber(1), FakeFrameInfo()) 949 val fakeImageInfo = CameraCaptureResultImageInfo(fakeCaptureResult) 950 val fakeImage: Image = mock() 951 whenever(fakeImageProxy.imageInfo).thenReturn(fakeImageInfo) 952 whenever(fakeImageProxy.image).thenReturn(fakeImage) 953 fakeZslControl.imageProxyToDequeue = fakeImageProxy 954 } 955 956 @Test 957 fun captureFailure_taskShouldFailure(): Unit = runTest { 958 // Arrange. 959 fakeCameraGraphSession.requestHandler = { requests -> 960 requests.forEach { request -> 961 // Callback capture fail immediately. 962 request.listeners.forEach { 963 val requestMetadata = FakeRequestMetadata() 964 val frameNumber = FrameNumber(100L) 965 it.onFailed( 966 requestMetadata = requestMetadata, 967 frameNumber = frameNumber, 968 requestFailure = FakeRequestFailure(requestMetadata, frameNumber) 969 ) 970 } 971 } 972 } 973 974 // Act. 975 val resultDeferredList = 976 capturePipeline.submitStillCaptures( 977 configs = listOf(singleConfig), 978 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 979 sessionConfigOptions = MutableOptionsBundle.create(), 980 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 981 flashMode = ImageCapture.FLASH_MODE_OFF, 982 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH, 983 ) 984 985 // Assert. 986 advanceUntilIdle() 987 val exception = 988 assertFailsWith(ImageCaptureException::class) { 989 resultDeferredList.awaitAllWithTimeout() 990 } 991 assertThat(exception.imageCaptureError).isEqualTo(ImageCapture.ERROR_CAPTURE_FAILED) 992 } 993 994 @Test 995 fun captureCancel_taskShouldFailureWithCAMERA_CLOSED(): Unit = runTest { 996 // Arrange. 997 fakeCameraGraphSession.requestHandler = { requests -> 998 requests.forEach { request -> 999 // Callback capture abort immediately. 1000 request.listeners.forEach { it.onAborted(singleRequest) } 1001 } 1002 } 1003 1004 // Act. 1005 val resultDeferredList = 1006 capturePipeline.submitStillCaptures( 1007 configs = listOf(singleConfig), 1008 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 1009 sessionConfigOptions = MutableOptionsBundle.create(), 1010 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 1011 flashMode = ImageCapture.FLASH_MODE_OFF, 1012 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH, 1013 ) 1014 1015 // Assert. 1016 advanceUntilIdle() 1017 val exception = 1018 Assert.assertThrows(ExecutionException::class.java) { 1019 Futures.allAsList(resultDeferredList.map { it.asListenableFuture() }) 1020 .get(2, TimeUnit.SECONDS) 1021 } 1022 Assert.assertTrue(exception.cause is ImageCaptureException) 1023 assertThat((exception.cause as ImageCaptureException).imageCaptureError) 1024 .isEqualTo(ImageCapture.ERROR_CAMERA_CLOSED) 1025 } 1026 1027 @Test 1028 fun stillCaptureWithFlashStopRepeatingQuirk_shouldStopRepeatingTemporarily() = runTest { 1029 // Arrange 1030 ReflectionHelpers.setStaticField(Build::class.java, "MANUFACTURER", "SAMSUNG") 1031 ReflectionHelpers.setStaticField(Build::class.java, "MODEL", "SM-A716") 1032 1033 val submittedRequestList = mutableListOf<Request>() 1034 fakeCameraGraphSession.requestHandler = { requests -> 1035 submittedRequestList.addAll(requests) 1036 } 1037 fakeUseCaseCameraState.update(streams = setOf(StreamId(0))) 1038 1039 // Act. 1040 capturePipeline.submitStillCaptures( 1041 configs = 1042 listOf( 1043 CaptureConfig.Builder() 1044 .apply { 1045 addSurface(fakeDeferrableSurface) 1046 implementationOptions = 1047 CaptureRequestOptions.Builder() 1048 .apply { 1049 setCaptureRequestOption( 1050 CONTROL_AE_MODE, 1051 CONTROL_AE_MODE_ON_ALWAYS_FLASH 1052 ) 1053 } 1054 .build() 1055 } 1056 .build() 1057 ), 1058 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 1059 sessionConfigOptions = MutableOptionsBundle.create(), 1060 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 1061 flashMode = ImageCapture.FLASH_MODE_ON, 1062 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH, 1063 ) 1064 1065 // Assert, stopRepeating -> submit -> startRepeating flow should be used. 1066 assertThat(fakeCameraGraphSession.stopRepeatingSemaphore.tryAcquire(this)).isTrue() 1067 1068 assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue() 1069 1070 // Completing the submitted capture request. 1071 submittedRequestList.complete() 1072 1073 assertThat(fakeCameraGraphSession.repeatingRequestSemaphore.tryAcquire(this)).isTrue() 1074 } 1075 1076 @Test 1077 fun stillCaptureWithFlashStopRepeatingQuirkNotEnabled_shouldNotStopRepeating() = runTest { 1078 // Arrange 1079 val submittedRequestList = mutableListOf<Request>() 1080 fakeCameraGraphSession.requestHandler = { requests -> 1081 submittedRequestList.addAll(requests) 1082 } 1083 fakeUseCaseCameraState.update(streams = setOf(StreamId(0))) 1084 1085 // Act. 1086 capturePipeline.submitStillCaptures( 1087 configs = 1088 listOf( 1089 CaptureConfig.Builder() 1090 .apply { 1091 addSurface(fakeDeferrableSurface) 1092 implementationOptions = 1093 CaptureRequestOptions.Builder() 1094 .apply { 1095 setCaptureRequestOption( 1096 CONTROL_AE_MODE, 1097 CONTROL_AE_MODE_ON_ALWAYS_FLASH 1098 ) 1099 } 1100 .build() 1101 } 1102 .build() 1103 ), 1104 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 1105 sessionConfigOptions = MutableOptionsBundle.create(), 1106 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 1107 flashMode = ImageCapture.FLASH_MODE_ON, 1108 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH, 1109 ) 1110 1111 // Assert, repeating should not be stopped when quirk not enabled. 1112 assertThat(fakeCameraGraphSession.stopRepeatingSemaphore.tryAcquire(this)).isFalse() 1113 1114 assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue() 1115 1116 // Resetting repeatingRequestSemaphore because startRepeating can be called before 1117 fakeCameraGraphSession.repeatingRequestSemaphore = Semaphore(0) 1118 1119 // Completing the submitted capture request. 1120 submittedRequestList.complete() 1121 1122 assertThat(fakeCameraGraphSession.repeatingRequestSemaphore.tryAcquire(this)).isFalse() 1123 } 1124 1125 @Test 1126 fun torchAsFlash_torchCorrection_shouldTurnsTorchOffOn(): Unit = runTest { 1127 torchStateCorrectionTest(ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH) 1128 } 1129 1130 @Test 1131 fun defaultCapture_torchCorrection_shouldTurnsTorchOffOn(): Unit = runTest { 1132 torchStateCorrectionTest(ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH) 1133 } 1134 1135 private suspend fun TestScope.torchStateCorrectionTest(flashType: Int) { 1136 // Arrange. 1137 torchControl.setTorchAsync(torch = true).join() 1138 verifyTorchState(true) 1139 1140 val requestList = mutableListOf<Request>() 1141 fakeCameraGraphSession.requestHandler = { requests -> requestList.addAll(requests) } 1142 val capturePipelineTorchCorrection = 1143 CapturePipelineTorchCorrection( 1144 cameraProperties = FakeCameraProperties(), 1145 capturePipelineImpl = capturePipeline, 1146 threads = fakeUseCaseThreads, 1147 torchControl = torchControl, 1148 ) 1149 1150 // Act. 1151 capturePipelineTorchCorrection.submitStillCaptures( 1152 configs = listOf(singleConfig), 1153 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 1154 sessionConfigOptions = MutableOptionsBundle.create(), 1155 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 1156 flashMode = ImageCapture.FLASH_MODE_ON, 1157 flashType = flashType, 1158 ) 1159 1160 assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue() 1161 assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isFalse() 1162 // Complete the capture request. 1163 requestList.complete() 1164 1165 // Assert, the Torch should be turned off, and then turned on. 1166 verifyTorchState(false) 1167 verifyTorchState(true) 1168 // No more invocation to set Torch mode. 1169 assertThat(fakeRequestControl.torchUpdateEventList.size).isEqualTo(0) 1170 } 1171 1172 private fun TestScope.verifyTorchState(state: Boolean) { 1173 assertThat(fakeRequestControl.setTorchSemaphore.tryAcquire(this)).isTrue() 1174 assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt() == state).isTrue() 1175 } 1176 1177 // TODO(b/326170400): port torch related precapture tests 1178 1179 @Test 1180 fun lock3aTriggered_whenScreenFlashPreCaptureCalled() = runTest { 1181 capturePipeline.invokeScreenFlashPreCaptureTasks(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) 1182 1183 assertThat(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this)).isTrue() 1184 } 1185 1186 @Test 1187 fun lock3aTriggeredAfterTimeout_whenScreenFlashApplyNotCompleted() = runTest { 1188 screenFlash.setApplyCompletedInstantly(false) 1189 1190 capturePipeline.invokeScreenFlashPreCaptureTasks(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) 1191 1192 assertThat(fakeCameraGraphSession.virtualTimeAtLock3AForCapture) 1193 .isEqualTo( 1194 TimeUnit.SECONDS.toMillis(ImageCapture.SCREEN_FLASH_UI_APPLY_TIMEOUT_SECONDS) 1195 ) 1196 } 1197 1198 @Test 1199 fun afNotTriggered_whenScreenFlashPreCaptureCalledWithMinimizeLatency() = runTest { 1200 capturePipeline.invokeScreenFlashPreCaptureTasks(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) 1201 1202 assumeTrue(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this)) 1203 assertThat(fakeCameraGraphSession.triggerAfAtLock3AForCapture).isFalse() 1204 } 1205 1206 @Test 1207 fun waitsForAwb_whenScreenFlashPreCaptureCalledWithMinimizeLatency() = runTest { 1208 capturePipeline.invokeScreenFlashPreCaptureTasks(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) 1209 1210 assumeTrue(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this)) 1211 assertThat(fakeCameraGraphSession.waitForAwbAtLock3AForCapture).isTrue() 1212 } 1213 1214 @Test 1215 fun afTriggered_whenScreenFlashPreCaptureCalledWithMaximumQuality() = runTest { 1216 capturePipeline.invokeScreenFlashPreCaptureTasks(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) 1217 1218 assumeTrue(fakeCameraGraphSession.lock3AForCaptureSemaphore.tryAcquire(this)) 1219 assertThat(fakeCameraGraphSession.triggerAfAtLock3AForCapture).isTrue() 1220 } 1221 1222 @Test 1223 fun screenFlashClearInvokedInMainThread_whenScreenFlashPostCaptureCalled() = runTest { 1224 capturePipeline.invokeScreenFlashPostCaptureTasks( 1225 ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 1226 ) 1227 1228 assertThat(screenFlash.lastClearThreadLooper).isEqualTo(Looper.getMainLooper()) 1229 } 1230 1231 // TODO(b/326170400): port torch related postcapture tests 1232 1233 @Test 1234 fun unlock3aTriggered_whenPostCaptureCalled() = runTest { 1235 capturePipeline.invokeScreenFlashPostCaptureTasks( 1236 ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 1237 ) 1238 1239 assertThat(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this)).isTrue() 1240 } 1241 1242 @Test 1243 fun doesNotCancelAf_whenPostCaptureCalledWithMinimizeLatency() = runTest { 1244 capturePipeline.invokeScreenFlashPostCaptureTasks( 1245 ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 1246 ) 1247 1248 assumeTrue(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this)) 1249 assertThat(fakeCameraGraphSession.cancelAfAtUnlock3AForCapture).isFalse() 1250 } 1251 1252 @Test 1253 fun cancelsAf_whenPostCaptureCalledWithMaximumQuality() = runTest { 1254 capturePipeline.invokeScreenFlashPostCaptureTasks( 1255 ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY, 1256 ) 1257 1258 assumeTrue(fakeCameraGraphSession.unlock3APostCaptureSemaphore.tryAcquire(this)) 1259 assertThat(fakeCameraGraphSession.cancelAfAtUnlock3AForCapture).isTrue() 1260 } 1261 1262 @Test 1263 fun screenFlashApplyInvoked_whenStillCaptureSubmittedWithScreenFlash() = runTest { 1264 capturePipeline 1265 .submitStillCaptures( 1266 configs = listOf(singleConfig), 1267 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 1268 sessionConfigOptions = MutableOptionsBundle.create(), 1269 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 1270 flashMode = ImageCapture.FLASH_MODE_SCREEN, 1271 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH, 1272 ) 1273 .joinAll() 1274 1275 assertThat(screenFlash.lastApplyThreadLooper).isNotNull() 1276 } 1277 1278 @Test 1279 fun mainCaptureRequestSubmitted_whenSubmittedWithScreenFlash() = runTest { 1280 capturePipeline 1281 .submitStillCaptures( 1282 configs = listOf(singleConfig), 1283 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 1284 sessionConfigOptions = MutableOptionsBundle.create(), 1285 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 1286 flashMode = ImageCapture.FLASH_MODE_SCREEN, 1287 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH, 1288 ) 1289 .joinAll() 1290 1291 assertThat(fakeCameraGraphSession.submitSemaphore.tryAcquire(this)).isTrue() 1292 } 1293 1294 @Test 1295 fun screenFlashClearInvoked_whenStillCaptureSubmittedWithScreenFlash() = runTest { 1296 capturePipeline 1297 .submitStillCaptures( 1298 configs = listOf(singleConfig), 1299 requestTemplate = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE), 1300 sessionConfigOptions = MutableOptionsBundle.create(), 1301 captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, 1302 flashMode = ImageCapture.FLASH_MODE_SCREEN, 1303 flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH, 1304 ) 1305 .joinAll() 1306 1307 // submitStillCaptures method does not wait for post-capture to be completed, so need to 1308 // wait a little to ensure it is completed 1309 delay(1000) 1310 1311 assertThat(screenFlash.awaitClear(3000)).isTrue() 1312 } 1313 1314 private fun createCapturePipeline( 1315 useTorchAsFlash: UseTorchAsFlash = NotUseTorchAsFlash, 1316 ) = 1317 CapturePipelineImpl( 1318 configAdapter = fakeCaptureConfigAdapter, 1319 cameraProperties = fakeCameraProperties, 1320 requestListener = comboRequestListener, 1321 threads = fakeUseCaseThreads, 1322 torchControl = torchControl, 1323 useCaseGraphConfig = fakeUseCaseGraphConfig, 1324 useCaseCameraState = fakeUseCaseCameraState, 1325 useTorchAsFlash = useTorchAsFlash, 1326 sessionProcessorManager = null, 1327 flashControl = flashControl, 1328 videoUsageControl = VideoUsageControl(), 1329 ) 1330 1331 // TODO(wenhungteng@): Porting overrideAeModeForStillCapture_quirkAbsent_notOverride, 1332 // overrideAeModeForStillCapture_aePrecaptureStarted_override, 1333 // overrideAeModeForStillCapture_aePrecaptureFinish_notOverride, 1334 // overrideAeModeForStillCapture_noAePrecaptureTriggered_notOverride 1335 1336 private fun List<Request>.complete() { 1337 // Callback capture complete. 1338 forEach { request -> 1339 request.listeners.forEach { 1340 it.onTotalCaptureResult( 1341 requestMetadata = FakeRequestMetadata(), 1342 frameNumber = FrameNumber(100L), 1343 totalCaptureResult = FakeFrameInfo(), 1344 ) 1345 } 1346 } 1347 } 1348 1349 private fun ComboRequestListener.simulate3aConvergence() { 1350 simulateRepeatingResult( 1351 resultParameters = 1352 mapOf( 1353 CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_CONVERGED, 1354 CaptureResult.CONTROL_AF_STATE to CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED, 1355 CaptureResult.CONTROL_AWB_STATE to CaptureResult.CONTROL_AWB_STATE_CONVERGED, 1356 ) 1357 ) 1358 } 1359 1360 private fun ComboRequestListener.simulateRepeatingResult( 1361 initialDelay: Long = 100, 1362 period: Long = 100, // in milliseconds 1363 requestParameters: Map<CaptureRequest.Key<*>, Any> = mutableMapOf(), 1364 resultParameters: Map<CaptureResult.Key<*>, Any> = mutableMapOf(), 1365 onResultSubmitted: (FrameInfo) -> Unit = {}, 1366 ) { 1367 let { listener -> 1368 runningRepeatingJob = 1369 fakeUseCaseThreads.scope.launch { 1370 delay(initialDelay) 1371 1372 // the counter uses 1000 frames for repeating request instead of infinity so 1373 // that 1374 // coroutine can complete and lead to an idle state, should be sufficient for 1375 // all 1376 // our testing purposes here 1377 var counter = 1000 1378 while (counter-- > 0) { 1379 val fakeRequestMetadata = 1380 FakeRequestMetadata(requestParameters = requestParameters) 1381 val fakeFrameMetadata = FakeFrameMetadata(resultMetadata = resultParameters) 1382 val fakeFrameInfo = 1383 FakeFrameInfo( 1384 metadata = fakeFrameMetadata, 1385 requestMetadata = fakeRequestMetadata, 1386 ) 1387 listener.onTotalCaptureResult( 1388 requestMetadata = fakeRequestMetadata, 1389 frameNumber = FrameNumber(101L), 1390 totalCaptureResult = fakeFrameInfo, 1391 ) 1392 onResultSubmitted(fakeFrameInfo) 1393 delay(period) 1394 } 1395 } 1396 } 1397 } 1398 1399 private suspend fun <T> Collection<Deferred<T>>.awaitAllWithTimeout( 1400 timeMillis: Long = TimeUnit.SECONDS.toMillis(5) 1401 ) = 1402 checkNotNull(withTimeoutOrNull(timeMillis) { awaitAll() }) { 1403 "Cannot complete the Deferred within $timeMillis" 1404 } 1405 1406 /** 1407 * Advances TestScope coroutine to idle state (i.e. all tasks completed) before trying to 1408 * acquire semaphore immediately. 1409 * 1410 * This saves time by not having to explicitly wait for a semaphore status to be updated. 1411 */ 1412 private fun Semaphore.tryAcquire(testScope: TestScope): Boolean { 1413 testScope.advanceUntilIdle() 1414 return tryAcquire() 1415 } 1416 } 1417