blob: e7b63a327dba2cfd8bfe81e52bfb17d0e7ce595e [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.
*/
@file:RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
package androidx.camera.camera2.pipe.integration.compat
import androidx.annotation.GuardedBy
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.FrameInfo
import androidx.camera.camera2.pipe.FrameNumber
import androidx.camera.camera2.pipe.Request
import androidx.camera.camera2.pipe.RequestMetadata
import androidx.camera.camera2.pipe.integration.adapter.propagateTo
import androidx.camera.camera2.pipe.integration.config.CameraScope
import androidx.camera.camera2.pipe.integration.impl.Camera2ImplConfig
import androidx.camera.camera2.pipe.integration.impl.UseCaseCamera
import androidx.camera.camera2.pipe.integration.impl.UseCaseCameraRequestControl
import androidx.camera.camera2.pipe.integration.impl.containsTag
import androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions
import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
import androidx.camera.core.CameraControl
import androidx.camera.core.impl.Config
import androidx.camera.core.impl.annotation.ExecutedBy
import dagger.Binds
import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
private const val TAG_KEY = "Camera2CameraControl.tag"
@ExperimentalCamera2Interop
interface Camera2CameraControlCompat : Request.Listener {
fun addRequestOption(bundle: CaptureRequestOptions)
fun getRequestOption(): CaptureRequestOptions
fun clearRequestOption()
fun cancelCurrentTask()
fun applyAsync(camera: UseCaseCamera?, cancelPreviousTask: Boolean = true): Deferred<Void?>
@Module
abstract class Bindings {
@Binds
abstract fun bindCamera2CameraControlCompImpl(
impl: Camera2CameraControlCompatImpl
): Camera2CameraControlCompat
}
}
@CameraScope
@ExperimentalCamera2Interop
class Camera2CameraControlCompatImpl @Inject constructor() : Camera2CameraControlCompat {
private val lock = Any()
private val updateSignalLock = Any()
@GuardedBy("lock")
private var configBuilder = Camera2ImplConfig.Builder()
@GuardedBy("updateSignalLock")
private var updateSignal: CompletableDeferred<Void?>? = null
@GuardedBy("updateSignalLock")
private var pendingSignal: CompletableDeferred<Void?>? = null
override fun addRequestOption(bundle: CaptureRequestOptions) {
synchronized(lock) {
for (option in bundle.listOptions()) {
@Suppress("UNCHECKED_CAST")
val objectOpt = option as Config.Option<Any>
configBuilder.mutableConfig.insertOption(
objectOpt,
Config.OptionPriority.ALWAYS_OVERRIDE,
bundle.retrieveOption(objectOpt)
)
}
}
}
override fun getRequestOption(): CaptureRequestOptions =
synchronized(lock) {
CaptureRequestOptions.Builder.from(
configBuilder.build()
).build()
}
override fun clearRequestOption() {
synchronized(lock) {
configBuilder = Camera2ImplConfig.Builder()
}
}
override fun cancelCurrentTask(): Unit = synchronized(updateSignalLock) {
updateSignal?.also {
updateSignal = null
}?.cancelSignal("The camera control has became inactive.")
pendingSignal?.also {
pendingSignal = null
}?.cancelSignal("The camera control has became inactive.")
}
override fun applyAsync(camera: UseCaseCamera?, cancelPreviousTask: Boolean): Deferred<Void?> {
val signal: CompletableDeferred<Void?> = CompletableDeferred()
val config = synchronized(lock) {
configBuilder.build()
}
synchronized(updateSignalLock) {
if (camera != null) {
if (cancelPreviousTask) {
// Cancel the previous request signal if exist.
updateSignal?.cancelSignal()
} else {
// propagate the result to the previous updateSignal
updateSignal?.let { previousUpdateSignal ->
signal.propagateTo(previousUpdateSignal)
}
}
updateSignal = signal
camera.requestControl.setConfigAsync(
type = UseCaseCameraRequestControl.Type.CAMERA2_CAMERA_CONTROL,
config = config,
tags = mapOf(TAG_KEY to signal.hashCode())
)
} else {
// If there is no camera for the parameter update, the signal would be treated as a
// pending signal, and the pending signal would be completed after the camera
// applied the parameter.
// Cancel the previous request signal if it exists. Only keep the latest signal.
pendingSignal?.cancelSignal()
pendingSignal = signal
}
}
return signal
}
private fun CompletableDeferred<Void?>.cancelSignal(
msg: String = "Camera2CameraControl was updated with new options."
) = this.apply {
completeExceptionally(CameraControl.OperationCanceledException(msg))
}
@ExecutedBy("UseCaseThreads")
override fun onComplete(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
result: FrameInfo
): Unit = synchronized(updateSignalLock) {
updateSignal?.apply {
if (requestMetadata.containsTag(TAG_KEY, hashCode())) {
// Going to complete the [updateSignal] if the result contains the [TAG_KEY]
complete(null)
updateSignal = null
// Also complete the [pendingSignal] if it exists.
pendingSignal?.also {
it.complete(null)
pendingSignal = null
}
}
}
}
}