1 /*
<lambda>null2  * 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 
17 package androidx.camera.camera2.pipe.integration.impl
18 
19 import androidx.camera.camera2.pipe.core.Log.debug
20 import androidx.camera.camera2.pipe.core.Log.warn
21 import androidx.camera.camera2.pipe.integration.adapter.awaitUntil
22 import androidx.camera.camera2.pipe.integration.adapter.propagateTo
23 import androidx.camera.camera2.pipe.integration.compat.workaround.UseFlashModeTorchFor3aUpdate
24 import androidx.camera.camera2.pipe.integration.config.CameraScope
25 import androidx.camera.camera2.pipe.integration.impl.TorchControl.TorchMode
26 import androidx.camera.core.CameraControl
27 import androidx.camera.core.ImageCapture
28 import androidx.camera.core.ImageCapture.ScreenFlash
29 import androidx.camera.core.ImageCapture.ScreenFlashListener
30 import androidx.camera.core.impl.CameraControlInternal
31 import dagger.Binds
32 import dagger.Module
33 import dagger.multibindings.IntoSet
34 import java.util.concurrent.TimeUnit
35 import javax.inject.Inject
36 import kotlinx.coroutines.CompletableDeferred
37 import kotlinx.coroutines.Deferred
38 import kotlinx.coroutines.Dispatchers
39 import kotlinx.coroutines.async
40 import kotlinx.coroutines.awaitAll
41 import kotlinx.coroutines.withContext
42 
43 internal const val DEFAULT_FLASH_MODE = ImageCapture.FLASH_MODE_OFF
44 
45 /** Implementation of Flash control exposed by [CameraControlInternal]. */
46 @CameraScope
47 public class FlashControl
48 @Inject
49 constructor(
50     private val cameraProperties: CameraProperties,
51     private val state3AControl: State3AControl,
52     private val threads: UseCaseThreads,
53     private val torchControl: TorchControl,
54     private val useFlashModeTorchFor3aUpdate: UseFlashModeTorchFor3aUpdate,
55 ) : UseCaseCameraControl {
56     private var _requestControl: UseCaseCameraRequestControl? = null
57     override var requestControl: UseCaseCameraRequestControl?
58         get() = _requestControl
59         set(value) {
60             _requestControl = value
61             setFlashAsync(_flashMode, false)
62         }
63 
64     override fun reset() {
65         _flashMode = DEFAULT_FLASH_MODE
66         _screenFlash = null
67         stopRunningTask()
68         setFlashAsync(DEFAULT_FLASH_MODE)
69     }
70 
71     @Volatile @ImageCapture.FlashMode private var _flashMode: Int = DEFAULT_FLASH_MODE
72 
73     @ImageCapture.FlashMode
74     public var flashMode: Int = _flashMode
75         get() = _flashMode
76         private set
77 
78     @Volatile private var _screenFlash: ScreenFlash? = null
79 
80     public var screenFlash: ScreenFlash? = _screenFlash
81         get() = _screenFlash
82         private set
83 
84     private var _updateSignal: CompletableDeferred<Unit>? = null
85 
86     public var updateSignal: Deferred<Unit> = CompletableDeferred(Unit)
87         get() =
88             if (_updateSignal != null) {
89                 _updateSignal!!
90             } else {
91                 CompletableDeferred(Unit)
92             }
93         private set
94 
95     public fun setFlashAsync(
96         @ImageCapture.FlashMode flashMode: Int,
97         cancelPreviousTask: Boolean = true
98     ): Deferred<Unit> {
99         debug { "setFlashAsync: flashMode = $flashMode, requestControl = $requestControl" }
100         val signal = CompletableDeferred<Unit>()
101 
102         requestControl?.let {
103 
104             // Update _flashMode immediately so that CameraControlInternal#getFlashMode()
105             // returns correct value.
106             _flashMode = flashMode
107 
108             if (cancelPreviousTask) {
109                 stopRunningTask()
110             } else {
111                 // Propagate the result to the previous updateSignal
112                 _updateSignal?.let { previousUpdateSignal ->
113                     signal.propagateTo(previousUpdateSignal)
114                 }
115             }
116 
117             _updateSignal = signal
118             state3AControl.flashMode = flashMode
119             state3AControl.updateSignal?.propagateTo(signal) ?: run { signal.complete(Unit) }
120         }
121             ?: run {
122                 signal.completeExceptionally(
123                     CameraControl.OperationCanceledException("Camera is not active.")
124                 )
125             }
126 
127         return signal
128     }
129 
130     private fun stopRunningTask() {
131         _updateSignal?.apply {
132             completeExceptionally(
133                 CameraControl.OperationCanceledException(
134                     "There is a new flash mode being set or camera was closed"
135                 )
136             )
137         }
138         _updateSignal = null
139     }
140 
141     public fun setScreenFlash(screenFlash: ScreenFlash?) {
142         _screenFlash = screenFlash
143     }
144 
145     public suspend fun startScreenFlashCaptureTasks() {
146         val pendingTasks = mutableListOf<Deferred<Unit>>()
147 
148         // Invoke ScreenFlash#apply and wait later for its listener to be completed
149         pendingTasks.add(
150             applyScreenFlash(
151                 TimeUnit.SECONDS.toMillis(ImageCapture.SCREEN_FLASH_UI_APPLY_TIMEOUT_SECONDS)
152             )
153         )
154 
155         // Try to set external flash AE mode if possible
156         setExternalFlashAeModeAsync()?.let { pendingTasks.add(it) }
157 
158         // Set FLASH_MODE_TORCH for quirks
159         setTorchForScreenFlash()?.let { pendingTasks.add(it) }
160 
161         pendingTasks.awaitAll()
162     }
163 
164     /**
165      * Invokes [ScreenFlash.apply] immediately and returns a [Deferred] waiting for the
166      * [ScreenFlashListener] to be completed.
167      */
168     private suspend fun applyScreenFlash(timeoutMillis: Long): Deferred<Unit> {
169         val onApplyCompletedSignal = CompletableDeferred<Unit>()
170         val screenFlashListener = ScreenFlashListener { onApplyCompletedSignal.complete(Unit) }
171 
172         withContext(Dispatchers.Main) {
173             val expirationTimeMillis = System.currentTimeMillis() + timeoutMillis
174             screenFlash?.apply(expirationTimeMillis, screenFlashListener)
175             debug {
176                 "applyScreenFlash: ScreenFlash.apply() invoked" +
177                     ", expirationTimeMillis = $expirationTimeMillis"
178             }
179         }
180 
181         return threads.scope.async {
182             debug { "applyScreenFlash: Waiting for ScreenFlashListener to be completed" }
183 
184             // Wait for ScreenFlashListener#onCompleted to be invoked,
185             // it's ok to give a little more time than expirationTimeMillis in ScreenFlash#apply
186 
187             if (onApplyCompletedSignal.awaitUntil(timeoutMillis)) {
188                 debug { "applyScreenFlash: ScreenFlashListener completed" }
189             } else {
190                 warn {
191                     "applyScreenFlash: ScreenFlashListener completion timed out" +
192                         " after $timeoutMillis ms"
193                 }
194             }
195         }
196     }
197 
198     /**
199      * Tries to set external flash AE mode if possible.
200      *
201      * @return A [Deferred] that reports the completion of the operation, `null` if not supported.
202      */
203     private fun setExternalFlashAeModeAsync(): Deferred<Unit>? {
204         val isExternalFlashAeModeSupported =
205             cameraProperties.metadata.isExternalFlashAeModeSupported()
206         debug {
207             "setExternalFlashAeModeAsync: isExternalFlashAeModeSupported = " +
208                 "$isExternalFlashAeModeSupported"
209         }
210 
211         if (!isExternalFlashAeModeSupported) {
212             return null
213         }
214 
215         state3AControl.tryExternalFlashAeMode = true
216         return state3AControl.updateSignal?.also {
217             debug { "setExternalFlashAeModeAsync: need to wait for state3AControl.updateSignal" }
218             it.invokeOnCompletion {
219                 debug { "setExternalFlashAeModeAsync: state3AControl.updateSignal completed" }
220             }
221         }
222     }
223 
224     /**
225      * Enables the torch mode for screen flash capture when required.
226      *
227      * Since this is required due to a device quirk despite lacking physical flash unit, the
228      * `ignoreFlashUnitAvailability` parameter is set to `true` while invoking
229      * [TorchControl.setTorchAsync].
230      *
231      * @return A [Deferred] that reports the completion of the operation, `null` if not required.
232      */
233     private fun setTorchForScreenFlash(): Deferred<Unit>? {
234         val shouldUseFlashModeTorch = useFlashModeTorchFor3aUpdate.shouldUseFlashModeTorch()
235         debug { "setTorchIfRequired: shouldUseFlashModeTorch = $shouldUseFlashModeTorch" }
236 
237         if (!shouldUseFlashModeTorch) {
238             return null
239         }
240 
241         return torchControl
242             .setTorchAsync(mode = TorchMode.USED_AS_FLASH, ignoreFlashUnitAvailability = true)
243             .also {
244                 debug { "setTorchIfRequired: need to wait for torch control to be completed" }
245                 it.invokeOnCompletion { debug { "setTorchIfRequired: torch control completed" } }
246             }
247     }
248 
249     public suspend fun stopScreenFlashCaptureTasks() {
250         withContext(Dispatchers.Main) {
251             screenFlash?.clear()
252             debug { "screenFlashPostCapture: ScreenFlash.clear() invoked" }
253         }
254 
255         if (cameraProperties.metadata.isExternalFlashAeModeSupported()) {
256             // Disable external flash AE mode, ok to complete whenever
257             state3AControl.tryExternalFlashAeMode = false
258         }
259 
260         if (useFlashModeTorchFor3aUpdate.shouldUseFlashModeTorch()) {
261             torchControl.setTorchAsync(mode = TorchMode.OFF, ignoreFlashUnitAvailability = true)
262         }
263     }
264 
265     /**
266      * Awaits for flash mode to be updated (if required) and returns the initial flash mode value
267      * i.e. the value for which the waiting was started.
268      */
269     public suspend fun awaitFlashModeUpdate(): Int {
270         debug { "FlashControl: Waiting for any ongoing update to be completed" }
271         // The flash mode may change while waiting for it to be updated, snapshotting it to ensure
272         // the initial flash mode value (for which waiting started) is returned afterwards.
273         val initialFlashMode = flashMode
274         updateSignal.join()
275         debug { "awaitFlashModeUpdate: initialFlashMode = $initialFlashMode" }
276         return initialFlashMode
277     }
278 
279     @Module
280     public abstract class Bindings {
281         @Binds
282         @IntoSet
283         public abstract fun provideControls(flashControl: FlashControl): UseCaseCameraControl
284     }
285 }
286