Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +15 −5 Original line number Original line Diff line number Diff line Loading @@ -389,11 +389,11 @@ class HeadsUpCoordinator @Inject constructor( // First check whether this notification should launch a full screen intent, and // First check whether this notification should launch a full screen intent, and // launch it if needed. // launch it if needed. val fsiDecision = mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry) val fsiDecision = mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry) if (fsiDecision != null && fsiDecision.shouldLaunch) { mNotificationInterruptStateProvider.logFullScreenIntentDecision(entry, fsiDecision) mNotificationInterruptStateProvider.logFullScreenIntentDecision(entry, fsiDecision) if (fsiDecision.shouldLaunch) { mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry) mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry) } else if (mFlags.fsiOnDNDUpdate() && } else if (mFlags.fsiOnDNDUpdate() && fsiDecision.equals(FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)) { fsiDecision == FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) { // If DND was the only reason this entry was suppressed, note it for potential // If DND was the only reason this entry was suppressed, note it for potential // reconsideration on later ranking updates. // reconsideration on later ranking updates. addForFSIReconsideration(entry, mSystemClock.currentTimeMillis()) addForFSIReconsideration(entry, mSystemClock.currentTimeMillis()) Loading Loading @@ -514,14 +514,24 @@ class HeadsUpCoordinator @Inject constructor( mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry) mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry) if (decision.shouldLaunch) { if (decision.shouldLaunch) { // Log both the launch of the full screen and also that this was via a // Log both the launch of the full screen and also that this was via a // ranking update. // ranking update, and finally revoke candidacy for FSI reconsideration mLogger.logEntryUpdatedToFullScreen(entry.key) mLogger.logEntryUpdatedToFullScreen(entry.key, decision.name) mNotificationInterruptStateProvider.logFullScreenIntentDecision( mNotificationInterruptStateProvider.logFullScreenIntentDecision( entry, decision) entry, decision) mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry) mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry) mFSIUpdateCandidates.remove(entry.key) // if we launch the FSI then this is no longer a candidate for HUN // if we launch the FSI then this is no longer a candidate for HUN continue continue } else if (decision == FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) { // decision has not changed; no need to log } else { // some other condition is now blocking FSI; log that and revoke candidacy // for FSI reconsideration mLogger.logEntryDisqualifiedFromFullScreen(entry.key, decision.name) mNotificationInterruptStateProvider.logFullScreenIntentDecision( entry, decision) mFSIUpdateCandidates.remove(entry.key) } } } } Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt +12 −2 Original line number Original line Diff line number Diff line Loading @@ -70,11 +70,21 @@ class HeadsUpCoordinatorLogger constructor( }) }) } } fun logEntryUpdatedToFullScreen(key: String) { fun logEntryUpdatedToFullScreen(key: String, reason: String) { buffer.log(TAG, LogLevel.DEBUG, { buffer.log(TAG, LogLevel.DEBUG, { str1 = key str1 = key str2 = reason }, { "updating entry to launch full screen intent: $str1 because $str2" }) } fun logEntryDisqualifiedFromFullScreen(key: String, reason: String) { buffer.log(TAG, LogLevel.DEBUG, { str1 = key str2 = reason }, { }, { "updating entry to launch full screen intent: $str1" "updated entry no longer qualifies for full screen intent: $str1 because $str2" }) }) } } Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java +4 −1 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.interruption; package com.android.systemui.statusbar.notification.interruption; import androidx.annotation.NonNull; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** /** Loading Loading @@ -153,7 +155,8 @@ public interface NotificationInterruptStateProvider { * @param entry the entry to evaluate * @param entry the entry to evaluate * @return FullScreenIntentDecision representing the decision for whether to show the intent * @return FullScreenIntentDecision representing the decision for whether to show the intent */ */ FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry); @NonNull FullScreenIntentDecision getFullScreenIntentDecision(@NonNull NotificationEntry entry); /** /** * Write the full screen launch decision for the given entry to logs. * Write the full screen launch decision for the given entry to logs. Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +15 −34 Original line number Original line Diff line number Diff line Loading @@ -35,6 +35,8 @@ import android.service.dreams.IDreamManager; import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification; import android.util.Log; import android.util.Log; import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger; Loading Loading @@ -232,6 +234,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter // suppressor. // suppressor. // // // If the entry was not suppressed by DND, just returns the given decision. // If the entry was not suppressed by DND, just returns the given decision. @NonNull private FullScreenIntentDecision getDecisionGivenSuppression(FullScreenIntentDecision decision, private FullScreenIntentDecision getDecisionGivenSuppression(FullScreenIntentDecision decision, boolean suppressedByDND) { boolean suppressedByDND) { if (suppressedByDND) { if (suppressedByDND) { Loading @@ -243,7 +246,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } } @Override @Override public FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry) { public FullScreenIntentDecision getFullScreenIntentDecision(@NonNull NotificationEntry entry) { if (entry.getSbn().getNotification().fullScreenIntent == null) { if (entry.getSbn().getNotification().fullScreenIntent == null) { if (entry.isStickyAndNotDemoted()) { if (entry.isStickyAndNotDemoted()) { return FullScreenIntentDecision.NO_FSI_SHOW_STICKY_HUN; return FullScreenIntentDecision.NO_FSI_SHOW_STICKY_HUN; Loading Loading @@ -336,52 +339,30 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter final int uid = entry.getSbn().getUid(); final int uid = entry.getSbn().getUid(); final String packageName = entry.getSbn().getPackageName(); final String packageName = entry.getSbn().getPackageName(); switch (decision) { switch (decision) { case NO_FSI_SHOW_STICKY_HUN: mLogger.logNoFullscreen(entry, "Permission denied, show sticky HUN"); return; case NO_FULL_SCREEN_INTENT: case NO_FULL_SCREEN_INTENT: return; // explicitly prevent logging for this (frequent) case case NO_FSI_SUPPRESSED_BY_DND: case NO_FSI_SUPPRESSED_ONLY_BY_DND: mLogger.logNoFullscreen(entry, "Suppressed by DND"); return; case NO_FSI_NOT_IMPORTANT_ENOUGH: mLogger.logNoFullscreen(entry, "Not important enough"); return; return; case NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR: case NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR: android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "groupAlertBehavior"); "groupAlertBehavior"); mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid, mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid, packageName); packageName); mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN"); mLogger.logNoFullscreenWarning(entry, return; decision + ": GroupAlertBehavior will prevent HUN"); case FSI_DEVICE_NOT_INTERACTIVE: mLogger.logFullscreen(entry, "Device is not interactive"); return; case FSI_DEVICE_IS_DREAMING: mLogger.logFullscreen(entry, "Device is dreaming"); return; case FSI_KEYGUARD_SHOWING: mLogger.logFullscreen(entry, "Keyguard is showing"); return; case NO_FSI_EXPECTED_TO_HUN: mLogger.logNoFullscreen(entry, "Expected to HUN"); return; case FSI_KEYGUARD_OCCLUDED: mLogger.logFullscreen(entry, "Expected not to HUN while keyguard occluded"); return; case FSI_LOCKED_SHADE: mLogger.logFullscreen(entry, "Keyguard is showing and not occluded"); return; return; case NO_FSI_NO_HUN_OR_KEYGUARD: case NO_FSI_NO_HUN_OR_KEYGUARD: android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "no hun or keyguard"); "no hun or keyguard"); mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName); mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName); mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard"); mLogger.logNoFullscreenWarning(entry, decision + ": Expected not to HUN while not on keyguard"); return; return; case FSI_EXPECTED_NOT_TO_HUN: default: mLogger.logFullscreen(entry, "Expected not to HUN"); if (decision.shouldLaunch) { mLogger.logFullscreen(entry, decision.name()); } else { mLogger.logNoFullscreen(entry, decision.name()); } } } } } Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +157 −4 Original line number Original line Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.Flags import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder Loading Loading @@ -58,6 +59,7 @@ import org.junit.Before import org.junit.Test import org.junit.Test import org.junit.runner.RunWith import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyString import org.mockito.ArgumentMatchers.anyString import org.mockito.BDDMockito.clearInvocations import org.mockito.BDDMockito.given import org.mockito.BDDMockito.given import org.mockito.Mockito.never import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.times Loading Loading @@ -166,6 +168,12 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { mGroupChild1 = mHelper.createChildNotification(GROUP_ALERT_ALL, 1, "child", 350) mGroupChild1 = mHelper.createChildNotification(GROUP_ALERT_ALL, 1, "child", 350) mGroupChild2 = mHelper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250) mGroupChild2 = mHelper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250) mGroupChild3 = mHelper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150) mGroupChild3 = mHelper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150) // Set the default FSI decision setShouldFullScreen(any(), FullScreenIntentDecision.NO_FULL_SCREEN_INTENT) // Run tests with default feature flag state whenever(mFlags.fsiOnDNDUpdate()).thenReturn(Flags.FSI_ON_DND_UPDATE.default) } } @Test @Test Loading Loading @@ -809,6 +817,39 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { verify(mHeadsUpManager, never()).showNotification(any()) verify(mHeadsUpManager, never()).showNotification(any()) } } @Test fun onEntryAdded_whenLaunchingFSI_doesLogDecision() { // GIVEN A new notification can FSI setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) mCollectionListener.onEntryAdded(mEntry) verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry) verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision( mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) } @Test fun onEntryAdded_whenNotLaunchingFSI_doesLogDecision() { // GIVEN A new notification can't FSI setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT) mCollectionListener.onEntryAdded(mEntry) verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision( mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT) } @Test fun onEntryAdded_whenNotLaunchingFSIBecauseOfDnd_doesLogDecision() { // GIVEN A new notification can't FSI because of DND setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) mCollectionListener.onEntryAdded(mEntry) verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision( mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) } @Test @Test fun testOnRankingApplied_noFSIOnUpdateWhenFlagOff() { fun testOnRankingApplied_noFSIOnUpdateWhenFlagOff() { // Ensure the feature flag is off // Ensure the feature flag is off Loading @@ -818,13 +859,22 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) mCollectionListener.onEntryAdded(mEntry) mCollectionListener.onEntryAdded(mEntry) // Verify that this causes a log verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision( mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) clearInvocations(mNotificationInterruptStateProvider) // and it is then updated to allow full screen // and it is then updated to allow full screen setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) mCollectionListener.onRankingApplied() mCollectionListener.onRankingApplied() // THEN it should not full screen because the feature is off // THEN it should not full screen because the feature is off verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry) verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) // VERIFY that no additional logging happens either verify(mNotificationInterruptStateProvider, never()) .logFullScreenIntentDecision(any(), any()) } } @Test @Test Loading @@ -836,8 +886,11 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) mCollectionListener.onEntryAdded(mEntry) mCollectionListener.onEntryAdded(mEntry) // at this point, it should not have full screened // at this point, it should not have full screened, but should have logged verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry) verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) clearInvocations(mNotificationInterruptStateProvider) // and it is then updated to allow full screen AND HUN // and it is then updated to allow full screen AND HUN setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) Loading @@ -847,10 +900,110 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) // THEN it should full screen but it should NOT HUN // THEN it should full screen and log but it should NOT HUN verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry) verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry) verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) verify(mHeadsUpManager, never()).showNotification(any()) verify(mHeadsUpManager, never()).showNotification(any()) verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) clearInvocations(mNotificationInterruptStateProvider) // WHEN ranking updates again and the pipeline reruns clearInvocations(mLaunchFullScreenIntentProvider) mCollectionListener.onRankingApplied() mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) // VERIFY that the FSI does not launch again or log verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) verify(mNotificationInterruptStateProvider, never()) .logFullScreenIntentDecision(any(), any()) } @Test fun testOnRankingApplied_withOnlyDndSuppressionAllowsFsiLater() { // Turn on the feature whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true) // GIVEN that mEntry was previously suppressed from full-screen only by DND setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) mCollectionListener.onEntryAdded(mEntry) // at this point, it should not have full screened, but should have logged verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) clearInvocations(mNotificationInterruptStateProvider) // ranking is applied with only DND blocking FSI setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) mCollectionListener.onRankingApplied() mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) // THEN it should still not yet full screen or HUN verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) verify(mHeadsUpManager, never()).showNotification(any()) // Same decision as before; is not logged verify(mNotificationInterruptStateProvider, never()) .logFullScreenIntentDecision(any(), any()) clearInvocations(mNotificationInterruptStateProvider) // and it is then updated to allow full screen AND HUN setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) setShouldHeadsUp(mEntry) whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) mCollectionListener.onRankingApplied() mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) // THEN it should full screen and log but it should NOT HUN verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry) verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) verify(mHeadsUpManager, never()).showNotification(any()) verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) clearInvocations(mNotificationInterruptStateProvider) } @Test fun testOnRankingApplied_newNonFullScreenAnswerInvalidatesCandidate() { // Turn on the feature whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true) // GIVEN that mEntry was previously suppressed from full-screen only by DND whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) mCollectionListener.onEntryAdded(mEntry) // at this point, it should not have full screened verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry) // now some other condition blocks FSI in addition to DND setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND) mCollectionListener.onRankingApplied() mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) // THEN it should NOT full screen or HUN verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) verify(mHeadsUpManager, never()).showNotification(any()) // NOW the DND logic changes and FSI and HUN are available clearInvocations(mLaunchFullScreenIntentProvider) setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) setShouldHeadsUp(mEntry) mCollectionListener.onRankingApplied() mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) // VERIFY that the FSI didn't happen, but that we do HUN verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) finishBind(mEntry) verify(mHeadsUpManager).showNotification(mEntry) } } @Test @Test Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +15 −5 Original line number Original line Diff line number Diff line Loading @@ -389,11 +389,11 @@ class HeadsUpCoordinator @Inject constructor( // First check whether this notification should launch a full screen intent, and // First check whether this notification should launch a full screen intent, and // launch it if needed. // launch it if needed. val fsiDecision = mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry) val fsiDecision = mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry) if (fsiDecision != null && fsiDecision.shouldLaunch) { mNotificationInterruptStateProvider.logFullScreenIntentDecision(entry, fsiDecision) mNotificationInterruptStateProvider.logFullScreenIntentDecision(entry, fsiDecision) if (fsiDecision.shouldLaunch) { mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry) mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry) } else if (mFlags.fsiOnDNDUpdate() && } else if (mFlags.fsiOnDNDUpdate() && fsiDecision.equals(FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)) { fsiDecision == FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) { // If DND was the only reason this entry was suppressed, note it for potential // If DND was the only reason this entry was suppressed, note it for potential // reconsideration on later ranking updates. // reconsideration on later ranking updates. addForFSIReconsideration(entry, mSystemClock.currentTimeMillis()) addForFSIReconsideration(entry, mSystemClock.currentTimeMillis()) Loading Loading @@ -514,14 +514,24 @@ class HeadsUpCoordinator @Inject constructor( mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry) mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry) if (decision.shouldLaunch) { if (decision.shouldLaunch) { // Log both the launch of the full screen and also that this was via a // Log both the launch of the full screen and also that this was via a // ranking update. // ranking update, and finally revoke candidacy for FSI reconsideration mLogger.logEntryUpdatedToFullScreen(entry.key) mLogger.logEntryUpdatedToFullScreen(entry.key, decision.name) mNotificationInterruptStateProvider.logFullScreenIntentDecision( mNotificationInterruptStateProvider.logFullScreenIntentDecision( entry, decision) entry, decision) mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry) mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry) mFSIUpdateCandidates.remove(entry.key) // if we launch the FSI then this is no longer a candidate for HUN // if we launch the FSI then this is no longer a candidate for HUN continue continue } else if (decision == FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) { // decision has not changed; no need to log } else { // some other condition is now blocking FSI; log that and revoke candidacy // for FSI reconsideration mLogger.logEntryDisqualifiedFromFullScreen(entry.key, decision.name) mNotificationInterruptStateProvider.logFullScreenIntentDecision( entry, decision) mFSIUpdateCandidates.remove(entry.key) } } } } Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt +12 −2 Original line number Original line Diff line number Diff line Loading @@ -70,11 +70,21 @@ class HeadsUpCoordinatorLogger constructor( }) }) } } fun logEntryUpdatedToFullScreen(key: String) { fun logEntryUpdatedToFullScreen(key: String, reason: String) { buffer.log(TAG, LogLevel.DEBUG, { buffer.log(TAG, LogLevel.DEBUG, { str1 = key str1 = key str2 = reason }, { "updating entry to launch full screen intent: $str1 because $str2" }) } fun logEntryDisqualifiedFromFullScreen(key: String, reason: String) { buffer.log(TAG, LogLevel.DEBUG, { str1 = key str2 = reason }, { }, { "updating entry to launch full screen intent: $str1" "updated entry no longer qualifies for full screen intent: $str1 because $str2" }) }) } } Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java +4 −1 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.interruption; package com.android.systemui.statusbar.notification.interruption; import androidx.annotation.NonNull; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** /** Loading Loading @@ -153,7 +155,8 @@ public interface NotificationInterruptStateProvider { * @param entry the entry to evaluate * @param entry the entry to evaluate * @return FullScreenIntentDecision representing the decision for whether to show the intent * @return FullScreenIntentDecision representing the decision for whether to show the intent */ */ FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry); @NonNull FullScreenIntentDecision getFullScreenIntentDecision(@NonNull NotificationEntry entry); /** /** * Write the full screen launch decision for the given entry to logs. * Write the full screen launch decision for the given entry to logs. Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +15 −34 Original line number Original line Diff line number Diff line Loading @@ -35,6 +35,8 @@ import android.service.dreams.IDreamManager; import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification; import android.util.Log; import android.util.Log; import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger; Loading Loading @@ -232,6 +234,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter // suppressor. // suppressor. // // // If the entry was not suppressed by DND, just returns the given decision. // If the entry was not suppressed by DND, just returns the given decision. @NonNull private FullScreenIntentDecision getDecisionGivenSuppression(FullScreenIntentDecision decision, private FullScreenIntentDecision getDecisionGivenSuppression(FullScreenIntentDecision decision, boolean suppressedByDND) { boolean suppressedByDND) { if (suppressedByDND) { if (suppressedByDND) { Loading @@ -243,7 +246,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } } @Override @Override public FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry) { public FullScreenIntentDecision getFullScreenIntentDecision(@NonNull NotificationEntry entry) { if (entry.getSbn().getNotification().fullScreenIntent == null) { if (entry.getSbn().getNotification().fullScreenIntent == null) { if (entry.isStickyAndNotDemoted()) { if (entry.isStickyAndNotDemoted()) { return FullScreenIntentDecision.NO_FSI_SHOW_STICKY_HUN; return FullScreenIntentDecision.NO_FSI_SHOW_STICKY_HUN; Loading Loading @@ -336,52 +339,30 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter final int uid = entry.getSbn().getUid(); final int uid = entry.getSbn().getUid(); final String packageName = entry.getSbn().getPackageName(); final String packageName = entry.getSbn().getPackageName(); switch (decision) { switch (decision) { case NO_FSI_SHOW_STICKY_HUN: mLogger.logNoFullscreen(entry, "Permission denied, show sticky HUN"); return; case NO_FULL_SCREEN_INTENT: case NO_FULL_SCREEN_INTENT: return; // explicitly prevent logging for this (frequent) case case NO_FSI_SUPPRESSED_BY_DND: case NO_FSI_SUPPRESSED_ONLY_BY_DND: mLogger.logNoFullscreen(entry, "Suppressed by DND"); return; case NO_FSI_NOT_IMPORTANT_ENOUGH: mLogger.logNoFullscreen(entry, "Not important enough"); return; return; case NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR: case NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR: android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "groupAlertBehavior"); "groupAlertBehavior"); mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid, mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid, packageName); packageName); mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN"); mLogger.logNoFullscreenWarning(entry, return; decision + ": GroupAlertBehavior will prevent HUN"); case FSI_DEVICE_NOT_INTERACTIVE: mLogger.logFullscreen(entry, "Device is not interactive"); return; case FSI_DEVICE_IS_DREAMING: mLogger.logFullscreen(entry, "Device is dreaming"); return; case FSI_KEYGUARD_SHOWING: mLogger.logFullscreen(entry, "Keyguard is showing"); return; case NO_FSI_EXPECTED_TO_HUN: mLogger.logNoFullscreen(entry, "Expected to HUN"); return; case FSI_KEYGUARD_OCCLUDED: mLogger.logFullscreen(entry, "Expected not to HUN while keyguard occluded"); return; case FSI_LOCKED_SHADE: mLogger.logFullscreen(entry, "Keyguard is showing and not occluded"); return; return; case NO_FSI_NO_HUN_OR_KEYGUARD: case NO_FSI_NO_HUN_OR_KEYGUARD: android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "no hun or keyguard"); "no hun or keyguard"); mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName); mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName); mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard"); mLogger.logNoFullscreenWarning(entry, decision + ": Expected not to HUN while not on keyguard"); return; return; case FSI_EXPECTED_NOT_TO_HUN: default: mLogger.logFullscreen(entry, "Expected not to HUN"); if (decision.shouldLaunch) { mLogger.logFullscreen(entry, decision.name()); } else { mLogger.logNoFullscreen(entry, decision.name()); } } } } } Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +157 −4 Original line number Original line Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.Flags import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder Loading Loading @@ -58,6 +59,7 @@ import org.junit.Before import org.junit.Test import org.junit.Test import org.junit.runner.RunWith import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyString import org.mockito.ArgumentMatchers.anyString import org.mockito.BDDMockito.clearInvocations import org.mockito.BDDMockito.given import org.mockito.BDDMockito.given import org.mockito.Mockito.never import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.times Loading Loading @@ -166,6 +168,12 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { mGroupChild1 = mHelper.createChildNotification(GROUP_ALERT_ALL, 1, "child", 350) mGroupChild1 = mHelper.createChildNotification(GROUP_ALERT_ALL, 1, "child", 350) mGroupChild2 = mHelper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250) mGroupChild2 = mHelper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250) mGroupChild3 = mHelper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150) mGroupChild3 = mHelper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150) // Set the default FSI decision setShouldFullScreen(any(), FullScreenIntentDecision.NO_FULL_SCREEN_INTENT) // Run tests with default feature flag state whenever(mFlags.fsiOnDNDUpdate()).thenReturn(Flags.FSI_ON_DND_UPDATE.default) } } @Test @Test Loading Loading @@ -809,6 +817,39 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { verify(mHeadsUpManager, never()).showNotification(any()) verify(mHeadsUpManager, never()).showNotification(any()) } } @Test fun onEntryAdded_whenLaunchingFSI_doesLogDecision() { // GIVEN A new notification can FSI setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) mCollectionListener.onEntryAdded(mEntry) verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry) verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision( mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) } @Test fun onEntryAdded_whenNotLaunchingFSI_doesLogDecision() { // GIVEN A new notification can't FSI setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT) mCollectionListener.onEntryAdded(mEntry) verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision( mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT) } @Test fun onEntryAdded_whenNotLaunchingFSIBecauseOfDnd_doesLogDecision() { // GIVEN A new notification can't FSI because of DND setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) mCollectionListener.onEntryAdded(mEntry) verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision( mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) } @Test @Test fun testOnRankingApplied_noFSIOnUpdateWhenFlagOff() { fun testOnRankingApplied_noFSIOnUpdateWhenFlagOff() { // Ensure the feature flag is off // Ensure the feature flag is off Loading @@ -818,13 +859,22 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) mCollectionListener.onEntryAdded(mEntry) mCollectionListener.onEntryAdded(mEntry) // Verify that this causes a log verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision( mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) clearInvocations(mNotificationInterruptStateProvider) // and it is then updated to allow full screen // and it is then updated to allow full screen setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) mCollectionListener.onRankingApplied() mCollectionListener.onRankingApplied() // THEN it should not full screen because the feature is off // THEN it should not full screen because the feature is off verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry) verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) // VERIFY that no additional logging happens either verify(mNotificationInterruptStateProvider, never()) .logFullScreenIntentDecision(any(), any()) } } @Test @Test Loading @@ -836,8 +886,11 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) mCollectionListener.onEntryAdded(mEntry) mCollectionListener.onEntryAdded(mEntry) // at this point, it should not have full screened // at this point, it should not have full screened, but should have logged verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry) verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) clearInvocations(mNotificationInterruptStateProvider) // and it is then updated to allow full screen AND HUN // and it is then updated to allow full screen AND HUN setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) Loading @@ -847,10 +900,110 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) // THEN it should full screen but it should NOT HUN // THEN it should full screen and log but it should NOT HUN verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry) verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry) verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) verify(mHeadsUpManager, never()).showNotification(any()) verify(mHeadsUpManager, never()).showNotification(any()) verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) clearInvocations(mNotificationInterruptStateProvider) // WHEN ranking updates again and the pipeline reruns clearInvocations(mLaunchFullScreenIntentProvider) mCollectionListener.onRankingApplied() mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) // VERIFY that the FSI does not launch again or log verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) verify(mNotificationInterruptStateProvider, never()) .logFullScreenIntentDecision(any(), any()) } @Test fun testOnRankingApplied_withOnlyDndSuppressionAllowsFsiLater() { // Turn on the feature whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true) // GIVEN that mEntry was previously suppressed from full-screen only by DND setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) mCollectionListener.onEntryAdded(mEntry) // at this point, it should not have full screened, but should have logged verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) clearInvocations(mNotificationInterruptStateProvider) // ranking is applied with only DND blocking FSI setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) mCollectionListener.onRankingApplied() mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) // THEN it should still not yet full screen or HUN verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) verify(mHeadsUpManager, never()).showNotification(any()) // Same decision as before; is not logged verify(mNotificationInterruptStateProvider, never()) .logFullScreenIntentDecision(any(), any()) clearInvocations(mNotificationInterruptStateProvider) // and it is then updated to allow full screen AND HUN setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) setShouldHeadsUp(mEntry) whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) mCollectionListener.onRankingApplied() mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) // THEN it should full screen and log but it should NOT HUN verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry) verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) verify(mHeadsUpManager, never()).showNotification(any()) verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) clearInvocations(mNotificationInterruptStateProvider) } @Test fun testOnRankingApplied_newNonFullScreenAnswerInvalidatesCandidate() { // Turn on the feature whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true) // GIVEN that mEntry was previously suppressed from full-screen only by DND whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) mCollectionListener.onEntryAdded(mEntry) // at this point, it should not have full screened verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry) // now some other condition blocks FSI in addition to DND setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND) mCollectionListener.onRankingApplied() mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) // THEN it should NOT full screen or HUN verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) verify(mHeadsUpManager, never()).showNotification(any()) // NOW the DND logic changes and FSI and HUN are available clearInvocations(mLaunchFullScreenIntentProvider) setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) setShouldHeadsUp(mEntry) mCollectionListener.onRankingApplied() mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) // VERIFY that the FSI didn't happen, but that we do HUN verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) finishBind(mEntry) verify(mHeadsUpManager).showNotification(mEntry) } } @Test @Test Loading