1 /*
2  * Copyright 2023 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.core.internal
18 
19 import androidx.annotation.GuardedBy
20 import androidx.camera.core.ImageCapture.ScreenFlash
21 import androidx.camera.core.ImageCapture.ScreenFlashListener
22 import androidx.camera.core.Logger
23 
24 /**
25  * Wrapper class around [ScreenFlash] to save the [ScreenFlashListener] passed to app.
26  *
27  * This allows us to clean up properly in case a capture is cancelled earlier (e.g. ImageCapture is
28  * unbound after [apply] is invoked but [clear] is not).
29  */
30 internal class ScreenFlashWrapper private constructor(private val screenFlash: ScreenFlash?) :
31     ScreenFlash {
32     private val lock = Object()
33 
34     @GuardedBy("lock") private var isClearScreenFlashPending: Boolean = false
35     @GuardedBy("lock") private var pendingListener: ScreenFlashListener? = null
36 
37     companion object {
38         private const val TAG = "ScreenFlashWrapper"
39 
fromnull40         @JvmStatic fun from(screenFlash: ScreenFlash?) = ScreenFlashWrapper(screenFlash)
41     }
42 
43     override fun apply(expirationTimeMillis: Long, screenFlashListener: ScreenFlashListener) {
44         synchronized(lock) {
45             isClearScreenFlashPending = true
46             pendingListener = screenFlashListener
47         }
48 
49         screenFlash?.apply(expirationTimeMillis) {
50             synchronized(lock) {
51                 if (pendingListener == null) {
52                     Logger.w(TAG, "apply: pendingListener is null!")
53                 }
54                 completePendingScreenFlashListener()
55             }
56         }
57             ?: run {
58                 Logger.e(TAG, "apply: screenFlash is null!")
59                 // Complete immediately in case this error case is invoked by some bug
60                 completePendingScreenFlashListener()
61             }
62     }
63 
clearnull64     override fun clear() {
65         completePendingScreenFlashClear()
66     }
67 
68     /** Gets the base [ScreenFlash] where the interface methods are delegated to. */
getBaseScreenFlashnull69     fun getBaseScreenFlash(): ScreenFlash? = screenFlash
70 
71     /** Completes the pending [ScreenFlashListener], if any. */
72     private fun completePendingScreenFlashListener() {
73         synchronized(lock) {
74             pendingListener?.onCompleted()
75             pendingListener = null
76         }
77     }
78 
79     /** Completes pending [ScreenFlash.clear] invocation, if any. */
completePendingScreenFlashClearnull80     private fun completePendingScreenFlashClear() {
81         synchronized(lock) {
82             if (isClearScreenFlashPending) {
83                 screenFlash?.clear()
84                     ?: run {
85                         Logger.e(TAG, "completePendingScreenFlashClear: screenFlash is null!")
86                     }
87             } else {
88                 Logger.w(TAG, "completePendingScreenFlashClear: none pending!")
89             }
90             isClearScreenFlashPending = false
91         }
92     }
93 
94     /** Completes all pending operations. */
completePendingTasksnull95     fun completePendingTasks() {
96         completePendingScreenFlashListener()
97         completePendingScreenFlashClear()
98     }
99 }
100