1 /*
2  * Copyright 2022 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.camera2.pipe
18 
19 import android.view.Surface
20 import androidx.annotation.GuardedBy
21 import androidx.annotation.RestrictTo
22 import androidx.camera.camera2.pipe.CameraSurfaceManager.SurfaceListener
23 import androidx.camera.camera2.pipe.CameraSurfaceManager.SurfaceToken
24 import androidx.camera.camera2.pipe.core.Log
25 import kotlinx.atomicfu.atomic
26 
27 /**
28  * CameraSurfaceManager is a utility class that manages the lifetime of [Surface]s being used in
29  * CameraPipe. It is a singleton, and has the same lifetime of CameraPipe, meaning that it manages
30  * the lifetime of [Surface]s across multiple [CameraGraph] instances.
31  *
32  * Users of CameraSurfaceManager would register a [SurfaceListener] to receive updates when a
33  * [Surface] is in use ("active") or no longer in use ("inactive") at CameraPipe.
34  *
35  * These callbacks are managed by acquiring and closing "usage tokens" for a given Surface.
36  * acquiring the first token causes [SurfaceListener.onSurfaceActive] to be invoked on all
37  * registered listeners. When a surface is no longer being used, the [SurfaceToken] must be closed.
38  * After all outstanding tokens have been closed, the listeners receive
39  * SurfaceListener.onSurfaceInactive] for that surface.
40  *
41  * If the same [Surface] is used in a subsequent [CameraGraph], it will be issued a different token.
42  * Essentially each token means a single use on a [Surface].
43  */
44 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
45 public class CameraSurfaceManager {
46 
47     private val lock = Any()
48 
49     @GuardedBy("lock") private val useCountMap: MutableMap<Surface, Int> = mutableMapOf()
50 
51     @GuardedBy("lock") private val listeners: MutableSet<SurfaceListener> = mutableSetOf()
52 
53     /**
54      * A new [SurfaceToken] is issued when a [Surface] is registered in CameraSurfaceManager. When
55      * all [SurfaceToken]s issued for a [Surface] is closed, the [Surface] is considered "inactive".
56      */
57     public inner class SurfaceToken(internal val surface: Surface) : AutoCloseable {
58         private val debugId = surfaceTokenDebugIds.incrementAndGet()
59         private val closed = atomic(false)
60 
closenull61         override fun close() {
62             if (closed.compareAndSet(expect = false, update = true)) {
63                 Log.debug { "SurfaceToken $this closed" }
64                 onTokenClosed(this)
65             }
66         }
67 
toStringnull68         override fun toString(): String = "SurfaceToken-$debugId"
69     }
70 
71     public interface SurfaceListener {
72         /**
73          * Called when a [Surface] is in use by a [CameraGraph]. Calling [CameraGraph.setSurface]
74          * will cause [onSurfaceActive] to be called on any currently registered listener. The
75          * surface will remain active until the [CameraGraph] is closed AND any underlying usage has
76          * been released (Normally this means that it will remain in use until the camera device is
77          * closed, or until the CaptureSession that uses it is replaced).
78          */
79         public fun onSurfaceActive(surface: Surface)
80 
81         /**
82          * Called when a [Surface] is considered "inactive" and no longer in use by [CameraGraph].
83          * This can happen under a few different scenarios:
84          * 1. A [Surface] is unset or replaced in a [CameraGraph].
85          * 2. A CaptureSession is closed or fails to configure and the [CameraGraph] has been
86          *    closed.
87          * 3. [CameraGraph] is closed, and the [Surface] isn't not in use by some other camera
88          *    subsystem.
89          */
90         public fun onSurfaceInactive(surface: Surface)
91     }
92 
93     /**
94      * Adds a [SurfaceListener] to receive [Surface] lifetime updates. When a listener is added, it
95      * will receive [SurfaceListener.onSurfaceActive] for all active Surfaces.
96      */
addListenernull97     public fun addListener(listener: SurfaceListener) {
98         val activeSurfaces =
99             synchronized(lock) {
100                 listeners.add(listener)
101                 useCountMap.filter { it.value > 0 }.keys
102             }
103 
104         activeSurfaces.forEach { listener.onSurfaceActive(it) }
105     }
106 
107     /** Removes a [SurfaceListener] to stop receiving [Surface] lifetime updates. */
removeListenernull108     public fun removeListener(listener: SurfaceListener) {
109         synchronized(lock) { listeners.remove(listener) }
110     }
111 
registerSurfacenull112     internal fun registerSurface(surface: Surface): AutoCloseable {
113         if (!surface.isValid) {
114             Log.warn { "registerSurface: Surface $surface isn't valid!" }
115         }
116         val surfaceToken: SurfaceToken
117         var listenersToInvoke: List<SurfaceListener>? = null
118 
119         synchronized(lock) {
120             surfaceToken = SurfaceToken(surface)
121             val newUseCount = (useCountMap[surface] ?: 0) + 1
122             useCountMap[surface] = newUseCount
123             if (DEBUG) {
124                 Log.debug {
125                     "registerSurface: surface=$surface, " +
126                         "surfaceToken=$surfaceToken, newUseCount=$newUseCount" +
127                         (if (DEBUG) " from ${Log.readStackTrace()}" else "")
128                 }
129             }
130 
131             if (newUseCount == 1) {
132                 Log.debug { "$surface for $surfaceToken is active" }
133                 listenersToInvoke = listeners.toList()
134             }
135         }
136 
137         listenersToInvoke?.forEach { it.onSurfaceActive(surface) }
138         return surfaceToken
139     }
140 
onTokenClosednull141     internal fun onTokenClosed(surfaceToken: SurfaceToken) {
142         val surface: Surface
143         var listenersToInvoke: List<SurfaceListener>? = null
144 
145         synchronized(lock) {
146             surface = surfaceToken.surface
147             val useCount = useCountMap[surface]
148             checkNotNull(useCount) { "Surface $surface ($surfaceToken) has no use count" }
149             val newUseCount = useCount - 1
150             useCountMap[surface] = newUseCount
151 
152             if (DEBUG) {
153                 Log.debug {
154                     "onTokenClosed: surface=$surface, " +
155                         "surfaceToken=$surfaceToken, newUseCount=$newUseCount" +
156                         (if (DEBUG) " from ${Log.readStackTrace()}" else "")
157                 }
158             }
159             if (newUseCount == 0) {
160                 Log.debug { "$surface for $surfaceToken is inactive" }
161                 listenersToInvoke = listeners.toList()
162                 useCountMap.remove(surface)
163             }
164         }
165 
166         listenersToInvoke?.forEach { it.onSurfaceInactive(surface) }
167     }
168 
169     public companion object {
170         public const val DEBUG: Boolean = false
171 
172         internal val surfaceTokenDebugIds = atomic(0)
173     }
174 }
175