diff options
author | 2024-10-09 15:54:08 +0000 | |
---|---|---|
committer | 2024-10-23 15:50:00 +0000 | |
commit | 0248b5c81e77966bb8ffbac498a60f2bfd6c00c5 (patch) | |
tree | 195207dae26c70e4b37e2fc3cebfca23cb6399a6 | |
parent | 4eb9e0faf7ddd555e8918be097941e66785e91fd (diff) |
NotifCollection.dismissNotifications will now remove hidden summaries.
Previously we would take the list of notifications exactly, but that left room that a notification's summary, if it was not included in the list, would be left in the shade. We now check if each entry is the sole logic child of a single summary, and if so we include that summary in the dismissal (assuming it was not already included), and generate the necessary stats object.
Bug: 355967751
Flag: com.android.systemui.notifications_dismiss_pruned_summaries
Test: atest NotifCollectionTest
Change-Id: Id3eda2f7a36227e4d5a921888735dd898d33a61a
3 files changed, 97 insertions, 11 deletions
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 1c29db128a8c..2047919eb9f8 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -129,6 +129,13 @@ flag { } flag { + name: "notifications_dismiss_pruned_summaries" + namespace: "systemui" + description: "NotifCollection.dismissNotifications will now dismiss summaries that are pruned from the shade." + bug: "355967751" +} + +flag { name: "notification_transparent_header_fix" namespace: "systemui" description: "fix the transparent group header issue for async header inflation." diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 7b3a93a4a094..b5c6c252e50f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -39,6 +39,7 @@ import static android.service.notification.NotificationListenerService.REASON_TI import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED; import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED; +import static com.android.systemui.Flags.notificationsDismissPrunedSummaries; import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.DISMISSED; import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED; @@ -69,6 +70,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -111,6 +113,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -277,6 +280,10 @@ public class NotifCollection implements Dumpable, PipelineDumpable { Assert.isMainThread(); checkForReentrantCall(); + if (notificationsDismissPrunedSummaries()) { + entriesToDismiss = includeSummariesToDismiss(entriesToDismiss); + } + final int entryCount = entriesToDismiss.size(); final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>(); for (int i = 0; i < entriesToDismiss.size(); i++) { @@ -336,6 +343,36 @@ public class NotifCollection implements Dumpable, PipelineDumpable { dispatchEventsAndRebuildList("dismissNotifications"); } + private List<Pair<NotificationEntry, DismissedByUserStats>> includeSummariesToDismiss( + List<Pair<NotificationEntry, DismissedByUserStats>> entriesToDismiss) { + final HashSet<NotificationEntry> entriesSet = new HashSet<>(entriesToDismiss.size()); + for (Pair<NotificationEntry, DismissedByUserStats> entryToStats : entriesToDismiss) { + entriesSet.add(entryToStats.first); + } + + final List<Pair<NotificationEntry, DismissedByUserStats>> entriesPlusSummaries = + new ArrayList<>(entriesToDismiss.size() + 1); + for (Pair<NotificationEntry, DismissedByUserStats> entryToStats : entriesToDismiss) { + entriesPlusSummaries.add(entryToStats); + NotificationEntry summary = fetchSummaryToDismiss(entryToStats.first); + if (summary != null && !entriesSet.contains(summary)) { + DismissedByUserStats currentStats = entryToStats.second; + NotificationVisibility summaryVisibility = NotificationVisibility.obtain( + summary.getKey(), + summary.getRanking().getRank(), + currentStats.notificationVisibility.count, + /* visible= */ false); + DismissedByUserStats summaryStats = new DismissedByUserStats( + currentStats.dismissalSurface, + currentStats.dismissalSentiment, + summaryVisibility + ); + entriesPlusSummaries.add(new Pair<>(summary, summaryStats)); + } + } + return entriesPlusSummaries; + } + /** * Dismisses a single notification on behalf of the user. */ @@ -1062,6 +1099,16 @@ public class NotifCollection implements Dumpable, PipelineDumpable { } } + @Nullable + private NotificationEntry fetchSummaryToDismiss(NotificationEntry entry) { + if (isOnlyChildInGroup(entry)) { + String group = entry.getSbn().getGroupKey(); + NotificationEntry summary = getGroupSummary(group); + if (summary != null && isDismissable(summary)) return summary; + } + return null; + } + /** A single method interface that callers can pass in when registering future dismissals */ public interface DismissedByUserStatsCreator { DismissedByUserStats createDismissedByUserStats(NotificationEntry entry); @@ -1092,16 +1139,6 @@ public class NotifCollection implements Dumpable, PipelineDumpable { + ">"; } - @Nullable - private NotificationEntry fetchSummaryToDismiss(NotificationEntry entry) { - if (isOnlyChildInGroup(entry)) { - String group = entry.getSbn().getGroupKey(); - NotificationEntry summary = getGroupSummary(group); - if (summary != null && isDismissable(summary)) return summary; - } - return null; - } - /** called when the entry has been removed from the collection */ public void onSystemServerCancel(@CancellationReason int cancellationReason) { Assert.isMainThread(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 2cf599a99c63..3893c9b05bd3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -45,6 +45,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -63,6 +64,7 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.os.Handler; import android.os.RemoteException; +import android.platform.test.annotations.EnableFlags; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; @@ -77,6 +79,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.LogBufferEulogizer; @@ -129,6 +132,7 @@ public class NotifCollectionTest extends SysuiTestCase { @Mock private GroupCoalescer mGroupCoalescer; @Spy private RecordingCollectionListener mCollectionListener; @Mock private CollectionReadyForBuildListener mBuildListener; + @Mock private NotificationDismissibilityProvider mDismissibilityProvider; @Spy private RecordingLifetimeExtender mExtender1 = new RecordingLifetimeExtender("Extender1"); @Spy private RecordingLifetimeExtender mExtender2 = new RecordingLifetimeExtender("Extender2"); @@ -160,6 +164,7 @@ public class NotifCollectionTest extends SysuiTestCase { allowTestableLooperAsMainThread(); when(mEulogizer.record(any(Exception.class))).thenAnswer(i -> i.getArguments()[0]); + doReturn(Boolean.TRUE).when(mDismissibilityProvider).isDismissable(any()); mListenerInOrder = inOrder(mCollectionListener); @@ -172,7 +177,7 @@ public class NotifCollectionTest extends SysuiTestCase { mBgExecutor, mEulogizer, mock(DumpManager.class), - mock(NotificationDismissibilityProvider.class)); + mDismissibilityProvider); mCollection.attach(mGroupCoalescer); mCollection.addCollectionListener(mCollectionListener); mCollection.setBuildListener(mBuildListener); @@ -1379,6 +1384,43 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_NOTIFICATIONS_DISMISS_PRUNED_SUMMARIES) + public void testDismissNotificationsIncludesPrunedParents() { + // GIVEN a collection with 2 groups; one has a single child, one has two. + mCollection.addNotificationDismissInterceptor(mInterceptor1); + + NotifEvent notif1summary = mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 1, "notif1summary").setGroup(mContext, "group1") + .setGroupSummary(mContext, true)); + NotifEvent notif1child = mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 1, "notif1child").setGroup(mContext, "group1")); + NotifEvent notif2summary = mNoMan.postNotif( + buildNotif(TEST_PACKAGE2, 2, "notif2summary").setGroup(mContext, "group2") + .setGroupSummary(mContext, true)); + NotifEvent notif2child1 = mNoMan.postNotif( + buildNotif(TEST_PACKAGE2, 2, "notif2child1").setGroup(mContext, "group2")); + NotifEvent notif2child2 = mNoMan.postNotif( + buildNotif(TEST_PACKAGE2, 2, "notif2child2").setGroup(mContext, "group2")); + NotificationEntry entry1summary = mCollectionListener.getEntry(notif1summary.key); + NotificationEntry entry1child = mCollectionListener.getEntry(notif1child.key); + NotificationEntry entry2summary = mCollectionListener.getEntry(notif2summary.key); + NotificationEntry entry2child1 = mCollectionListener.getEntry(notif2child1.key); + NotificationEntry entry2child2 = mCollectionListener.getEntry(notif2child2.key); + + // WHEN one child from each group are manually dismissed together + mCollection.dismissNotifications( + List.of(new Pair<>(entry1child, defaultStats(entry1child)), + new Pair<>(entry2child1, defaultStats(entry2child1)))); + + // THEN the summary for the singleton child is dismissed, but not the other summary + verify(mInterceptor1).shouldInterceptDismissal(entry1summary); + verify(mInterceptor1).shouldInterceptDismissal(entry1child); + verify(mInterceptor1, never()).shouldInterceptDismissal(entry2summary); + verify(mInterceptor1).shouldInterceptDismissal(entry2child1); + verify(mInterceptor1, never()).shouldInterceptDismissal(entry2child2); + } + + @Test public void testDismissAllNotificationsCallsRebuildOnce() { // GIVEN a collection with a couple notifications NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); |