blob: 3eb4b54f39f20a28b84a2edf5e45f2f1d4056337 [file] [log] [blame]
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.camera.camera2.pipe.integration.impl
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.core.Log.debug
import androidx.camera.camera2.pipe.core.Log.warn
import androidx.camera.camera2.pipe.integration.adapter.awaitUntil
import androidx.camera.camera2.pipe.integration.adapter.propagateTo
import androidx.camera.camera2.pipe.integration.compat.workaround.UseFlashModeTorchFor3aUpdate
import androidx.camera.camera2.pipe.integration.config.CameraScope
import androidx.camera.core.CameraControl
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCapture.ScreenFlash
import androidx.camera.core.ImageCapture.ScreenFlashListener
import androidx.camera.core.impl.CameraControlInternal
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
internal const val DEFAULT_FLASH_MODE = ImageCapture.FLASH_MODE_OFF
/**
* Implementation of Flash control exposed by [CameraControlInternal].
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
@CameraScope
class FlashControl @Inject constructor(
private val cameraProperties: CameraProperties,
private val state3AControl: State3AControl,
private val threads: UseCaseThreads,
private val torchControl: TorchControl,
private val useFlashModeTorchFor3aUpdate: UseFlashModeTorchFor3aUpdate,
) : UseCaseCameraControl {
private var _useCaseCamera: UseCaseCamera? = null
override var useCaseCamera: UseCaseCamera?
get() = _useCaseCamera
set(value) {
_useCaseCamera = value
setFlashAsync(_flashMode, false)
}
override fun reset() {
_flashMode = DEFAULT_FLASH_MODE
_screenFlash = null
threads.sequentialScope.launch {
stopRunningTask()
}
setFlashAsync(DEFAULT_FLASH_MODE)
}
@Volatile
@ImageCapture.FlashMode
private var _flashMode: Int = DEFAULT_FLASH_MODE
@ImageCapture.FlashMode
var flashMode: Int = _flashMode
get() = _flashMode
private set
@Volatile
private var _screenFlash: ScreenFlash? = null
var screenFlash: ScreenFlash? = _screenFlash
get() = _screenFlash
private set
private var _updateSignal: CompletableDeferred<Unit>? = null
var updateSignal: Deferred<Unit> = CompletableDeferred(Unit)
get() = if (_updateSignal != null) {
_updateSignal!!
} else {
CompletableDeferred(Unit)
}
private set
fun setFlashAsync(
@ImageCapture.FlashMode flashMode: Int,
cancelPreviousTask: Boolean = true
): Deferred<Unit> {
val signal = CompletableDeferred<Unit>()
useCaseCamera?.let {
// Update _flashMode immediately so that CameraControlInternal#getFlashMode()
// returns correct value.
_flashMode = flashMode
threads.sequentialScope.launch {
if (cancelPreviousTask) {
stopRunningTask()
} else {
// Propagate the result to the previous updateSignal
_updateSignal?.let { previousUpdateSignal ->
signal.propagateTo(previousUpdateSignal)
}
}
_updateSignal = signal
state3AControl.flashMode = flashMode
state3AControl.updateSignal?.propagateTo(signal) ?: run { signal.complete(Unit) }
}
} ?: run {
signal.completeExceptionally(
CameraControl.OperationCanceledException("Camera is not active.")
)
}
return signal
}
private fun stopRunningTask() {
_updateSignal?.apply {
completeExceptionally(
CameraControl.OperationCanceledException(
"There is a new flash mode being set or camera was closed"
)
)
}
_updateSignal = null
}
fun setScreenFlash(screenFlash: ScreenFlash?) {
_screenFlash = screenFlash
}
suspend fun startScreenFlashCaptureTasks() {
val pendingTasks = mutableListOf<Deferred<Unit>>()
// Invoke ScreenFlash#apply and wait later for its listener to be completed
pendingTasks.add(
applyScreenFlash(
TimeUnit.SECONDS.toMillis(ImageCapture.SCREEN_FLASH_UI_APPLY_TIMEOUT_SECONDS)
)
)
// Try to set external flash AE mode if possible
setExternalFlashAeModeAsync()?.let { pendingTasks.add(it) }
// Set FLASH_MODE_TORCH for quirks
setTorchForScreenFlash()?.let { pendingTasks.add(it) }
pendingTasks.awaitAll()
}
/**
* Invokes [ScreenFlash.apply] immediately and returns a [Deferred] waiting for the
* [ScreenFlashListener] to be completed.
*/
private suspend fun applyScreenFlash(timeoutMillis: Long): Deferred<Unit> {
val onApplyCompletedSignal = CompletableDeferred<Unit>()
val screenFlashListener = ScreenFlashListener {
onApplyCompletedSignal.complete(Unit)
}
withContext(Dispatchers.Main) {
val expirationTimeMillis = System.currentTimeMillis() + timeoutMillis
screenFlash?.apply(
expirationTimeMillis,
screenFlashListener
)
debug {
"applyScreenFlash: ScreenFlash.apply() invoked" +
", expirationTimeMillis = $expirationTimeMillis"
}
}
return threads.scope.async {
debug { "applyScreenFlash: Waiting for ScreenFlashListener to be completed" }
// Wait for ScreenFlashListener#onCompleted to be invoked,
// it's ok to give a little more time than expirationTimeMillis in ScreenFlash#apply
if (onApplyCompletedSignal.awaitUntil(timeoutMillis)) {
debug { "applyScreenFlash: ScreenFlashListener completed" }
} else {
warn { "applyScreenFlash: ScreenFlashListener completion timed out" +
" after $timeoutMillis ms" }
}
}
}
/**
* Tries to set external flash AE mode if possible.
*
* @return A [Deferred] that reports the completion of the operation, `null` if not supported.
*/
private fun setExternalFlashAeModeAsync(): Deferred<Unit>? {
val isExternalFlashAeModeSupported =
cameraProperties.metadata.isExternalFlashAeModeSupported()
debug {
"setExternalFlashAeModeAsync: isExternalFlashAeModeSupported = " +
"$isExternalFlashAeModeSupported"
}
if (!isExternalFlashAeModeSupported) {
return null
}
state3AControl.tryExternalFlashAeMode = true
return state3AControl.updateSignal?.also {
debug {
"setExternalFlashAeModeAsync: need to wait for state3AControl.updateSignal"
}
it.invokeOnCompletion {
debug { "setExternalFlashAeModeAsync: state3AControl.updateSignal completed" }
}
}
}
/**
* Enables the torch mode for screen flash capture when required.
*
* Since this is required due to a device quirk despite lacking physical flash unit, the
* `ignoreFlashUnitAvailability` parameter is set to `true` while invoking
* [TorchControl.setTorchAsync].
*
* @return A [Deferred] that reports the completion of the operation, `null` if not required.
*/
private fun setTorchForScreenFlash(): Deferred<Unit>? {
val shouldUseFlashModeTorch = useFlashModeTorchFor3aUpdate.shouldUseFlashModeTorch()
debug {
"setTorchIfRequired: shouldUseFlashModeTorch = $shouldUseFlashModeTorch"
}
if (!shouldUseFlashModeTorch) {
return null
}
return torchControl.setTorchAsync(torch = true, ignoreFlashUnitAvailability = true).also {
debug {
"setTorchIfRequired: need to wait for torch control to be completed"
}
it.invokeOnCompletion {
debug { "setTorchIfRequired: torch control completed" }
}
}
}
suspend fun stopScreenFlashCaptureTasks() {
withContext(Dispatchers.Main) {
screenFlash?.clear()
debug { "screenFlashPostCapture: ScreenFlash.clear() invoked" }
}
if (cameraProperties.metadata.isExternalFlashAeModeSupported()) {
// Disable external flash AE mode, ok to complete whenever
state3AControl.tryExternalFlashAeMode = false
}
if (useFlashModeTorchFor3aUpdate.shouldUseFlashModeTorch()) {
torchControl.setTorchAsync(torch = false, ignoreFlashUnitAvailability = true)
}
}
@Module
abstract class Bindings {
@Binds
@IntoSet
abstract fun provideControls(flashControl: FlashControl): UseCaseCameraControl
}
}