blob: aba1a20c947645aa30e07781cf31c345e8dc9753 [file] [log] [blame]
WenHung_Tengd87b8b12021-12-03 16:12:13 +08001/*
2 * Copyright 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.camera.camera2.pipe.integration.impl
18
19import android.hardware.camera2.CameraCharacteristics
WenHung_Tengd87b8b12021-12-03 16:12:13 +080020import android.os.Build
WenHung_Tengd87b8b12021-12-03 16:12:13 +080021import androidx.camera.camera2.pipe.Result3A
WenHung_Tengd87b8b12021-12-03 16:12:13 +080022import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
WenHung_Teng2b4753432023-02-22 11:12:17 +080023import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpAutoFlashAEModeDisabler
WenHung_Tengd87b8b12021-12-03 16:12:13 +080024import androidx.camera.camera2.pipe.integration.testing.FakeCameraProperties
25import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCamera
WenHung_Teng77f6d862023-02-09 21:36:39 +080026import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCameraRequestControl
WenHung_Tengd87b8b12021-12-03 16:12:13 +080027import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
28import androidx.camera.core.CameraControl
29import androidx.camera.core.TorchState
WenHung_Tengd87b8b12021-12-03 16:12:13 +080030import androidx.lifecycle.Lifecycle
31import androidx.lifecycle.Observer
32import androidx.lifecycle.testing.TestLifecycleOwner
33import androidx.testutils.assertThrows
34import com.google.common.truth.Truth
WenHung_Teng77f6d862023-02-09 21:36:39 +080035import com.google.common.util.concurrent.MoreExecutors
Tahsin Masrur2f2ca5a2022-11-30 04:09:09 +000036import java.util.Objects
WenHung_Teng77f6d862023-02-09 21:36:39 +080037import java.util.concurrent.TimeUnit
WenHung_Tengd87b8b12021-12-03 16:12:13 +080038import kotlinx.coroutines.CompletableDeferred
39import kotlinx.coroutines.CoroutineScope
40import kotlinx.coroutines.Deferred
41import kotlinx.coroutines.ExperimentalCoroutinesApi
42import kotlinx.coroutines.Job
43import kotlinx.coroutines.asCoroutineDispatcher
44import kotlinx.coroutines.runBlocking
Oleksandr Karpovich7b8b5432021-12-29 11:49:35 +010045import kotlinx.coroutines.test.UnconfinedTestDispatcher
WenHung_Teng77f6d862023-02-09 21:36:39 +080046import kotlinx.coroutines.withTimeout
WenHung_Tengd87b8b12021-12-03 16:12:13 +080047import org.junit.Before
48import org.junit.Test
49import org.junit.runner.RunWith
50import org.robolectric.annotation.Config
51import org.robolectric.annotation.internal.DoNotInstrument
WenHung_Tengd87b8b12021-12-03 16:12:13 +080052
53@RunWith(RobolectricCameraPipeTestRunner::class)
54@DoNotInstrument
55@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
56@OptIn(ExperimentalCoroutinesApi::class)
57class TorchControlTest {
58
59 companion object {
WenHung_Teng77f6d862023-02-09 21:36:39 +080060 private val executor = MoreExecutors.directExecutor()
WenHung_Tengd87b8b12021-12-03 16:12:13 +080061 private val fakeUseCaseThreads by lazy {
62 val dispatcher = executor.asCoroutineDispatcher()
63 val cameraScope = CoroutineScope(Job() + dispatcher)
64
65 UseCaseThreads(
66 cameraScope,
67 executor,
68 dispatcher
69 )
70 }
WenHung_Tengd87b8b12021-12-03 16:12:13 +080071 }
72
73 private val metadata = FakeCameraMetadata(
74 mapOf(
75 CameraCharacteristics.FLASH_INFO_AVAILABLE to true,
76 ),
77 )
78
WenHung_Teng77f6d862023-02-09 21:36:39 +080079 private val neverCompleteTorchRequestControl = FakeUseCaseCameraRequestControl().apply {
80 // Set a CompletableDeferred without set it to completed.
81 setTorchResult = CompletableDeferred()
WenHung_Tengd87b8b12021-12-03 16:12:13 +080082 }
83
84 private lateinit var torchControl: TorchControl
85
86 @Before
87 fun setUp() {
WenHung_Tengecad8bb2023-01-18 14:40:17 +080088 val fakeUseCaseCamera = FakeUseCaseCamera()
89 val fakeCameraProperties = FakeCameraProperties(metadata)
WenHung_Tengd87b8b12021-12-03 16:12:13 +080090 torchControl = TorchControl(
WenHung_Tengecad8bb2023-01-18 14:40:17 +080091 fakeCameraProperties,
WenHung_Teng2b4753432023-02-22 11:12:17 +080092 State3AControl(
93 fakeCameraProperties,
94 NoOpAutoFlashAEModeDisabler,
95 ).apply {
WenHung_Tengecad8bb2023-01-18 14:40:17 +080096 useCaseCamera = fakeUseCaseCamera
97 },
WenHung_Tengd87b8b12021-12-03 16:12:13 +080098 fakeUseCaseThreads,
99 )
WenHung_Tengecad8bb2023-01-18 14:40:17 +0800100 torchControl.useCaseCamera = fakeUseCaseCamera
WenHung_Tengd87b8b12021-12-03 16:12:13 +0800101 }
102
103 @Test
104 fun enableTorch_whenNoFlashUnit(): Unit = runBlocking {
105 assertThrows<IllegalStateException> {
WenHung_Tengecad8bb2023-01-18 14:40:17 +0800106 val fakeUseCaseCamera = FakeUseCaseCamera()
107 val fakeCameraProperties = FakeCameraProperties()
108
WenHung_Tengd87b8b12021-12-03 16:12:13 +0800109 // Without a flash unit, this Job will complete immediately with a IllegalStateException
110 TorchControl(
WenHung_Tengecad8bb2023-01-18 14:40:17 +0800111 fakeCameraProperties,
WenHung_Teng2b4753432023-02-22 11:12:17 +0800112 State3AControl(fakeCameraProperties, NoOpAutoFlashAEModeDisabler).apply {
WenHung_Tengecad8bb2023-01-18 14:40:17 +0800113 useCaseCamera = fakeUseCaseCamera
114 },
WenHung_Tengd87b8b12021-12-03 16:12:13 +0800115 fakeUseCaseThreads,
116 ).also {
WenHung_Tengecad8bb2023-01-18 14:40:17 +0800117 it.useCaseCamera = fakeUseCaseCamera
WenHung_Tengd87b8b12021-12-03 16:12:13 +0800118 }.setTorchAsync(true).await()
119 }
120 }
121
122 @Test
123 fun getTorchState_whenNoFlashUnit() {
WenHung_Tengecad8bb2023-01-18 14:40:17 +0800124 val fakeUseCaseCamera = FakeUseCaseCamera()
125 val fakeCameraProperties = FakeCameraProperties()
126
WenHung_Tengd87b8b12021-12-03 16:12:13 +0800127 val torchState = TorchControl(
WenHung_Tengecad8bb2023-01-18 14:40:17 +0800128 fakeCameraProperties,
WenHung_Teng2b4753432023-02-22 11:12:17 +0800129 State3AControl(fakeCameraProperties, NoOpAutoFlashAEModeDisabler).apply {
130
WenHung_Tengecad8bb2023-01-18 14:40:17 +0800131 useCaseCamera = fakeUseCaseCamera
132 },
WenHung_Tengd87b8b12021-12-03 16:12:13 +0800133 fakeUseCaseThreads,
134 ).also {
WenHung_Tengecad8bb2023-01-18 14:40:17 +0800135 it.useCaseCamera = fakeUseCaseCamera
WenHung_Tengd87b8b12021-12-03 16:12:13 +0800136 }.torchStateLiveData.value
137
138 Truth.assertThat(torchState).isEqualTo(TorchState.OFF)
139 }
140
141 @Test
142 fun enableTorch_whenInactive(): Unit = runBlocking {
143 assertThrows<CameraControl.OperationCanceledException> {
WenHung_Tengecad8bb2023-01-18 14:40:17 +0800144 val fakeUseCaseCamera = FakeUseCaseCamera()
145 val fakeCameraProperties = FakeCameraProperties(metadata)
146
WenHung_Tengd87b8b12021-12-03 16:12:13 +0800147 TorchControl(
WenHung_Tengecad8bb2023-01-18 14:40:17 +0800148 fakeCameraProperties,
WenHung_Teng2b4753432023-02-22 11:12:17 +0800149 State3AControl(fakeCameraProperties, NoOpAutoFlashAEModeDisabler).apply {
WenHung_Tengecad8bb2023-01-18 14:40:17 +0800150 useCaseCamera = fakeUseCaseCamera
151 },
WenHung_Tengd87b8b12021-12-03 16:12:13 +0800152 fakeUseCaseThreads,
153 ).setTorchAsync(true).await()
154 }
155 }
156
157 @Test
158 fun getTorchState_whenInactive() {
159 torchControl.useCaseCamera = null
160 Truth.assertThat(torchControl.torchStateLiveData.value).isEqualTo(TorchState.OFF)
161 }
162
163 @Test
164 fun enableTorch_torchStateOn(): Unit = runBlocking {
165 torchControl.setTorchAsync(true)
166 // LiveData is updated synchronously. Don't need to wait for the result of the setTorchAsync
167 Truth.assertThat(torchControl.torchStateLiveData.value).isEqualTo(TorchState.ON)
168 }
169
170 @Test
171 fun disableTorch_TorchStateOff() {
172 torchControl.setTorchAsync(true)
173 // LiveData is updated synchronously. Don't need to wait for the result of the setTorchAsync
174 val firstTorchState = Objects.requireNonNull<Int>(torchControl.torchStateLiveData.value)
175 torchControl.setTorchAsync(false)
176 // LiveData is updated synchronously. Don't need to wait for the result of the setTorchAsync
177 val secondTorchState = torchControl.torchStateLiveData.value
178 Truth.assertThat(firstTorchState).isEqualTo(TorchState.ON)
179 Truth.assertThat(secondTorchState).isEqualTo(TorchState.OFF)
180 }
181
182 @Test
183 fun enableDisableTorch_futureWillCompleteSuccessfully(): Unit = runBlocking {
184 // Job should be completed without exception
185 torchControl.setTorchAsync(true).await()
186
187 // Job should be completed without exception
188 torchControl.setTorchAsync(false).await()
189 }
190
191 @Test
192 fun enableTorchTwice_cancelPreviousFuture(): Unit = runBlocking {
193 val deferred = torchControl.also {
WenHung_Teng81496032021-12-23 15:47:21 +0800194 it.useCaseCamera = FakeUseCaseCamera(requestControl = neverCompleteTorchRequestControl)
WenHung_Tengd87b8b12021-12-03 16:12:13 +0800195 }.setTorchAsync(true)
196
197 torchControl.setTorchAsync(true)
198
199 assertThrows<CameraControl.OperationCanceledException> {
200 deferred.await()
201 }
202 }
203
204 @Test
205 fun setInActive_cancelPreviousFuture(): Unit = runBlocking {
206 val deferred = torchControl.also {
WenHung_Teng81496032021-12-23 15:47:21 +0800207 it.useCaseCamera = FakeUseCaseCamera(requestControl = neverCompleteTorchRequestControl)
WenHung_Tengd87b8b12021-12-03 16:12:13 +0800208 }.setTorchAsync(true)
209
210 // reset() will be called after all the UseCases are detached.
211 torchControl.reset()
212
213 assertThrows<CameraControl.OperationCanceledException> {
214 deferred.await()
215 }
216 }
217
218 @Test
219 fun setInActiveWhenTorchOn_changeToTorchOff() {
220 torchControl.setTorchAsync(true)
221 val initialTorchState = torchControl.torchStateLiveData.value
222
223 // reset() will be called after all the UseCases are detached.
224 torchControl.reset()
225
226 val torchStateAfterInactive = torchControl.torchStateLiveData.value
227 Truth.assertThat(initialTorchState).isEqualTo(TorchState.ON)
228 Truth.assertThat(torchStateAfterInactive).isEqualTo(TorchState.OFF)
229 }
230
231 @Test
232 fun enableDisableTorch_observeTorchStateLiveData() {
233 val receivedTorchState = mutableListOf<Int?>()
234 // The observer should be notified of initial state
235 torchControl.torchStateLiveData.observe(
236 TestLifecycleOwner(
237 Lifecycle.State.STARTED,
Oleksandr Karpovich7b8b5432021-12-29 11:49:35 +0100238 UnconfinedTestDispatcher()
WenHung_Tengd87b8b12021-12-03 16:12:13 +0800239 ), object : Observer<Int?> {
240 private var mValue: Int? = null
241 override fun onChanged(value: Int?) {
242 if (mValue != value) {
243 mValue = value
244 receivedTorchState.add(value)
245 }
246 }
247 })
248 torchControl.setTorchAsync(true)
249 torchControl.setTorchAsync(false)
250 Truth.assertThat(receivedTorchState[0]).isEqualTo(TorchState.OFF) // initial state
251 Truth.assertThat(receivedTorchState[1]).isEqualTo(TorchState.ON) // by setTorchAsync(true)
252 Truth.assertThat(receivedTorchState[2]).isEqualTo(TorchState.OFF) // by setTorchAsync(false)
253 }
WenHung_Teng77f6d862023-02-09 21:36:39 +0800254
255 @Test
256 fun useCaseCameraUpdated_setTorchResultShouldPropagate(): Unit = runBlocking {
257 // Arrange.
258 torchControl.useCaseCamera =
259 FakeUseCaseCamera(requestControl = neverCompleteTorchRequestControl)
260
261 val deferred = torchControl.setTorchAsync(true)
262 val fakeRequestControl = FakeUseCaseCameraRequestControl().apply {
263 setTorchResult = CompletableDeferred<Result3A>()
264 }
265 val fakeUseCaseCamera = FakeUseCaseCamera(requestControl = fakeRequestControl)
266
267 // Act. Simulate the UseCaseCamera is recreated.
268 torchControl.useCaseCamera = fakeUseCaseCamera
269
270 // Simulate setTorch is completed in the recreated UseCaseCamera
271 fakeRequestControl.setTorchResult.complete(Result3A(status = Result3A.Status.OK))
272
273 // Assert. The setTorch task should be completed.
274 Truth.assertThat(deferred.awaitWithTimeout()).isNotNull()
275 }
276
277 @Test
278 fun useCaseCameraUpdated_onlyCompleteLatestRequest(): Unit = runBlocking {
279 // Arrange.
280 torchControl.useCaseCamera =
281 FakeUseCaseCamera(requestControl = neverCompleteTorchRequestControl)
282
283 val deferred = torchControl.setTorchAsync(true)
284 val fakeRequestControl = FakeUseCaseCameraRequestControl().apply {
285 setTorchResult = CompletableDeferred()
286 }
287 val fakeUseCaseCamera = FakeUseCaseCamera(requestControl = fakeRequestControl)
288
289 // Act. Simulate the UseCaseCamera is recreated.
290 torchControl.useCaseCamera = fakeUseCaseCamera
291 // Act. Set Torch mode again.
292 val deferred2 = torchControl.setTorchAsync(false)
293 // Simulate setTorch is completed in the recreated UseCaseCamera
294 fakeRequestControl.setTorchResult.complete(Result3A(status = Result3A.Status.OK))
295
296 // Assert. The previous setTorch task should be cancelled
297 assertThrows<CameraControl.OperationCanceledException> {
298 deferred.awaitWithTimeout()
299 }
300 // Assert. The latest setTorch task should be completed.
301 Truth.assertThat(deferred2.awaitWithTimeout()).isNotNull()
302 }
303
304 private suspend fun <T> Deferred<T>.awaitWithTimeout(
305 timeMillis: Long = TimeUnit.SECONDS.toMillis(5)
306 ) = withTimeout(timeMillis) {
307 await()
308 }
Tahsin Masrur2f2ca5a2022-11-30 04:09:09 +0000309}