ScreenFlashWrapper.kt
/*
* Copyright 2023 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.core.internal
import androidx.annotation.GuardedBy
import androidx.annotation.RequiresApi
import androidx.camera.core.ImageCapture.ScreenFlash
import androidx.camera.core.ImageCapture.ScreenFlashListener
import androidx.camera.core.Logger
/**
* Wrapper class around [ScreenFlash] to save the [ScreenFlashListener] passed to app.
*
* This allows us to clean up properly in case a capture is cancelled earlier (e.g. ImageCapture is
* unbound after [apply] is invoked but [clear] is not).
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
class ScreenFlashWrapper private constructor(
private val screenFlash: ScreenFlash?
) : ScreenFlash {
private val lock = Object()
@GuardedBy("lock")
private var isClearScreenFlashPending: Boolean = false
@GuardedBy("lock")
private var pendingListener: ScreenFlashListener? = null
companion object {
private const val TAG = "ScreenFlashWrapper"
@JvmStatic
fun from(screenFlash: ScreenFlash?) = ScreenFlashWrapper(screenFlash)
}
override fun apply(expirationTimeMillis: Long, screenFlashListener: ScreenFlashListener) {
synchronized(lock) {
isClearScreenFlashPending = true
pendingListener = screenFlashListener
}
screenFlash?.apply(expirationTimeMillis) {
synchronized(lock) {
if (pendingListener == null) {
Logger.w(TAG, "apply: pendingListener is null!")
}
completePendingScreenFlashListener()
}
} ?: run {
Logger.e(TAG, "apply: screenFlash is null!")
// Complete immediately in case this error case is invoked by some bug
completePendingScreenFlashListener()
}
}
override fun clear() {
completePendingScreenFlashClear()
}
/**
* Gets the base [ScreenFlash] where the interface methods are delegated to.
*/
fun getBaseScreenFlash(): ScreenFlash? = screenFlash
/**
* Completes the pending [ScreenFlashListener], if any.
*/
private fun completePendingScreenFlashListener() {
synchronized(lock) {
pendingListener?.onCompleted()
pendingListener = null
}
}
/**
* Completes pending [ScreenFlash.clear] invocation, if any.
*/
private fun completePendingScreenFlashClear() {
synchronized(lock) {
if (isClearScreenFlashPending) {
screenFlash?.clear() ?: run {
Logger.e(TAG, "completePendingScreenFlashClear: screenFlash is null!")
}
} else {
Logger.w(TAG, "completePendingScreenFlashClear: none pending!")
}
isClearScreenFlashPending = false
}
}
/**
* Completes all pending operations.
*/
fun completePendingTasks() {
completePendingScreenFlashListener()
completePendingScreenFlashClear()
}
}