/* * Copyright (C) 2022 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 com.android.systemui.statusbar.notification.collection import android.view.Choreographer import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.util.ListenerSet import com.android.systemui.util.concurrency.DelayableExecutor import dagger.Binds import dagger.Module import javax.inject.Inject /** * Choreographs evaluation resulting from multiple asynchronous sources. Specifically, it exposes * [schedule], and [addOnEvalListener]; the former will "schedule" an asynchronous invocation of the * latter. Multiple invocations of [schedule] before any added listeners are invoked have no effect. */ interface NotifPipelineChoreographer { /** * Schedules all listeners registered with [addOnEvalListener] to be asynchronously executed at * some point in the future. The exact timing is up to the implementation. */ fun schedule() /** Cancels a pending evaluation triggered by any recent calls to [schedule]. */ fun cancel() /** Adds a listener [Runnable] that will be invoked when the scheduled evaluation occurs. */ fun addOnEvalListener(onEvalListener: Runnable) /** Removes a listener previously registered with [addOnEvalListener]. */ fun removeOnEvalListener(onEvalListener: Runnable) } @Module(includes = [PrivateModule::class]) object NotifPipelineChoreographerModule @Module interface PrivateModule { @Binds fun bindChoreographer(impl: NotifPipelineChoreographerImpl): NotifPipelineChoreographer } private const val TIMEOUT_MS: Long = 100 @SysUISingleton class NotifPipelineChoreographerImpl @Inject constructor( private val viewChoreographer: Choreographer, @Main private val executor: DelayableExecutor ) : NotifPipelineChoreographer { private val listeners = ListenerSet() private var timeoutSubscription: Runnable? = null private var isScheduled = false private val frameCallback = Choreographer.FrameCallback { if (isScheduled) { isScheduled = false timeoutSubscription?.run() listeners.forEach { it.run() } } } override fun schedule() { if (isScheduled) return isScheduled = true viewChoreographer.postFrameCallback(frameCallback) if (!isScheduled) { // Guard against synchronous evaluation of the frame callback. return } timeoutSubscription = executor.executeDelayed(::onTimeout, TIMEOUT_MS) } override fun cancel() { if (!isScheduled) return timeoutSubscription?.run() viewChoreographer.removeFrameCallback(frameCallback) } override fun addOnEvalListener(onEvalListener: Runnable) { listeners.addIfAbsent(onEvalListener) } override fun removeOnEvalListener(onEvalListener: Runnable) { listeners.remove(onEvalListener) } private fun onTimeout() { if (isScheduled) { isScheduled = false viewChoreographer.removeFrameCallback(frameCallback) listeners.forEach { it.run() } } } } class FakeNotifPipelineChoreographer : NotifPipelineChoreographer { var isScheduled = false val listeners = ListenerSet() fun runIfScheduled() { if (isScheduled) { isScheduled = false listeners.forEach { it.run() } } } override fun schedule() { isScheduled = true } override fun cancel() { isScheduled = false } override fun addOnEvalListener(onEvalListener: Runnable) { listeners.addIfAbsent(onEvalListener) } override fun removeOnEvalListener(onEvalListener: Runnable) { listeners.remove(onEvalListener) } }