1 /*
2  * Copyright (C) 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 com.android.systemui.statusbar.notification.collection
18 
19 import android.view.Choreographer
20 import com.android.systemui.dagger.SysUISingleton
21 import com.android.systemui.dagger.qualifiers.Main
22 import com.android.systemui.util.ListenerSet
23 import com.android.systemui.util.concurrency.DelayableExecutor
24 import dagger.Binds
25 import dagger.Module
26 import javax.inject.Inject
27 
28 /**
29  * Choreographs evaluation resulting from multiple asynchronous sources. Specifically, it exposes
30  * [schedule], and [addOnEvalListener]; the former will "schedule" an asynchronous invocation of the
31  * latter. Multiple invocations of [schedule] before any added listeners are invoked have no effect.
32  */
33 interface NotifPipelineChoreographer {
34     /**
35      * Schedules all listeners registered with [addOnEvalListener] to be asynchronously executed at
36      * some point in the future. The exact timing is up to the implementation.
37      */
schedulenull38     fun schedule()
39 
40     /** Cancels a pending evaluation triggered by any recent calls to [schedule]. */
41     fun cancel()
42 
43     /** Adds a listener [Runnable] that will be invoked when the scheduled evaluation occurs. */
44     fun addOnEvalListener(onEvalListener: Runnable)
45 
46     /** Removes a listener previously registered with [addOnEvalListener]. */
47     fun removeOnEvalListener(onEvalListener: Runnable)
48 }
49 
50 @Module(includes = [PrivateModule::class])
51 object NotifPipelineChoreographerModule
52 
53 @Module
54 interface PrivateModule {
55     @Binds
56     fun bindChoreographer(impl: NotifPipelineChoreographerImpl): NotifPipelineChoreographer
57 }
58 
59 private const val TIMEOUT_MS: Long = 100
60 
61 @SysUISingleton
62 class NotifPipelineChoreographerImpl @Inject constructor(
63     private val viewChoreographer: Choreographer,
64     @Main private val executor: DelayableExecutor
65 ) : NotifPipelineChoreographer {
66 
67     private val listeners = ListenerSet<Runnable>()
68     private var timeoutSubscription: Runnable? = null
69     private var isScheduled = false
70 
<lambda>null71     private val frameCallback = Choreographer.FrameCallback {
72         if (isScheduled) {
73             isScheduled = false
74             timeoutSubscription?.run()
75             listeners.forEach { it.run() }
76         }
77     }
78 
schedulenull79     override fun schedule() {
80         if (isScheduled) return
81         isScheduled = true
82         viewChoreographer.postFrameCallback(frameCallback)
83         if (!isScheduled) {
84             // Guard against synchronous evaluation of the frame callback.
85             return
86         }
87         timeoutSubscription = executor.executeDelayed(::onTimeout, TIMEOUT_MS)
88     }
89 
cancelnull90     override fun cancel() {
91         if (!isScheduled) return
92         timeoutSubscription?.run()
93         viewChoreographer.removeFrameCallback(frameCallback)
94     }
95 
addOnEvalListenernull96     override fun addOnEvalListener(onEvalListener: Runnable) {
97         listeners.addIfAbsent(onEvalListener)
98     }
99 
removeOnEvalListenernull100     override fun removeOnEvalListener(onEvalListener: Runnable) {
101         listeners.remove(onEvalListener)
102     }
103 
onTimeoutnull104     private fun onTimeout() {
105         if (isScheduled) {
106             isScheduled = false
107             viewChoreographer.removeFrameCallback(frameCallback)
108             listeners.forEach { it.run() }
109         }
110     }
111 }
112 
113 class FakeNotifPipelineChoreographer : NotifPipelineChoreographer {
114 
115     var isScheduled = false
116     val listeners = ListenerSet<Runnable>()
117 
runIfSchedulednull118     fun runIfScheduled() {
119         if (isScheduled) {
120             isScheduled = false
121             listeners.forEach { it.run() }
122         }
123     }
124 
schedulenull125     override fun schedule() {
126         isScheduled = true
127     }
128 
cancelnull129     override fun cancel() {
130         isScheduled = false
131     }
132 
addOnEvalListenernull133     override fun addOnEvalListener(onEvalListener: Runnable) {
134         listeners.addIfAbsent(onEvalListener)
135     }
136 
removeOnEvalListenernull137     override fun removeOnEvalListener(onEvalListener: Runnable) {
138         listeners.remove(onEvalListener)
139     }
140 }
141