blob: 1699de3cb3be02b541a0352325c0d3c4d4c67ca6 [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 android.hardware.camera2.CaptureRequest
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.integration.adapter.propagateTo
import androidx.camera.camera2.pipe.integration.compat.workaround.isFlashAvailable
import androidx.camera.camera2.pipe.integration.config.CameraScope
import androidx.camera.core.CameraControl
import androidx.camera.core.TorchState
import androidx.camera.core.impl.CameraControlInternal
import androidx.camera.core.impl.utils.Threads
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
import javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.launch
/**
* Implementation of Torch control exposed by [CameraControlInternal].
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
@CameraScope
class TorchControl @Inject constructor(
cameraProperties: CameraProperties,
private val state3AControl: State3AControl,
private val threads: UseCaseThreads,
) : UseCaseCameraControl {
private var _useCaseCamera: UseCaseCamera? = null
override var useCaseCamera: UseCaseCamera?
get() = _useCaseCamera
set(value) {
_useCaseCamera = value
setTorchAsync(
torch = when (torchStateLiveData.value) {
TorchState.ON -> true
else -> false
},
cancelPreviousTask = false,
)
}
override fun reset() {
_torchState.setLiveDataValue(false)
threads.sequentialScope.launch {
stopRunningTaskInternal()
}
setTorchAsync(false)
}
private val hasFlashUnit: Boolean = cameraProperties.isFlashAvailable()
private val _torchState = MutableLiveData(TorchState.OFF)
val torchStateLiveData: LiveData<Int>
get() = _torchState
private var _updateSignal: CompletableDeferred<Unit>? = null
/**
* Turn the torch on or off.
*
* @param torch Whether the torch should be on or off.
* @param cancelPreviousTask Whether to cancel the previous task if it's running.
* @param ignoreFlashUnitAvailability Whether to ignore the flash unit availability. When true,
* torch mode setting will be attempted even if a physical flash unit is not available.
*/
fun setTorchAsync(
torch: Boolean,
cancelPreviousTask: Boolean = true,
ignoreFlashUnitAvailability: Boolean = false
): Deferred<Unit> {
val signal = CompletableDeferred<Unit>()
if (!ignoreFlashUnitAvailability && !hasFlashUnit) {
return signal.createFailureResult(IllegalStateException("No flash unit"))
}
useCaseCamera?.let { useCaseCamera ->
_torchState.setLiveDataValue(torch)
threads.sequentialScope.launch {
if (cancelPreviousTask) {
stopRunningTaskInternal()
} else {
// Propagate the result to the previous updateSignal
_updateSignal?.let { previousUpdateSignal ->
signal.propagateTo(previousUpdateSignal)
}
}
_updateSignal = signal
// TODO(b/209757083), handle the failed result of the setTorchAsync().
useCaseCamera.requestControl.setTorchAsync(torch).join()
// Hold the internal AE mode to ON while the torch is turned ON.
state3AControl.preferredAeMode =
if (torch) CaptureRequest.CONTROL_AE_MODE_ON else null
// Always update3A again to reset the AE state in the Camera-pipe controller.
state3AControl.invalidate()
state3AControl.updateSignal?.propagateTo(signal) ?: run { signal.complete(Unit) }
}
} ?: run {
signal.createFailureResult(
CameraControl.OperationCanceledException("Camera is not active.")
)
}
return signal
}
private fun stopRunningTaskInternal() {
_updateSignal?.createFailureResult(
CameraControl.OperationCanceledException(
"There is a new enableTorch being set"
)
)
_updateSignal = null
}
private fun CompletableDeferred<Unit>.createFailureResult(exception: Exception) = apply {
completeExceptionally(exception)
}
private fun MutableLiveData<Int>.setLiveDataValue(enableTorch: Boolean) = when (enableTorch) {
true -> TorchState.ON
false -> TorchState.OFF
}.let { torchState ->
if (Threads.isMainThread()) {
this.value = torchState
} else {
this.postValue(torchState)
}
}
@Module
abstract class Bindings {
@Binds
@IntoSet
abstract fun provideControls(torchControl: TorchControl): UseCaseCameraControl
}
}