Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +24 −2 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.Nullable; import android.app.Notification; import android.os.RemoteException; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; Loading Loading @@ -239,8 +240,7 @@ public class NotifCollection implements Dumpable { // Also mark any children as dismissed as system server will auto-dismiss them as well if (entry.getSbn().getNotification().isGroupSummary()) { for (NotificationEntry otherEntry : mNotificationSet.values()) { if (otherEntry.getSbn().getGroupKey().equals(entry.getSbn().getGroupKey()) && otherEntry.getDismissState() != DISMISSED) { if (shouldAutoDismiss(otherEntry, entry.getSbn().getGroupKey())) { otherEntry.setDismissState(PARENT_DISMISSED); if (isCanceled(otherEntry)) { canceledEntries.add(otherEntry); Loading Loading @@ -544,6 +544,28 @@ public class NotifCollection implements Dumpable { return entry.getDismissState() != NOT_DISMISSED; } /** * When a group summary is dismissed, NotificationManager will also try to dismiss its children. * Returns true if we think dismissing the group summary with group key * <code>dismissedGroupKey</code> will cause NotificationManager to also dismiss * <code>entry</code>. * * See NotificationManager.cancelGroupChildrenByListLocked() for corresponding code. */ private static boolean shouldAutoDismiss( NotificationEntry entry, String dismissedGroupKey) { return entry.getSbn().getGroupKey().equals(dismissedGroupKey) && !entry.getSbn().getNotification().isGroupSummary() && !hasFlag(entry, Notification.FLAG_FOREGROUND_SERVICE) && !hasFlag(entry, Notification.FLAG_BUBBLE) && entry.getDismissState() != DISMISSED; } private static boolean hasFlag(NotificationEntry entry, int flag) { return (entry.getSbn().getNotification().flags & flag) != 0; } private void dispatchOnEntryInit(NotificationEntry entry) { mAmDispatchingToOtherCode = true; for (NotifCollectionListener listener : mNotifCollectionListeners) { Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java +5 −0 Original line number Diff line number Diff line Loading @@ -140,6 +140,11 @@ public class SbnBuilder { return this; } public SbnBuilder setFlag(Context context, int mask, boolean value) { modifyNotification(context).setFlag(mask, value); return this; } public Notification.Builder modifyNotification(Context context) { if (mNotification != null) { mNotificationBuilder = new Notification.Builder(context, mNotification); Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +103 −0 Original line number Diff line number Diff line Loading @@ -46,9 +46,12 @@ import static org.mockito.Mockito.when; import static java.util.Objects.requireNonNull; import android.annotation.Nullable; import android.app.Notification; import android.os.RemoteException; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArrayMap; Loading Loading @@ -733,6 +736,78 @@ public class NotifCollectionTest extends SysuiTestCase { assertEquals(NOT_DISMISSED, entry1.getDismissState()); } @Test public void testDismissingSummaryDoesNotDismissForegroundServiceChildren() { // GIVEN a collection with three grouped notifs in it CollectionEvent notif0 = postNotif( buildNotif(TEST_PACKAGE, 0) .setGroup(mContext, GROUP_1) .setGroupSummary(mContext, true)); CollectionEvent notif1 = postNotif( buildNotif(TEST_PACKAGE, 1) .setGroup(mContext, GROUP_1) .setFlag(mContext, Notification.FLAG_FOREGROUND_SERVICE, true)); CollectionEvent notif2 = postNotif( buildNotif(TEST_PACKAGE, 2) .setGroup(mContext, GROUP_1)); // WHEN the summary is dismissed mCollection.dismissNotification(notif0.entry, defaultStats(notif0.entry)); // THEN the foreground service child is not dismissed assertEquals(DISMISSED, notif0.entry.getDismissState()); assertEquals(NOT_DISMISSED, notif1.entry.getDismissState()); assertEquals(PARENT_DISMISSED, notif2.entry.getDismissState()); } @Test public void testDismissingSummaryDoesNotDismissBubbledChildren() { // GIVEN a collection with three grouped notifs in it CollectionEvent notif0 = postNotif( buildNotif(TEST_PACKAGE, 0) .setGroup(mContext, GROUP_1) .setGroupSummary(mContext, true)); CollectionEvent notif1 = postNotif( buildNotif(TEST_PACKAGE, 1) .setGroup(mContext, GROUP_1) .setFlag(mContext, Notification.FLAG_BUBBLE, true)); CollectionEvent notif2 = postNotif( buildNotif(TEST_PACKAGE, 2) .setGroup(mContext, GROUP_1)); // WHEN the summary is dismissed mCollection.dismissNotification(notif0.entry, defaultStats(notif0.entry)); // THEN the bubbled child is not dismissed assertEquals(DISMISSED, notif0.entry.getDismissState()); assertEquals(NOT_DISMISSED, notif1.entry.getDismissState()); assertEquals(PARENT_DISMISSED, notif2.entry.getDismissState()); } @Test public void testDismissingSummaryDoesNotDismissDuplicateSummaries() { // GIVEN a group with a two summaries CollectionEvent notif0 = postNotif( buildNotif(TEST_PACKAGE, 0) .setGroup(mContext, GROUP_1) .setGroupSummary(mContext, true)); CollectionEvent notif1 = postNotif( buildNotif(TEST_PACKAGE, 1) .setGroup(mContext, GROUP_1) .setGroupSummary(mContext, true)); CollectionEvent notif2 = postNotif( buildNotif(TEST_PACKAGE, 2) .setGroup(mContext, GROUP_1)); // WHEN the first summary is dismissed mCollection.dismissNotification(notif0.entry, defaultStats(notif0.entry)); // THEN the second summary is not auto-dismissed (but the child is) assertEquals(DISMISSED, notif0.entry.getDismissState()); assertEquals(NOT_DISMISSED, notif1.entry.getDismissState()); assertEquals(PARENT_DISMISSED, notif2.entry.getDismissState()); } @Test public void testLifetimeExtendersAreQueriedWhenNotifRemoved() { // GIVEN a couple notifications and a few lifetime extenders Loading Loading @@ -1000,6 +1075,13 @@ public class NotifCollectionTest extends SysuiTestCase { NotificationVisibility.obtain(entry.getKey(), 7, 2, true)); } public CollectionEvent postNotif(NotificationEntryBuilder builder) { clearInvocations(mCollectionListener); NotifEvent rawEvent = mNoMan.postNotif(builder); verify(mCollectionListener).onEntryAdded(mEntryCaptor.capture()); return new CollectionEvent(rawEvent, requireNonNull(mEntryCaptor.getValue())); } private static class RecordingCollectionListener implements NotifCollectionListener { private final Map<String, NotificationEntry> mLastSeenEntries = new ArrayMap<>(); Loading @@ -1014,6 +1096,7 @@ public class NotifCollectionTest extends SysuiTestCase { @Override public void onEntryUpdated(NotificationEntry entry) { mLastSeenEntries.put(entry.getKey(), entry); } @Override Loading Loading @@ -1098,6 +1181,26 @@ public class NotifCollectionTest extends SysuiTestCase { } } /** * Wrapper around {@link NotifEvent} that adds the NotificationEntry that the collection under * test creates. */ private static class CollectionEvent { public final String key; public final StatusBarNotification sbn; public final Ranking ranking; public final RankingMap rankingMap; public final NotificationEntry entry; private CollectionEvent(NotifEvent rawEvent, NotificationEntry entry) { this.key = rawEvent.key; this.sbn = rawEvent.sbn; this.ranking = rawEvent.ranking; this.rankingMap = rawEvent.rankingMap; this.entry = entry; } } private static final String TEST_PACKAGE = "com.android.test.collection"; private static final String TEST_PACKAGE2 = "com.android.test.collection2"; Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java +5 −0 Original line number Diff line number Diff line Loading @@ -162,6 +162,11 @@ public class NotificationEntryBuilder { return this; } public NotificationEntryBuilder setFlag(Context context, int mask, boolean value) { mSbnBuilder.setFlag(context, mask, value); return this; } /* Delegated to RankingBuilder */ public NotificationEntryBuilder setRank(int rank) { Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +24 −2 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.Nullable; import android.app.Notification; import android.os.RemoteException; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; Loading Loading @@ -239,8 +240,7 @@ public class NotifCollection implements Dumpable { // Also mark any children as dismissed as system server will auto-dismiss them as well if (entry.getSbn().getNotification().isGroupSummary()) { for (NotificationEntry otherEntry : mNotificationSet.values()) { if (otherEntry.getSbn().getGroupKey().equals(entry.getSbn().getGroupKey()) && otherEntry.getDismissState() != DISMISSED) { if (shouldAutoDismiss(otherEntry, entry.getSbn().getGroupKey())) { otherEntry.setDismissState(PARENT_DISMISSED); if (isCanceled(otherEntry)) { canceledEntries.add(otherEntry); Loading Loading @@ -544,6 +544,28 @@ public class NotifCollection implements Dumpable { return entry.getDismissState() != NOT_DISMISSED; } /** * When a group summary is dismissed, NotificationManager will also try to dismiss its children. * Returns true if we think dismissing the group summary with group key * <code>dismissedGroupKey</code> will cause NotificationManager to also dismiss * <code>entry</code>. * * See NotificationManager.cancelGroupChildrenByListLocked() for corresponding code. */ private static boolean shouldAutoDismiss( NotificationEntry entry, String dismissedGroupKey) { return entry.getSbn().getGroupKey().equals(dismissedGroupKey) && !entry.getSbn().getNotification().isGroupSummary() && !hasFlag(entry, Notification.FLAG_FOREGROUND_SERVICE) && !hasFlag(entry, Notification.FLAG_BUBBLE) && entry.getDismissState() != DISMISSED; } private static boolean hasFlag(NotificationEntry entry, int flag) { return (entry.getSbn().getNotification().flags & flag) != 0; } private void dispatchOnEntryInit(NotificationEntry entry) { mAmDispatchingToOtherCode = true; for (NotifCollectionListener listener : mNotifCollectionListeners) { Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java +5 −0 Original line number Diff line number Diff line Loading @@ -140,6 +140,11 @@ public class SbnBuilder { return this; } public SbnBuilder setFlag(Context context, int mask, boolean value) { modifyNotification(context).setFlag(mask, value); return this; } public Notification.Builder modifyNotification(Context context) { if (mNotification != null) { mNotificationBuilder = new Notification.Builder(context, mNotification); Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +103 −0 Original line number Diff line number Diff line Loading @@ -46,9 +46,12 @@ import static org.mockito.Mockito.when; import static java.util.Objects.requireNonNull; import android.annotation.Nullable; import android.app.Notification; import android.os.RemoteException; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArrayMap; Loading Loading @@ -733,6 +736,78 @@ public class NotifCollectionTest extends SysuiTestCase { assertEquals(NOT_DISMISSED, entry1.getDismissState()); } @Test public void testDismissingSummaryDoesNotDismissForegroundServiceChildren() { // GIVEN a collection with three grouped notifs in it CollectionEvent notif0 = postNotif( buildNotif(TEST_PACKAGE, 0) .setGroup(mContext, GROUP_1) .setGroupSummary(mContext, true)); CollectionEvent notif1 = postNotif( buildNotif(TEST_PACKAGE, 1) .setGroup(mContext, GROUP_1) .setFlag(mContext, Notification.FLAG_FOREGROUND_SERVICE, true)); CollectionEvent notif2 = postNotif( buildNotif(TEST_PACKAGE, 2) .setGroup(mContext, GROUP_1)); // WHEN the summary is dismissed mCollection.dismissNotification(notif0.entry, defaultStats(notif0.entry)); // THEN the foreground service child is not dismissed assertEquals(DISMISSED, notif0.entry.getDismissState()); assertEquals(NOT_DISMISSED, notif1.entry.getDismissState()); assertEquals(PARENT_DISMISSED, notif2.entry.getDismissState()); } @Test public void testDismissingSummaryDoesNotDismissBubbledChildren() { // GIVEN a collection with three grouped notifs in it CollectionEvent notif0 = postNotif( buildNotif(TEST_PACKAGE, 0) .setGroup(mContext, GROUP_1) .setGroupSummary(mContext, true)); CollectionEvent notif1 = postNotif( buildNotif(TEST_PACKAGE, 1) .setGroup(mContext, GROUP_1) .setFlag(mContext, Notification.FLAG_BUBBLE, true)); CollectionEvent notif2 = postNotif( buildNotif(TEST_PACKAGE, 2) .setGroup(mContext, GROUP_1)); // WHEN the summary is dismissed mCollection.dismissNotification(notif0.entry, defaultStats(notif0.entry)); // THEN the bubbled child is not dismissed assertEquals(DISMISSED, notif0.entry.getDismissState()); assertEquals(NOT_DISMISSED, notif1.entry.getDismissState()); assertEquals(PARENT_DISMISSED, notif2.entry.getDismissState()); } @Test public void testDismissingSummaryDoesNotDismissDuplicateSummaries() { // GIVEN a group with a two summaries CollectionEvent notif0 = postNotif( buildNotif(TEST_PACKAGE, 0) .setGroup(mContext, GROUP_1) .setGroupSummary(mContext, true)); CollectionEvent notif1 = postNotif( buildNotif(TEST_PACKAGE, 1) .setGroup(mContext, GROUP_1) .setGroupSummary(mContext, true)); CollectionEvent notif2 = postNotif( buildNotif(TEST_PACKAGE, 2) .setGroup(mContext, GROUP_1)); // WHEN the first summary is dismissed mCollection.dismissNotification(notif0.entry, defaultStats(notif0.entry)); // THEN the second summary is not auto-dismissed (but the child is) assertEquals(DISMISSED, notif0.entry.getDismissState()); assertEquals(NOT_DISMISSED, notif1.entry.getDismissState()); assertEquals(PARENT_DISMISSED, notif2.entry.getDismissState()); } @Test public void testLifetimeExtendersAreQueriedWhenNotifRemoved() { // GIVEN a couple notifications and a few lifetime extenders Loading Loading @@ -1000,6 +1075,13 @@ public class NotifCollectionTest extends SysuiTestCase { NotificationVisibility.obtain(entry.getKey(), 7, 2, true)); } public CollectionEvent postNotif(NotificationEntryBuilder builder) { clearInvocations(mCollectionListener); NotifEvent rawEvent = mNoMan.postNotif(builder); verify(mCollectionListener).onEntryAdded(mEntryCaptor.capture()); return new CollectionEvent(rawEvent, requireNonNull(mEntryCaptor.getValue())); } private static class RecordingCollectionListener implements NotifCollectionListener { private final Map<String, NotificationEntry> mLastSeenEntries = new ArrayMap<>(); Loading @@ -1014,6 +1096,7 @@ public class NotifCollectionTest extends SysuiTestCase { @Override public void onEntryUpdated(NotificationEntry entry) { mLastSeenEntries.put(entry.getKey(), entry); } @Override Loading Loading @@ -1098,6 +1181,26 @@ public class NotifCollectionTest extends SysuiTestCase { } } /** * Wrapper around {@link NotifEvent} that adds the NotificationEntry that the collection under * test creates. */ private static class CollectionEvent { public final String key; public final StatusBarNotification sbn; public final Ranking ranking; public final RankingMap rankingMap; public final NotificationEntry entry; private CollectionEvent(NotifEvent rawEvent, NotificationEntry entry) { this.key = rawEvent.key; this.sbn = rawEvent.sbn; this.ranking = rawEvent.ranking; this.rankingMap = rawEvent.rankingMap; this.entry = entry; } } private static final String TEST_PACKAGE = "com.android.test.collection"; private static final String TEST_PACKAGE2 = "com.android.test.collection2"; Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java +5 −0 Original line number Diff line number Diff line Loading @@ -162,6 +162,11 @@ public class NotificationEntryBuilder { return this; } public NotificationEntryBuilder setFlag(Context context, int mask, boolean value) { mSbnBuilder.setFlag(context, mask, value); return this; } /* Delegated to RankingBuilder */ public NotificationEntryBuilder setRank(int rank) { Loading