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 38d8d979a4dae..ec3285f2b2411 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
@@ -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;
@@ -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);
@@ -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
+ * dismissedGroupKey will cause NotificationManager to also dismiss
+ * entry.
+ *
+ * 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) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java
index 26054027ed5fa..62f406ff835ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java
@@ -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);
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 abc0f3ee8a527..96db16adb7dc6 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
@@ -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;
@@ -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
@@ -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 mLastSeenEntries = new ArrayMap<>();
@@ -1014,6 +1096,7 @@ public class NotifCollectionTest extends SysuiTestCase {
@Override
public void onEntryUpdated(NotificationEntry entry) {
+ mLastSeenEntries.put(entry.getKey(), entry);
}
@Override
@@ -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";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
index 300ec189e31df..d2bb0119c0b34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
@@ -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) {