| /* |
| * Copyright (C) 2020 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.coordinator; |
| |
| import static junit.framework.Assert.assertFalse; |
| |
| import static org.junit.Assert.assertTrue; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.Mockito.doAnswer; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import android.testing.AndroidTestingRunner; |
| import android.testing.TestableLooper; |
| |
| import androidx.test.filters.SmallTest; |
| |
| import com.android.systemui.SysuiTestCase; |
| import com.android.systemui.dump.DumpManager; |
| import com.android.systemui.keyguard.WakefulnessLifecycle; |
| import com.android.systemui.plugins.statusbar.StatusBarStateController; |
| import com.android.systemui.shade.NotifPanelEvents; |
| import com.android.systemui.statusbar.notification.collection.GroupEntry; |
| import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; |
| import com.android.systemui.statusbar.notification.collection.NotifPipeline; |
| import com.android.systemui.statusbar.notification.collection.NotificationEntry; |
| import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; |
| import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; |
| import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; |
| import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider; |
| import com.android.systemui.statusbar.policy.HeadsUpManager; |
| import com.android.systemui.util.concurrency.FakeExecutor; |
| import com.android.systemui.util.time.FakeSystemClock; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Captor; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| import org.mockito.verification.VerificationMode; |
| |
| @SmallTest |
| @RunWith(AndroidTestingRunner.class) |
| @TestableLooper.RunWithLooper |
| public class VisualStabilityCoordinatorTest extends SysuiTestCase { |
| |
| private VisualStabilityCoordinator mCoordinator; |
| |
| @Mock private DumpManager mDumpManager; |
| @Mock private NotifPipeline mNotifPipeline; |
| @Mock private WakefulnessLifecycle mWakefulnessLifecycle; |
| @Mock private StatusBarStateController mStatusBarStateController; |
| @Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener; |
| @Mock private HeadsUpManager mHeadsUpManager; |
| @Mock private NotifPanelEvents mNotifPanelEvents; |
| @Mock private VisualStabilityProvider mVisualStabilityProvider; |
| |
| @Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor; |
| @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor; |
| @Captor private ArgumentCaptor<NotifPanelEvents.Listener> mNotifPanelEventsCallbackCaptor; |
| @Captor private ArgumentCaptor<NotifStabilityManager> mNotifStabilityManagerCaptor; |
| |
| private FakeSystemClock mFakeSystemClock = new FakeSystemClock(); |
| private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock); |
| |
| private WakefulnessLifecycle.Observer mWakefulnessObserver; |
| private StatusBarStateController.StateListener mStatusBarStateListener; |
| private NotifPanelEvents.Listener mNotifPanelEventsCallback; |
| private NotifStabilityManager mNotifStabilityManager; |
| private NotificationEntry mEntry; |
| private GroupEntry mGroupEntry; |
| |
| @Before |
| public void setUp() { |
| MockitoAnnotations.initMocks(this); |
| |
| mCoordinator = new VisualStabilityCoordinator( |
| mFakeExecutor, |
| mDumpManager, |
| mHeadsUpManager, |
| mNotifPanelEvents, |
| mStatusBarStateController, |
| mVisualStabilityProvider, |
| mWakefulnessLifecycle); |
| |
| mCoordinator.attach(mNotifPipeline); |
| |
| // capture arguments: |
| verify(mWakefulnessLifecycle).addObserver(mWakefulnessObserverCaptor.capture()); |
| mWakefulnessObserver = mWakefulnessObserverCaptor.getValue(); |
| |
| verify(mStatusBarStateController).addCallback(mSBStateListenerCaptor.capture()); |
| mStatusBarStateListener = mSBStateListenerCaptor.getValue(); |
| |
| verify(mNotifPanelEvents).registerListener(mNotifPanelEventsCallbackCaptor.capture()); |
| mNotifPanelEventsCallback = mNotifPanelEventsCallbackCaptor.getValue(); |
| |
| verify(mNotifPipeline).setVisualStabilityManager(mNotifStabilityManagerCaptor.capture()); |
| mNotifStabilityManager = mNotifStabilityManagerCaptor.getValue(); |
| mNotifStabilityManager.setInvalidationListener(mInvalidateListener); |
| |
| mEntry = new NotificationEntryBuilder() |
| .setPkg("testPkg1") |
| .build(); |
| |
| mGroupEntry = new GroupEntryBuilder() |
| .setSummary(mEntry) |
| .build(); |
| |
| when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(false); |
| |
| // Whenever we invalidate, the pipeline runs again, so we invalidate the state |
| doAnswer(i -> { |
| mNotifStabilityManager.onBeginRun(); |
| return null; |
| }).when(mInvalidateListener).onPluggableInvalidated(eq(mNotifStabilityManager), any()); |
| } |
| |
| @Test |
| public void testScreenOff_groupAndSectionChangesAllowed() { |
| // GIVEN screen is off, panel isn't expanded and device isn't pulsing |
| setFullyDozed(true); |
| setSleepy(true); |
| setPanelExpanded(false); |
| setPulsing(false); |
| |
| // THEN group changes are allowed |
| assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); |
| assertTrue(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); |
| |
| // THEN section changes are allowed |
| assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); |
| } |
| |
| @Test |
| public void testScreenTurningOff_groupAndSectionChangesNotAllowed() { |
| // GIVEN the screen is turning off (sleepy but partially dozed) |
| setFullyDozed(false); |
| setSleepy(true); |
| setPanelExpanded(true); |
| setPulsing(false); |
| |
| // THEN group changes are NOT allowed |
| assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); |
| assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); |
| |
| // THEN section changes are NOT allowed |
| assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); |
| } |
| |
| @Test |
| public void testScreenTurningOn_groupAndSectionChangesNotAllowed() { |
| // GIVEN the screen is turning on (still fully dozed, not sleepy) |
| setFullyDozed(true); |
| setSleepy(false); |
| setPanelExpanded(true); |
| setPulsing(false); |
| |
| // THEN group changes are NOT allowed |
| assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); |
| assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); |
| |
| // THEN section changes are NOT allowed |
| assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); |
| } |
| |
| @Test |
| public void testPanelNotExpanded_groupAndSectionChangesAllowed() { |
| // GIVEN screen is on but the panel isn't expanded and device isn't pulsing |
| setFullyDozed(false); |
| setSleepy(false); |
| setPanelExpanded(false); |
| setPulsing(false); |
| |
| // THEN group changes are allowed |
| assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); |
| assertTrue(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); |
| |
| // THEN section changes are allowed |
| assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); |
| } |
| |
| @Test |
| public void testPanelExpanded_groupAndSectionChangesNotAllowed() { |
| // GIVEN the panel true expanded and device isn't pulsing |
| setFullyDozed(false); |
| setSleepy(false); |
| setPanelExpanded(true); |
| setPulsing(false); |
| |
| // THEN group changes are NOT allowed |
| assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); |
| assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); |
| |
| // THEN section changes are NOT allowed |
| assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); |
| } |
| |
| @Test |
| public void testPulsing_screenOff_groupAndSectionChangesNotAllowed() { |
| // GIVEN the device is pulsing and screen is off |
| setFullyDozed(true); |
| setSleepy(true); |
| setPulsing(true); |
| |
| // THEN group changes are NOT allowed |
| assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); |
| assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); |
| |
| // THEN section changes are NOT allowed |
| assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); |
| } |
| |
| @Test |
| public void testPulsing_panelNotExpanded_groupAndSectionChangesNotAllowed() { |
| // GIVEN the device is pulsing and screen is off with the panel not expanded |
| setFullyDozed(true); |
| setSleepy(true); |
| setPanelExpanded(false); |
| setPulsing(true); |
| |
| // THEN group changes are NOT allowed |
| assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); |
| assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); |
| |
| // THEN section changes are NOT allowed |
| assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); |
| } |
| |
| @Test |
| public void testOverrideReorderingSuppression_onlySectionChangesAllowed() { |
| // GIVEN section changes typically wouldn't be allowed because the panel is expanded and |
| // we're not pulsing |
| setFullyDozed(false); |
| setSleepy(false); |
| setPanelExpanded(true); |
| setPulsing(true); |
| |
| // WHEN we temporarily allow section changes for this notification entry |
| mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis()); |
| |
| // THEN group changes aren't allowed |
| assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); |
| assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); |
| |
| // THEN section changes are allowed for this notification but not other notifications |
| assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); |
| assertFalse(mNotifStabilityManager.isSectionChangeAllowed( |
| new NotificationEntryBuilder() |
| .setPkg("testPkg2") |
| .build())); |
| } |
| |
| @Test |
| public void testTemporarilyAllowSectionChanges_callsInvalidate() { |
| // GIVEN section changes typically wouldn't be allowed because the panel is expanded |
| setFullyDozed(false); |
| setSleepy(false); |
| setPanelExpanded(true); |
| setPulsing(false); |
| |
| // WHEN we temporarily allow section changes for this notification entry |
| mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.uptimeMillis()); |
| |
| // THEN the notification list is invalidated |
| verifyStabilityManagerWasInvalidated(times(1)); |
| } |
| |
| @Test |
| public void testTemporarilyAllowSectionChanges_noInvalidationCalled() { |
| // GIVEN section changes typically WOULD be allowed |
| setFullyDozed(true); |
| setSleepy(true); |
| setPanelExpanded(false); |
| setPulsing(false); |
| |
| // WHEN we temporarily allow section changes for this notification entry |
| mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis()); |
| |
| // THEN invalidate is not called because this entry was never suppressed from reordering |
| verifyStabilityManagerWasInvalidated(never()); |
| } |
| |
| @Test |
| public void testTemporarilyAllowSectionChangesTimeout() { |
| // GIVEN section changes typically WOULD be allowed |
| setFullyDozed(true); |
| setSleepy(true); |
| setPanelExpanded(false); |
| setPulsing(false); |
| assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); |
| |
| // WHEN we temporarily allow section changes for this notification entry |
| mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis()); |
| |
| // THEN invalidate is not called because this entry was never suppressed from reordering; |
| // THEN section changes are allowed for this notification |
| verifyStabilityManagerWasInvalidated(never()); |
| assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); |
| |
| // WHEN we're pulsing (now disallowing reordering) |
| setPulsing(true); |
| |
| // THEN we're still allowed to reorder this section because it's still in the list of |
| // notifications to allow section changes |
| assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); |
| |
| // WHEN the timeout for the temporarily allow section reordering runnable is finsihed |
| mFakeExecutor.advanceClockToNext(); |
| mFakeExecutor.runNextReady(); |
| |
| // THEN section changes aren't allowed anymore |
| assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); |
| } |
| |
| @Test |
| public void testTemporarilyAllowSectionChanges_isPulsingChangeBeforeTimeout() { |
| // GIVEN section changes typically wouldn't be allowed because the device is pulsing |
| setFullyDozed(true); |
| setSleepy(true); |
| setPanelExpanded(false); |
| setPulsing(true); |
| |
| // WHEN we temporarily allow section changes for this notification entry |
| mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis()); |
| // can now reorder, so invalidates |
| verifyStabilityManagerWasInvalidated(times(1)); |
| |
| // WHEN reordering is now allowed because device isn't pulsing anymore |
| setPulsing(false); |
| |
| // THEN invalidate isn't called a second time since reordering was already allowed |
| verifyStabilityManagerWasInvalidated(times(1)); |
| } |
| |
| @Test |
| public void testNeverSuppressedChanges_noInvalidationCalled() { |
| // GIVEN no notifications are currently being suppressed from grouping nor being sorted |
| |
| // WHEN device isn't pulsing anymore |
| setPulsing(false); |
| |
| // WHEN fully dozed |
| setFullyDozed(true); |
| |
| // WHEN sleepy |
| setSleepy(true); |
| |
| // WHEN panel isn't expanded |
| setPanelExpanded(false); |
| |
| // THEN we never see any calls to invalidate since there weren't any notifications that |
| // were being suppressed from grouping or section changes |
| verifyStabilityManagerWasInvalidated(never()); |
| } |
| |
| @Test |
| public void testNotSuppressingGroupChangesAnymore_invalidationCalled() { |
| // GIVEN visual stability is being maintained b/c panel is expanded |
| setPulsing(false); |
| setFullyDozed(false); |
| setSleepy(false); |
| setPanelExpanded(true); |
| |
| assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); |
| assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); |
| |
| // WHEN the panel isn't expanded anymore |
| setPanelExpanded(false); |
| |
| // invalidate is called because we were previously suppressing a group change |
| verifyStabilityManagerWasInvalidated(times(1)); |
| } |
| |
| @Test |
| public void testNotLaunchingActivityAnymore_invalidationCalled() { |
| // GIVEN visual stability is being maintained b/c animation is playing |
| setActivityLaunching(true); |
| |
| assertFalse(mNotifStabilityManager.isPipelineRunAllowed()); |
| |
| // WHEN the animation has stopped playing |
| setActivityLaunching(false); |
| |
| // invalidate is called, b/c we were previously suppressing the pipeline from running |
| verifyStabilityManagerWasInvalidated(times(1)); |
| } |
| |
| @Test |
| public void testNotCollapsingPanelAnymore_invalidationCalled() { |
| // GIVEN visual stability is being maintained b/c animation is playing |
| setPanelCollapsing(true); |
| |
| assertFalse(mNotifStabilityManager.isPipelineRunAllowed()); |
| |
| // WHEN the animation has stopped playing |
| setPanelCollapsing(false); |
| |
| // invalidate is called, b/c we were previously suppressing the pipeline from running |
| verifyStabilityManagerWasInvalidated(times(1)); |
| } |
| |
| @Test |
| public void testNeverSuppressPipelineRunFromPanelCollapse_noInvalidationCalled() { |
| // GIVEN animation is playing |
| setPanelCollapsing(true); |
| |
| // WHEN the animation has stopped playing |
| setPanelCollapsing(false); |
| |
| // THEN invalidate is not called, b/c nothing has been suppressed |
| verifyStabilityManagerWasInvalidated(never()); |
| } |
| |
| @Test |
| public void testNeverSuppressPipelineRunFromLaunchActivity_noInvalidationCalled() { |
| // GIVEN animation is playing |
| setActivityLaunching(true); |
| |
| // WHEN the animation has stopped playing |
| setActivityLaunching(false); |
| |
| // THEN invalidate is not called, b/c nothing has been suppressed |
| verifyStabilityManagerWasInvalidated(never()); |
| } |
| |
| @Test |
| public void testNotSuppressingEntryReorderingAnymoreWillInvalidate() { |
| // GIVEN visual stability is being maintained b/c panel is expanded |
| setPulsing(false); |
| setFullyDozed(false); |
| setSleepy(false); |
| setPanelExpanded(true); |
| |
| assertFalse(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)); |
| // The pipeline still has to report back that entry reordering was suppressed |
| mNotifStabilityManager.onEntryReorderSuppressed(); |
| |
| // WHEN the panel isn't expanded anymore |
| setPanelExpanded(false); |
| |
| // invalidate is called because we were previously suppressing an entry reorder |
| verifyStabilityManagerWasInvalidated(times(1)); |
| } |
| |
| @Test |
| public void testQueryingEntryReorderingButNotReportingReorderSuppressedDoesNotInvalidate() { |
| // GIVEN visual stability is being maintained b/c panel is expanded |
| setPulsing(false); |
| setFullyDozed(false); |
| setSleepy(false); |
| setPanelExpanded(true); |
| |
| assertFalse(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)); |
| |
| // WHEN the panel isn't expanded anymore |
| setPanelExpanded(false); |
| |
| // invalidate is not called because we were not told that an entry reorder was suppressed |
| verifyStabilityManagerWasInvalidated(never()); |
| } |
| |
| @Test |
| public void testHeadsUp_allowedToChangeGroupAndSection() { |
| // GIVEN group + section changes disallowed |
| setFullyDozed(false); |
| setSleepy(false); |
| setPanelExpanded(true); |
| setPulsing(true); |
| assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); |
| assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); |
| assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); |
| |
| // GIVEN mEntry is a HUN |
| when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true); |
| |
| // THEN group + section changes are allowed |
| assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); |
| assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); |
| |
| // BUT pruning the group for which this is the summary would still NOT be allowed. |
| assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); |
| } |
| |
| private void verifyStabilityManagerWasInvalidated(VerificationMode mode) { |
| verify(mInvalidateListener, mode).onPluggableInvalidated(eq(mNotifStabilityManager), any()); |
| } |
| |
| private void setActivityLaunching(boolean activityLaunching) { |
| mNotifPanelEventsCallback.onLaunchingActivityChanged(activityLaunching); |
| } |
| |
| private void setPanelCollapsing(boolean collapsing) { |
| mNotifPanelEventsCallback.onPanelCollapsingChanged(collapsing); |
| } |
| |
| private void setPulsing(boolean pulsing) { |
| mStatusBarStateListener.onPulsingChanged(pulsing); |
| } |
| |
| private void setFullyDozed(boolean fullyDozed) { |
| float dozeAmount = fullyDozed ? 1 : 0; |
| mStatusBarStateListener.onDozeAmountChanged(dozeAmount, dozeAmount); |
| } |
| |
| private void setSleepy(boolean sleepy) { |
| if (sleepy) { |
| mWakefulnessObserver.onFinishedGoingToSleep(); |
| } else { |
| mWakefulnessObserver.onStartedWakingUp(); |
| } |
| } |
| |
| private void setPanelExpanded(boolean expanded) { |
| mStatusBarStateListener.onExpandedChanged(expanded); |
| } |
| |
| } |