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