blob: 96c40ecd3d814b157d5f30ec04be274ce4fcdf58 [file] [log] [blame]
/*
* 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.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.phone.NotifPanelEvents;
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);
}
}