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.camera2.internal
18 
19 import android.hardware.camera2.CameraCharacteristics
20 import android.hardware.camera2.CaptureRequest
21 import android.hardware.camera2.CaptureResult
22 import android.os.Looper.getMainLooper
23 import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat
24 import androidx.camera.camera2.internal.compat.quirk.TorchFlashRequiredFor3aUpdateQuirk
25 import androidx.camera.camera2.internal.compat.workaround.UseFlashModeTorchFor3aUpdate
26 import androidx.camera.core.ImageCapture
27 import androidx.camera.core.ImageCapture.ScreenFlash
28 import androidx.camera.core.impl.CameraControlInternal
29 import androidx.camera.core.impl.CaptureConfig
30 import androidx.camera.core.impl.Quirk
31 import androidx.camera.core.impl.Quirks
32 import androidx.camera.testing.impl.mocks.MockScreenFlash
33 import androidx.testutils.assertThrows
34 import com.google.common.truth.Truth.assertThat
35 import com.google.common.util.concurrent.MoreExecutors
36 import java.util.concurrent.Executors
37 import java.util.concurrent.Future
38 import java.util.concurrent.TimeUnit
39 import java.util.concurrent.TimeoutException
40 import org.junit.After
41 import org.junit.Test
42 import org.junit.runner.RunWith
43 import org.robolectric.RobolectricTestRunner
44 import org.robolectric.Shadows.shadowOf
45 import org.robolectric.annotation.Config
46 import org.robolectric.annotation.internal.DoNotInstrument
47 import org.robolectric.shadow.api.Shadow
48 import org.robolectric.shadows.ShadowCameraCharacteristics
49 import org.robolectric.shadows.ShadowTotalCaptureResult
50 
51 @Config(minSdk = 21)
52 @RunWith(RobolectricTestRunner::class)
53 @DoNotInstrument
54 class ScreenFlashTaskTest {
55     private val executorService = Executors.newSingleThreadScheduledExecutor()
56 
57     private var cameraCharacteristics = createCameraCharacteristicsCompat()
58     private val screenFlash = MockScreenFlash()
59 
60     private lateinit var cameraControl: FakeCamera2CameraControlImpl
61 
62     @After
63     fun tearDown() {
64         executorService.shutdown()
65     }
66 
67     // 3s timeout is hardcoded in ImageCapture.ScreenFlash.apply documentation
68     @Test
69     fun screenFlashApplyInvokedWithAtLeast3sTimeout_whenPreCaptureCalled() {
70         val screenFlashTask = createScreenFlashTask()
71         val initialTime = System.currentTimeMillis()
72 
73         screenFlashTask.preCapture(null)
74         shadowOf(getMainLooper()).idle()
75 
76         assertThat(screenFlash.lastApplyExpirationTimeMillis)
77             .isAtLeast(initialTime + TimeUnit.SECONDS.toMillis(3))
78     }
79 
80     @Test
81     fun screenFlashApplyInvokedWithLessThan4sTimeout_whenPreCaptureCalled() {
82         val screenFlashTask = createScreenFlashTask()
83         val initialTime = System.currentTimeMillis()
84 
85         screenFlashTask.preCapture(null)
86         shadowOf(getMainLooper()).idle()
87 
88         assertThat(screenFlash.lastApplyExpirationTimeMillis)
89             .isLessThan(initialTime + TimeUnit.SECONDS.toMillis(4))
90     }
91 
92     @Test
93     fun screenFlashApplyInvokedInMainThread_whenPreCaptureCalled() {
94         val screenFlashTask = createScreenFlashTask()
95 
96         screenFlashTask.preCapture(null)
97         shadowOf(getMainLooper()).idle()
98 
99         assertThat(screenFlash.lastApplyThreadLooper).isEqualTo(getMainLooper())
100     }
101 
102     @Config(minSdk = 28)
103     @Test
104     fun externalFlashAeModeEnabled_whenPreCaptureCalled() {
105         cameraCharacteristics = createCameraCharacteristicsCompat(addExternalFlashAeMode = true)
106         val screenFlashTask = createScreenFlashTask()
107 
108         screenFlashTask.preCapture(null)
109         shadowOf(getMainLooper()).idle() // ScreenFlash#apply is invoked in main thread
110 
111         assertThat(cameraControl.focusMeteringControl.externalFlashAeModeEnabled).isEqualTo(true)
112     }
113 
114     @Config(minSdk = 28)
115     @Test
116     fun externalFlashAeModeEnabled_whenScreenFlashApplyNotCompleted() {
117         cameraCharacteristics = createCameraCharacteristicsCompat(addExternalFlashAeMode = true)
118         val screenFlashTask = createScreenFlashTask()
119         screenFlash.setApplyCompletedInstantly(false)
120 
121         screenFlashTask.preCapture(null)
122         shadowOf(getMainLooper()).idle() // ScreenFlash#apply is invoked in main thread
123 
124         assertThat(cameraControl.focusMeteringControl.externalFlashAeModeEnabled).isEqualTo(true)
125     }
126 
127     @Test
128     fun torchNotEnabled_whenPreCaptureCalledWithoutQuirk() {
129         val screenFlashTask = createScreenFlashTask(addTorchFlashRequiredQuirk = false)
130 
131         screenFlashTask.preCapture(null)
132         shadowOf(getMainLooper()).idle() // ScreenFlash#apply is invoked in main thread
133 
134         assertThat(cameraControl.isTorchEnabled).isFalse()
135     }
136 
137     @Config(minSdk = 28)
138     @Test
139     fun torchNotEnabled_whenPreCaptureCalledWithQuirk_butExternalFlashAeModeSupported() {
140         cameraCharacteristics = createCameraCharacteristicsCompat(addExternalFlashAeMode = true)
141         val screenFlashTask = createScreenFlashTask(addTorchFlashRequiredQuirk = true)
142 
143         screenFlashTask.preCapture(null)
144         shadowOf(getMainLooper()).idle() // ScreenFlash#apply is invoked in main thread
145 
146         assertThat(cameraControl.isTorchEnabled).isFalse()
147     }
148 
149     @Test
150     fun torchEnabled_whenPreCaptureCalledWithQuirk_andNoExternalFlashAeMode() {
151         cameraCharacteristics = createCameraCharacteristicsCompat(addExternalFlashAeMode = false)
152         val screenFlashTask = createScreenFlashTask(addTorchFlashRequiredQuirk = true)
153 
154         screenFlashTask.preCapture(null)
155         shadowOf(getMainLooper()).idle() // ScreenFlash#apply is invoked in main thread
156 
157         assertThat(cameraControl.isTorchEnabled).isTrue()
158     }
159 
160     @Test
161     fun torchEnabled_whenScreenFlashApplyNotCompleted() {
162         cameraCharacteristics = createCameraCharacteristicsCompat(addExternalFlashAeMode = false)
163         val screenFlashTask = createScreenFlashTask(addTorchFlashRequiredQuirk = true)
164         screenFlash.setApplyCompletedInstantly(false)
165 
166         screenFlashTask.preCapture(null)
167         shadowOf(getMainLooper()).idle() // ScreenFlash#apply is invoked in main thread
168 
169         assertThat(cameraControl.isTorchEnabled).isTrue()
170     }
171 
172     @Test
173     fun aePrecaptureTriggered_whenPreCaptureCalled() {
174         val screenFlashTask = createScreenFlashTask()
175 
176         screenFlashTask.preCapture(null)
177         shadowOf(getMainLooper()).idle() // ScreenFlash#apply is invoked in main thread
178 
179         assertThat(cameraControl.focusMeteringControl.triggerAePrecaptureCount).isEqualTo(1)
180     }
181 
182     @Test
183     fun aePrecaptureNotTriggeredUntilTimeout_whenScreenFlashApplyNotCompleted() {
184         val screenFlashTask = createScreenFlashTask()
185         screenFlash.setApplyCompletedInstantly(false)
186 
187         screenFlashTask.preCapture(null)
188         shadowOf(getMainLooper()).idle() // ScreenFlash#apply is invoked in main thread
189 
190         cameraControl.focusMeteringControl.awaitTriggerAePrecapture(1000)
191     }
192 
193     @Test
194     fun aePrecaptureTriggeredAfterTimeout_whenScreenFlashApplyNotCompleted() {
195         val screenFlashTask = createScreenFlashTask()
196         screenFlash.setApplyCompletedInstantly(false)
197 
198         screenFlashTask.preCapture(null)
199         shadowOf(getMainLooper()).idle() // ScreenFlash#apply is invoked in main thread
200 
201         cameraControl.focusMeteringControl.awaitTriggerAePrecapture(
202             TimeUnit.SECONDS.toMillis(ImageCapture.SCREEN_FLASH_UI_APPLY_TIMEOUT_SECONDS + 1)
203         )
204     }
205 
206     @Test
207     fun preCaptureIncompleteUntilTimeout_without3aConverge() {
208         val screenFlashTask = createScreenFlashTask()
209 
210         val future = screenFlashTask.preCapture(null)
211         shadowOf(getMainLooper()).idle() // ScreenFlash#apply is invoked in main thread
212 
213         future.awaitException(1000, TimeoutException::class.java)
214     }
215 
216     @Test
217     fun preCaptureCompletes_when3aConverges() {
218         val screenFlashTask = createScreenFlashTask()
219 
220         val future = screenFlashTask.preCapture(null)
221         shadowOf(getMainLooper()).idle() // ScreenFlash#apply is invoked in main thread
222 
223         cameraControl.notifyCaptureResultListeners(RESULT_CONVERGED)
224 
225         future.await(1000)
226     }
227 
228     @Test
229     fun preCaptureCompletesByTimeout_without3aConverge() {
230         val screenFlashTask = createScreenFlashTask()
231 
232         val future = screenFlashTask.preCapture(null)
233         shadowOf(getMainLooper()).idle() // ScreenFlash#apply is invoked in main thread
234 
235         future.await(3000)
236     }
237 
238     @Test
239     fun screenFlashClearInvokedInMainThread_whenPostCaptureCalled() {
240         val screenFlashTask = createScreenFlashTask()
241 
242         screenFlashTask.postCapture()
243         shadowOf(getMainLooper()).idle()
244 
245         assertThat(screenFlash.lastClearThreadLooper).isEqualTo(getMainLooper())
246     }
247 
248     @Test
249     fun torchDisabled_whenPostCaptureCalledWithQuirk_andNoExternalFlashAeMode() {
250         cameraCharacteristics = createCameraCharacteristicsCompat(addExternalFlashAeMode = false)
251         val screenFlashTask = createScreenFlashTask(addTorchFlashRequiredQuirk = true)
252         screenFlashTask.preCapture(null)
253 
254         screenFlashTask.postCapture()
255 
256         assertThat(cameraControl.isTorchEnabled).isFalse()
257     }
258 
259     @Config(minSdk = 28)
260     @Test
261     fun externalFlashAeModeDisabled_whenPostCaptureCalled() {
262         cameraCharacteristics = createCameraCharacteristicsCompat(addExternalFlashAeMode = true)
263         val screenFlashTask = createScreenFlashTask()
264         screenFlashTask.preCapture(null)
265 
266         screenFlashTask.postCapture()
267 
268         assertThat(cameraControl.focusMeteringControl.externalFlashAeModeEnabled).isEqualTo(false)
269     }
270 
271     @Test
272     fun afAeTriggerCancelled_whenPostCaptureCalled() {
273         val screenFlashTask = createScreenFlashTask()
274 
275         screenFlashTask.postCapture()
276         shadowOf(getMainLooper()).idle()
277 
278         assertThat(cameraControl.focusMeteringControl.cancelAfAeTriggerCount).isEqualTo(1)
279     }
280 
281     private fun createScreenFlashTask(
282         addTorchFlashRequiredQuirk: Boolean = false
283     ): Camera2CapturePipeline.ScreenFlashTask {
284         val quirks =
285             Quirks(
286                 mutableListOf<Quirk>().apply {
287                     if (addTorchFlashRequiredQuirk) {
288                         add(TorchFlashRequiredFor3aUpdateQuirk(cameraCharacteristics))
289                     }
290                 }
291             )
292 
293         cameraControl = FakeCamera2CameraControlImpl(cameraCharacteristics, quirks, screenFlash)
294         return Camera2CapturePipeline.ScreenFlashTask(
295             cameraControl,
296             MoreExecutors.directExecutor(),
297             executorService,
298             UseFlashModeTorchFor3aUpdate(quirks),
299         )
300     }
301 
302     private fun createCameraCharacteristicsCompat(
303         addExternalFlashAeMode: Boolean = false,
304     ) =
305         CameraCharacteristicsCompat.toCameraCharacteristicsCompat(
306             ShadowCameraCharacteristics.newCameraCharacteristics().also {
307                 Shadow.extract<ShadowCameraCharacteristics>(it).apply {
308                     if (addExternalFlashAeMode) {
309                         set(
310                             CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES,
311                             intArrayOf(CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH)
312                         )
313                     }
314                 }
315             },
316             CAMERA_ID
317         )
318 
319     internal inner class FakeCamera2CameraControlImpl(
320         cameraCharacteristics: CameraCharacteristicsCompat = createCameraCharacteristicsCompat(),
321         quirks: Quirks,
322         private val screenFlash: ScreenFlash,
323     ) :
324         Camera2CameraControlImpl(
325             cameraCharacteristics,
326             executorService,
327             MoreExecutors.directExecutor(),
328             object : CameraControlInternal.ControlUpdateCallback {
329                 override fun onCameraControlUpdateSessionConfig() {}
330 
331                 override fun onCameraControlCaptureRequests(
332                     captureConfigs: MutableList<CaptureConfig>
333                 ) {}
334             }
335         ) {
336         private lateinit var captureResultListeners: MutableList<CaptureResultListener>
337         var isTorchEnabled = false
338 
339         private val focusMeteringControl = FakeFocusMeteringControl(this, quirks)
340 
341         init {
342             // can be called from super class constructor
343             if (!::captureResultListeners.isInitialized) {
344                 captureResultListeners = mutableListOf()
345             }
346         }
347 
348         override fun getFocusMeteringControl(): FakeFocusMeteringControl {
349             return focusMeteringControl
350         }
351 
352         override fun getScreenFlash(): ScreenFlash {
353             return screenFlash
354         }
355 
356         override fun enableTorchInternal(torchState: Int) {
357             isTorchEnabled = torchState != TorchControl.OFF
358         }
359 
360         override fun addCaptureResultListener(listener: CaptureResultListener) {
361             // can be called from super class constructor
362             if (!::captureResultListeners.isInitialized) {
363                 captureResultListeners = mutableListOf()
364             }
365             captureResultListeners.add(listener)
366         }
367 
368         @Suppress("UNCHECKED_CAST")
369         fun notifyCaptureResultListeners(resultParameters: Map<CaptureResult.Key<*>, *>) {
370             captureResultListeners.forEach { listener ->
371                 val shadowCaptureResult = ShadowTotalCaptureResult()
372 
373                 resultParameters.forEach { (k, v) ->
374                     shadowCaptureResult.set(k as CaptureResult.Key<Any>, v as Any)
375                 }
376 
377                 listener.onCaptureResult(ShadowTotalCaptureResult.newTotalCaptureResult())
378             }
379         }
380     }
381 
382     private fun Future<*>.await(timeoutMillis: Long) = get(timeoutMillis, TimeUnit.MILLISECONDS)
383 
384     private fun <T : Throwable?> Future<*>.awaitException(
385         timeoutMillis: Long,
386         exceptionType: Class<T>
387     ) {
388         assertThrows(exceptionType) { get(timeoutMillis, TimeUnit.MILLISECONDS) }
389     }
390 
391     companion object {
392         private const val CAMERA_ID = "0"
393 
394         private val RESULT_CONVERGED: Map<CaptureResult.Key<*>, *> =
395             mapOf(
396                 CaptureResult.CONTROL_AF_MODE to CaptureResult.CONTROL_AF_MODE_AUTO,
397                 CaptureResult.CONTROL_AF_STATE to CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED,
398                 CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_CONVERGED,
399                 CaptureResult.CONTROL_AWB_STATE to CaptureResult.CONTROL_AWB_STATE_CONVERGED,
400             )
401     }
402 }
403