Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt 0 → 100644 +140 −0 Original line number Diff line number Diff line /* * 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.Module import dagger.Provides /** * 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 object NotifPipelineChoreographerModule { @Provides @JvmStatic @SysUISingleton fun provideChoreographer( choreographer: Choreographer, @Main mainExecutor: DelayableExecutor ): NotifPipelineChoreographer = NotifPipelineChoreographerImpl(choreographer, mainExecutor) } private const val TIMEOUT_MS: Long = 100 private class NotifPipelineChoreographerImpl( private val viewChoreographer: Choreographer, private val executor: DelayableExecutor ) : NotifPipelineChoreographer { private val listeners = ListenerSet<Runnable>() 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<Runnable>() 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) } } packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +9 −5 Original line number Diff line number Diff line Loading @@ -122,20 +122,23 @@ public class ShadeListBuilder implements Dumpable { private List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList); private List<ListEntry> mReadOnlyNewNotifList = Collections.unmodifiableList(mNewNotifList); private final NotifPipelineChoreographer mChoreographer; @Inject public ShadeListBuilder( SystemClock systemClock, DumpManager dumpManager, NotifPipelineChoreographer pipelineChoreographer, NotifPipelineFlags flags, NotificationInteractionTracker interactionTracker, ShadeListBuilderLogger logger, DumpManager dumpManager, NotificationInteractionTracker interactionTracker SystemClock systemClock ) { Assert.isMainThread(); mSystemClock = systemClock; mLogger = logger; mAlwaysLogList = flags.isDevLoggingEnabled(); mInteractionTracker = interactionTracker; mChoreographer = pipelineChoreographer; dumpManager.registerDumpable(TAG, this); setSectioners(Collections.emptyList()); Loading @@ -148,6 +151,7 @@ public class ShadeListBuilder implements Dumpable { public void attach(NotifCollection collection) { Assert.isMainThread(); collection.setBuildListener(mReadyForBuildListener); mChoreographer.addOnEvalListener(this::buildList); } /** Loading Loading @@ -290,7 +294,7 @@ public class ShadeListBuilder implements Dumpable { mLogger.logOnBuildList(); mAllEntries = entries; buildList(); mChoreographer.schedule(); } }; Loading Loading @@ -1281,7 +1285,7 @@ public class ShadeListBuilder implements Dumpable { private void rebuildListIfBefore(@PipelineState.StateName int state) { mPipelineState.requireIsBefore(state); if (mPipelineState.is(STATE_IDLE)) { buildList(); mChoreographer.schedule(); } } Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +2 −0 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl; import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotifPipelineChoreographerModule; import com.android.systemui.statusbar.notification.collection.coordinator.ShadeEventCoordinator; import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator; import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorsModule; Loading Loading @@ -103,6 +104,7 @@ import dagger.Provides; */ @Module(includes = { CoordinatorsModule.class, NotifPipelineChoreographerModule.class, NotifPanelEventSourceModule.class, NotificationSectionHeadersModule.class, }) Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt 0 → 100644 +100 −0 Original line number Diff line number Diff line /* * 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.testing.AndroidTestingRunner import android.view.Choreographer import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.withArgCaptor import org.junit.Assert.assertTrue import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.anyLong import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) class NotifPipelineChoreographerTest : SysuiTestCase() { val viewChoreographer: Choreographer = mock() val timeoueSubscription: Runnable = mock() val executor: DelayableExecutor = mock<DelayableExecutor>().also { whenever(it.executeDelayed(any(), anyLong())).thenReturn(timeoueSubscription) } val pipelineChoreographer: NotifPipelineChoreographer = NotifPipelineChoreographerModule .provideChoreographer(viewChoreographer, executor) @Test fun scheduleThenEvalFrameCallback() { // GIVEN a registered eval listener and scheduled choreographer var hasEvaluated = false pipelineChoreographer.addOnEvalListener { hasEvaluated = true } pipelineChoreographer.schedule() val frameCallback: Choreographer.FrameCallback = withArgCaptor { verify(viewChoreographer).postFrameCallback(capture()) } // WHEN the choreographer would invoke its callback frameCallback.doFrame(0) // THEN the choreographer would evaluate, and the timeoutSubscription would have been // cancelled assertTrue(hasEvaluated) verify(timeoueSubscription).run() } @Test fun scheduleThenEvalTimeoutCallback() { // GIVEN a registered eval listener and scheduled choreographer var hasEvaluated = false pipelineChoreographer.addOnEvalListener { hasEvaluated = true } pipelineChoreographer.schedule() val frameCallback: Choreographer.FrameCallback = withArgCaptor { verify(viewChoreographer).postFrameCallback(capture()) } val runnable: Runnable = withArgCaptor { verify(executor).executeDelayed(capture(), anyLong()) } // WHEN the executor would invoke its callback (indicating a timeout) runnable.run() // THEN the choreographer would evaluate, and the FrameCallback would have been unregistered assertTrue(hasEvaluated) verify(viewChoreographer).removeFrameCallback(frameCallback) } @Test fun scheduleThenCancel() { // GIVEN a scheduled choreographer pipelineChoreographer.schedule() val frameCallback: Choreographer.FrameCallback = withArgCaptor { verify(viewChoreographer).postFrameCallback(capture()) } // WHEN the scheduled run is cancelled pipelineChoreographer.cancel() // THEN both the FrameCallback is unregistered and the timeout subscription is cancelled. verify(viewChoreographer).removeFrameCallback(frameCallback) verify(timeoueSubscription).run() } } No newline at end of file packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +46 −3 Original line number Diff line number Diff line Loading @@ -111,6 +111,8 @@ public class ShadeListBuilderTest extends SysuiTestCase { @Captor private ArgumentCaptor<CollectionReadyForBuildListener> mBuildListenerCaptor; private final FakeNotifPipelineChoreographer mPipelineChoreographer = new FakeNotifPipelineChoreographer(); private CollectionReadyForBuildListener mReadyForBuildListener; private List<NotificationEntryBuilder> mPendingSet = new ArrayList<>(); private List<NotificationEntry> mEntrySet = new ArrayList<>(); Loading @@ -127,11 +129,12 @@ public class ShadeListBuilderTest extends SysuiTestCase { allowTestableLooperAsMainThread(); mListBuilder = new ShadeListBuilder( mSystemClock, mDumpManager, mPipelineChoreographer, mNotifPipelineFlags, mInteractionTracker, mLogger, mDumpManager, mInteractionTracker mSystemClock ); mListBuilder.setOnRenderListListener(mOnRenderListListener); Loading Loading @@ -567,6 +570,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { // WHEN the pipeline is kicked off mReadyForBuildListener.onBuildList(singletonList(entry)); mPipelineChoreographer.runIfScheduled(); // THEN the entry's initialization time is reset assertFalse(entry.hasFinishedInitialization()); Loading Loading @@ -1024,26 +1028,38 @@ public class ShadeListBuilderTest extends SysuiTestCase { clearInvocations(mOnRenderListListener); packageFilter.invalidateList(); assertTrue(mPipelineChoreographer.isScheduled()); mPipelineChoreographer.runIfScheduled(); verify(mOnRenderListListener).onRenderList(anyList()); clearInvocations(mOnRenderListListener); idPromoter.invalidateList(); assertTrue(mPipelineChoreographer.isScheduled()); mPipelineChoreographer.runIfScheduled(); verify(mOnRenderListListener).onRenderList(anyList()); clearInvocations(mOnRenderListListener); section.invalidateList(); assertTrue(mPipelineChoreographer.isScheduled()); mPipelineChoreographer.runIfScheduled(); verify(mOnRenderListListener).onRenderList(anyList()); clearInvocations(mOnRenderListListener); hypeComparator.invalidateList(); assertTrue(mPipelineChoreographer.isScheduled()); mPipelineChoreographer.runIfScheduled(); verify(mOnRenderListListener).onRenderList(anyList()); clearInvocations(mOnRenderListListener); sectionComparator.invalidateList(); assertTrue(mPipelineChoreographer.isScheduled()); mPipelineChoreographer.runIfScheduled(); verify(mOnRenderListListener).onRenderList(anyList()); clearInvocations(mOnRenderListListener); preRenderInvalidator.invalidateList(); assertTrue(mPipelineChoreographer.isScheduled()); mPipelineChoreographer.runIfScheduled(); verify(mOnRenderListListener).onRenderList(anyList()); } Loading Loading @@ -1515,6 +1531,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { // WHEN visual stability manager allows group changes again mStabilityManager.setAllowGroupChanges(true); mStabilityManager.invalidateList(); mPipelineChoreographer.runIfScheduled(); // THEN entries are grouped verifyBuiltList( Loading Loading @@ -1553,6 +1570,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { // WHEN section changes are allowed again mStabilityManager.setAllowSectionChanges(true); mStabilityManager.invalidateList(); mPipelineChoreographer.runIfScheduled(); // THEN the section updates assertEquals(newSectioner, mEntrySet.get(0).getSection().getSectioner()); Loading Loading @@ -1772,6 +1790,30 @@ public class ShadeListBuilderTest extends SysuiTestCase { verifyBuiltList(); } @Test public void testMultipleInvalidationsCoalesce() { // GIVEN a PreGroupFilter and a FinalizeFilter NotifFilter filter1 = new PackageFilter(PACKAGE_5); NotifFilter filter2 = new PackageFilter(PACKAGE_0); mListBuilder.addPreGroupFilter(filter1); mListBuilder.addFinalizeFilter(filter2); // WHEN both filters invalidate filter1.invalidateList(); filter2.invalidateList(); // THEN the pipeline choreographer is scheduled to evaluate, AND the pipeline hasn't // actually run. assertTrue(mPipelineChoreographer.isScheduled()); verify(mOnRenderListListener, never()).onRenderList(anyList()); // WHEN the pipeline choreographer actually runs mPipelineChoreographer.runIfScheduled(); // THEN the pipeline runs verify(mOnRenderListListener).onRenderList(anyList()); } @Test public void testIsSorted() { Comparator<Integer> intCmp = Integer::compare; Loading Loading @@ -1914,6 +1956,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { } mReadyForBuildListener.onBuildList(mEntrySet); mPipelineChoreographer.runIfScheduled(); } private void verifyBuiltList(ExpectedEntry ...expectedEntries) { Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt 0 → 100644 +140 −0 Original line number Diff line number Diff line /* * 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.Module import dagger.Provides /** * 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 object NotifPipelineChoreographerModule { @Provides @JvmStatic @SysUISingleton fun provideChoreographer( choreographer: Choreographer, @Main mainExecutor: DelayableExecutor ): NotifPipelineChoreographer = NotifPipelineChoreographerImpl(choreographer, mainExecutor) } private const val TIMEOUT_MS: Long = 100 private class NotifPipelineChoreographerImpl( private val viewChoreographer: Choreographer, private val executor: DelayableExecutor ) : NotifPipelineChoreographer { private val listeners = ListenerSet<Runnable>() 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<Runnable>() 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) } }
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +9 −5 Original line number Diff line number Diff line Loading @@ -122,20 +122,23 @@ public class ShadeListBuilder implements Dumpable { private List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList); private List<ListEntry> mReadOnlyNewNotifList = Collections.unmodifiableList(mNewNotifList); private final NotifPipelineChoreographer mChoreographer; @Inject public ShadeListBuilder( SystemClock systemClock, DumpManager dumpManager, NotifPipelineChoreographer pipelineChoreographer, NotifPipelineFlags flags, NotificationInteractionTracker interactionTracker, ShadeListBuilderLogger logger, DumpManager dumpManager, NotificationInteractionTracker interactionTracker SystemClock systemClock ) { Assert.isMainThread(); mSystemClock = systemClock; mLogger = logger; mAlwaysLogList = flags.isDevLoggingEnabled(); mInteractionTracker = interactionTracker; mChoreographer = pipelineChoreographer; dumpManager.registerDumpable(TAG, this); setSectioners(Collections.emptyList()); Loading @@ -148,6 +151,7 @@ public class ShadeListBuilder implements Dumpable { public void attach(NotifCollection collection) { Assert.isMainThread(); collection.setBuildListener(mReadyForBuildListener); mChoreographer.addOnEvalListener(this::buildList); } /** Loading Loading @@ -290,7 +294,7 @@ public class ShadeListBuilder implements Dumpable { mLogger.logOnBuildList(); mAllEntries = entries; buildList(); mChoreographer.schedule(); } }; Loading Loading @@ -1281,7 +1285,7 @@ public class ShadeListBuilder implements Dumpable { private void rebuildListIfBefore(@PipelineState.StateName int state) { mPipelineState.requireIsBefore(state); if (mPipelineState.is(STATE_IDLE)) { buildList(); mChoreographer.schedule(); } } Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +2 −0 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl; import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotifPipelineChoreographerModule; import com.android.systemui.statusbar.notification.collection.coordinator.ShadeEventCoordinator; import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator; import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorsModule; Loading Loading @@ -103,6 +104,7 @@ import dagger.Provides; */ @Module(includes = { CoordinatorsModule.class, NotifPipelineChoreographerModule.class, NotifPanelEventSourceModule.class, NotificationSectionHeadersModule.class, }) Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt 0 → 100644 +100 −0 Original line number Diff line number Diff line /* * 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.testing.AndroidTestingRunner import android.view.Choreographer import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.withArgCaptor import org.junit.Assert.assertTrue import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.anyLong import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) class NotifPipelineChoreographerTest : SysuiTestCase() { val viewChoreographer: Choreographer = mock() val timeoueSubscription: Runnable = mock() val executor: DelayableExecutor = mock<DelayableExecutor>().also { whenever(it.executeDelayed(any(), anyLong())).thenReturn(timeoueSubscription) } val pipelineChoreographer: NotifPipelineChoreographer = NotifPipelineChoreographerModule .provideChoreographer(viewChoreographer, executor) @Test fun scheduleThenEvalFrameCallback() { // GIVEN a registered eval listener and scheduled choreographer var hasEvaluated = false pipelineChoreographer.addOnEvalListener { hasEvaluated = true } pipelineChoreographer.schedule() val frameCallback: Choreographer.FrameCallback = withArgCaptor { verify(viewChoreographer).postFrameCallback(capture()) } // WHEN the choreographer would invoke its callback frameCallback.doFrame(0) // THEN the choreographer would evaluate, and the timeoutSubscription would have been // cancelled assertTrue(hasEvaluated) verify(timeoueSubscription).run() } @Test fun scheduleThenEvalTimeoutCallback() { // GIVEN a registered eval listener and scheduled choreographer var hasEvaluated = false pipelineChoreographer.addOnEvalListener { hasEvaluated = true } pipelineChoreographer.schedule() val frameCallback: Choreographer.FrameCallback = withArgCaptor { verify(viewChoreographer).postFrameCallback(capture()) } val runnable: Runnable = withArgCaptor { verify(executor).executeDelayed(capture(), anyLong()) } // WHEN the executor would invoke its callback (indicating a timeout) runnable.run() // THEN the choreographer would evaluate, and the FrameCallback would have been unregistered assertTrue(hasEvaluated) verify(viewChoreographer).removeFrameCallback(frameCallback) } @Test fun scheduleThenCancel() { // GIVEN a scheduled choreographer pipelineChoreographer.schedule() val frameCallback: Choreographer.FrameCallback = withArgCaptor { verify(viewChoreographer).postFrameCallback(capture()) } // WHEN the scheduled run is cancelled pipelineChoreographer.cancel() // THEN both the FrameCallback is unregistered and the timeout subscription is cancelled. verify(viewChoreographer).removeFrameCallback(frameCallback) verify(timeoueSubscription).run() } } No newline at end of file
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +46 −3 Original line number Diff line number Diff line Loading @@ -111,6 +111,8 @@ public class ShadeListBuilderTest extends SysuiTestCase { @Captor private ArgumentCaptor<CollectionReadyForBuildListener> mBuildListenerCaptor; private final FakeNotifPipelineChoreographer mPipelineChoreographer = new FakeNotifPipelineChoreographer(); private CollectionReadyForBuildListener mReadyForBuildListener; private List<NotificationEntryBuilder> mPendingSet = new ArrayList<>(); private List<NotificationEntry> mEntrySet = new ArrayList<>(); Loading @@ -127,11 +129,12 @@ public class ShadeListBuilderTest extends SysuiTestCase { allowTestableLooperAsMainThread(); mListBuilder = new ShadeListBuilder( mSystemClock, mDumpManager, mPipelineChoreographer, mNotifPipelineFlags, mInteractionTracker, mLogger, mDumpManager, mInteractionTracker mSystemClock ); mListBuilder.setOnRenderListListener(mOnRenderListListener); Loading Loading @@ -567,6 +570,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { // WHEN the pipeline is kicked off mReadyForBuildListener.onBuildList(singletonList(entry)); mPipelineChoreographer.runIfScheduled(); // THEN the entry's initialization time is reset assertFalse(entry.hasFinishedInitialization()); Loading Loading @@ -1024,26 +1028,38 @@ public class ShadeListBuilderTest extends SysuiTestCase { clearInvocations(mOnRenderListListener); packageFilter.invalidateList(); assertTrue(mPipelineChoreographer.isScheduled()); mPipelineChoreographer.runIfScheduled(); verify(mOnRenderListListener).onRenderList(anyList()); clearInvocations(mOnRenderListListener); idPromoter.invalidateList(); assertTrue(mPipelineChoreographer.isScheduled()); mPipelineChoreographer.runIfScheduled(); verify(mOnRenderListListener).onRenderList(anyList()); clearInvocations(mOnRenderListListener); section.invalidateList(); assertTrue(mPipelineChoreographer.isScheduled()); mPipelineChoreographer.runIfScheduled(); verify(mOnRenderListListener).onRenderList(anyList()); clearInvocations(mOnRenderListListener); hypeComparator.invalidateList(); assertTrue(mPipelineChoreographer.isScheduled()); mPipelineChoreographer.runIfScheduled(); verify(mOnRenderListListener).onRenderList(anyList()); clearInvocations(mOnRenderListListener); sectionComparator.invalidateList(); assertTrue(mPipelineChoreographer.isScheduled()); mPipelineChoreographer.runIfScheduled(); verify(mOnRenderListListener).onRenderList(anyList()); clearInvocations(mOnRenderListListener); preRenderInvalidator.invalidateList(); assertTrue(mPipelineChoreographer.isScheduled()); mPipelineChoreographer.runIfScheduled(); verify(mOnRenderListListener).onRenderList(anyList()); } Loading Loading @@ -1515,6 +1531,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { // WHEN visual stability manager allows group changes again mStabilityManager.setAllowGroupChanges(true); mStabilityManager.invalidateList(); mPipelineChoreographer.runIfScheduled(); // THEN entries are grouped verifyBuiltList( Loading Loading @@ -1553,6 +1570,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { // WHEN section changes are allowed again mStabilityManager.setAllowSectionChanges(true); mStabilityManager.invalidateList(); mPipelineChoreographer.runIfScheduled(); // THEN the section updates assertEquals(newSectioner, mEntrySet.get(0).getSection().getSectioner()); Loading Loading @@ -1772,6 +1790,30 @@ public class ShadeListBuilderTest extends SysuiTestCase { verifyBuiltList(); } @Test public void testMultipleInvalidationsCoalesce() { // GIVEN a PreGroupFilter and a FinalizeFilter NotifFilter filter1 = new PackageFilter(PACKAGE_5); NotifFilter filter2 = new PackageFilter(PACKAGE_0); mListBuilder.addPreGroupFilter(filter1); mListBuilder.addFinalizeFilter(filter2); // WHEN both filters invalidate filter1.invalidateList(); filter2.invalidateList(); // THEN the pipeline choreographer is scheduled to evaluate, AND the pipeline hasn't // actually run. assertTrue(mPipelineChoreographer.isScheduled()); verify(mOnRenderListListener, never()).onRenderList(anyList()); // WHEN the pipeline choreographer actually runs mPipelineChoreographer.runIfScheduled(); // THEN the pipeline runs verify(mOnRenderListListener).onRenderList(anyList()); } @Test public void testIsSorted() { Comparator<Integer> intCmp = Integer::compare; Loading Loading @@ -1914,6 +1956,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { } mReadyForBuildListener.onBuildList(mEntrySet); mPipelineChoreographer.runIfScheduled(); } private void verifyBuiltList(ExpectedEntry ...expectedEntries) { Loading