Merge ""Clear all" causes auto-grouped persistent notifications losing the group summary"
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index b1cd627..9cb8a01 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -16,9 +16,13 @@
package com.android.server.notification;
import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -37,6 +41,11 @@
private final Callback mCallback;
private final int mAutoGroupAtCount;
+ // count the number of ongoing notifications per group
+ // userId -> (package name -> (group Id -> (set of notification keys)))
+ final ArrayMap<String, ArraySet<String>>
+ mOngoingGroupCount = new ArrayMap<>();
+
// Map of user : <Map of package : notification keys>. Only contains notifications that are not
// grouped by the app (aka no group or sort key).
Map<Integer, Map<String, LinkedHashSet<String>>> mUngroupedNotifications = new HashMap<>();
@@ -46,10 +55,52 @@
mCallback = callback;
}
+ private String generatePackageGroupKey(int userId, String pkg, String group) {
+ return userId + "|" + pkg + "|" + group;
+ }
+
+ @VisibleForTesting
+ protected int getOngoingGroupCount(int userId, String pkg, String group) {
+ String key = generatePackageGroupKey(userId, pkg, group);
+ return mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0)).size();
+ }
+
+ private void addToOngoingGroupCount(StatusBarNotification sbn, boolean add) {
+ if (sbn.getNotification().isGroupSummary()) return;
+ if (!sbn.isOngoing() && add) return;
+ String group = sbn.getGroup();
+ if (group == null) return;
+ int userId = sbn.getUser().getIdentifier();
+ String key = generatePackageGroupKey(userId, sbn.getPackageName(), group);
+ ArraySet<String> notifications = mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0));
+ if (add) {
+ notifications.add(sbn.getKey());
+ mOngoingGroupCount.put(key, notifications);
+ } else {
+ notifications.remove(sbn.getKey());
+ // we dont need to put it back if it is default
+ }
+ String combinedKey = generatePackageGroupKey(userId, sbn.getPackageName(), group);
+ boolean needsOngoingFlag = notifications.size() > 0;
+ mCallback.updateAutogroupSummary(sbn.getKey(), needsOngoingFlag);
+ }
+
+ public void onNotificationUpdated(StatusBarNotification childSbn,
+ boolean autogroupSummaryExists) {
+ if (childSbn.getGroup() != AUTOGROUP_KEY
+ || childSbn.getNotification().isGroupSummary()) return;
+ if (childSbn.isOngoing()) {
+ addToOngoingGroupCount(childSbn, true);
+ } else {
+ addToOngoingGroupCount(childSbn, false);
+ }
+ }
+
public void onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) {
if (DEBUG) Log.i(TAG, "POSTED " + sbn.getKey());
try {
List<String> notificationsToGroup = new ArrayList<>();
+ if (autogroupSummaryExists) addToOngoingGroupCount(sbn, true);
if (!sbn.isAppGroup()) {
// Not grouped by the app, add to the list of notifications for the app;
// send grouping update if app exceeds the autogrouping limit.
@@ -90,6 +141,7 @@
public void onNotificationRemoved(StatusBarNotification sbn) {
try {
+ addToOngoingGroupCount(sbn, false);
maybeUngroup(sbn, true, sbn.getUserId());
} catch (Exception e) {
Slog.e(TAG, "Error processing canceled notification", e);
@@ -159,5 +211,6 @@
void removeAutoGroup(String key);
void addAutoGroupSummary(int userId, String pkg, String triggeringKey);
void removeAutoGroupSummary(int user, String pkg);
+ void updateAutogroupSummary(String key, boolean needsOngoingFlag);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5039566..d7efa1b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -601,6 +601,40 @@
}
}
+ /**
+ * This method will update the flags of the summary.
+ * It will set it to FLAG_ONGOING_EVENT if any of its group members
+ * has the same flag. It will delete the flag otherwise
+ * @param userId user id of the autogroup summary
+ * @param pkg package of the autogroup summary
+ * @param needsOngoingFlag true if the group has at least one ongoing notification
+ */
+ @GuardedBy("mNotificationLock")
+ protected void updateAutobundledSummaryFlags(int userId, String pkg, boolean needsOngoingFlag) {
+ ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
+ if (summaries == null) {
+ return;
+ }
+ String summaryKey = summaries.get(pkg);
+ if (summaryKey == null) {
+ return;
+ }
+ NotificationRecord summary = mNotificationsByKey.get(summaryKey);
+ if (summary == null) {
+ return;
+ }
+ int oldFlags = summary.sbn.getNotification().flags;
+ if (needsOngoingFlag) {
+ summary.sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+ } else {
+ summary.sbn.getNotification().flags &= ~FLAG_ONGOING_EVENT;
+ }
+
+ if (summary.sbn.getNotification().flags != oldFlags) {
+ mHandler.post(new EnqueueNotificationRunnable(userId, summary));
+ }
+ }
+
private void allowDndPackage(String packageName) {
try {
getBinderService().setNotificationPolicyAccessGranted(packageName, true);
@@ -1917,7 +1951,6 @@
});
}
-
private GroupHelper getGroupHelper() {
mAutoGroupAtCount =
getContext().getResources().getInteger(R.integer.config_autoGroupAtCount);
@@ -1947,6 +1980,15 @@
clearAutogroupSummaryLocked(userId, pkg);
}
}
+
+ @Override
+ public void updateAutogroupSummary(String key, boolean needsOngoingFlag) {
+ synchronized (mNotificationLock) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ updateAutobundledSummaryFlags(r.getUser().getIdentifier(),
+ r.sbn.getPackageName(), needsOngoingFlag);
+ }
+ }
});
}
@@ -5762,6 +5804,10 @@
n, hasAutoGroupSummaryLocked(n));
}
});
+ } else if (oldSbn != null) {
+ final NotificationRecord finalRecord = r;
+ mHandler.post(() -> mGroupHelper.onNotificationUpdated(
+ finalRecord.sbn, hasAutoGroupSummaryLocked(n)));
}
} else {
Slog.e(TAG, "Not posting notification without small icon: " + notification);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index f652c5a..721641a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -15,6 +15,8 @@
*/
package com.android.server.notification;
+import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@@ -155,6 +157,196 @@
}
@Test
+ public void testAutoGroupCount_addingNoGroupSBN() {
+ final String pkg = "package";
+ ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
+ notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ }
+
+ for (StatusBarNotification sbn: notifications) {
+ sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
+ sbn.setOverrideGroupKey(AUTOGROUP_KEY);
+ }
+
+ for (StatusBarNotification sbn: notifications) {
+ mGroupHelper.onNotificationPosted(sbn, true);
+ }
+
+ verify(mCallback, times(AUTOGROUP_AT_COUNT + 1))
+ .updateAutogroupSummary(anyString(), eq(true));
+
+ int userId = UserHandle.SYSTEM.getIdentifier();
+ assertEquals(mGroupHelper.getOngoingGroupCount(
+ userId, pkg, AUTOGROUP_KEY), AUTOGROUP_AT_COUNT + 1);
+ }
+
+ @Test
+ public void testAutoGroupCount_UpdateNotification() {
+ final String pkg = "package";
+ ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
+ notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ }
+
+ for (StatusBarNotification sbn: notifications) {
+ sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
+ sbn.setOverrideGroupKey(AUTOGROUP_KEY);
+ }
+
+ for (StatusBarNotification sbn: notifications) {
+ mGroupHelper.onNotificationPosted(sbn, true);
+ }
+
+ notifications.get(0).getNotification().flags &= ~Notification.FLAG_ONGOING_EVENT;
+
+ mGroupHelper.onNotificationUpdated(notifications.get(0), true);
+
+ verify(mCallback, times(AUTOGROUP_AT_COUNT + 2))
+ .updateAutogroupSummary(anyString(), eq(true));
+
+ int userId = UserHandle.SYSTEM.getIdentifier();
+ assertEquals(mGroupHelper.getOngoingGroupCount(
+ userId, pkg, AUTOGROUP_KEY), AUTOGROUP_AT_COUNT);
+ }
+
+ @Test
+ public void testAutoGroupCount_UpdateNotificationAfterChanges() {
+ final String pkg = "package";
+ ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
+ notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ }
+
+ for (StatusBarNotification sbn: notifications) {
+ sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
+ sbn.setOverrideGroupKey(AUTOGROUP_KEY);
+ }
+
+ for (StatusBarNotification sbn: notifications) {
+ mGroupHelper.onNotificationPosted(sbn, true);
+ }
+
+ notifications.get(0).getNotification().flags &= ~Notification.FLAG_ONGOING_EVENT;
+
+ mGroupHelper.onNotificationUpdated(notifications.get(0), true);
+
+ notifications.get(0).getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
+
+ mGroupHelper.onNotificationUpdated(notifications.get(0), true);
+
+ verify(mCallback, times(AUTOGROUP_AT_COUNT + 3))
+ .updateAutogroupSummary(anyString(), eq(true));
+
+ int userId = UserHandle.SYSTEM.getIdentifier();
+ assertEquals(mGroupHelper.getOngoingGroupCount(
+ userId, pkg, AUTOGROUP_KEY), AUTOGROUP_AT_COUNT + 1);
+ }
+
+ @Test
+ public void testAutoGroupCount_RemoveNotification() {
+ final String pkg = "package";
+ ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
+ notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ }
+
+ for (StatusBarNotification sbn: notifications) {
+ sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
+ sbn.setOverrideGroupKey(AUTOGROUP_KEY);
+ }
+
+ for (StatusBarNotification sbn: notifications) {
+ mGroupHelper.onNotificationPosted(sbn, true);
+ }
+
+ mGroupHelper.onNotificationRemoved(notifications.get(0));
+
+ verify(mCallback, times(AUTOGROUP_AT_COUNT + 2))
+ .updateAutogroupSummary(anyString(), eq(true));
+
+ int userId = UserHandle.SYSTEM.getIdentifier();
+ assertEquals(mGroupHelper.getOngoingGroupCount(
+ userId, pkg, AUTOGROUP_KEY), AUTOGROUP_AT_COUNT);
+ }
+
+
+ @Test
+ public void testAutoGroupCount_UpdateToNoneOngoingNotification() {
+ final String pkg = "package";
+ ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
+ notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ }
+
+ for (StatusBarNotification sbn: notifications) {
+ sbn.setOverrideGroupKey(AUTOGROUP_KEY);
+ }
+
+ for (StatusBarNotification sbn: notifications) {
+ mGroupHelper.onNotificationPosted(sbn, true);
+ }
+
+ notifications.get(0).getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
+ mGroupHelper.onNotificationUpdated(notifications.get(0), true);
+
+ verify(mCallback, times(1))
+ .updateAutogroupSummary(anyString(), eq(true));
+
+ int userId = UserHandle.SYSTEM.getIdentifier();
+ assertEquals(mGroupHelper.getOngoingGroupCount(
+ userId, pkg, AUTOGROUP_KEY), 1);
+ }
+
+ @Test
+ public void testAutoGroupCount_AddOneOngoingNotification() {
+ final String pkg = "package";
+ ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
+ notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ }
+ StatusBarNotification sbn = notifications.get(0);
+ sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
+ sbn.setOverrideGroupKey(AUTOGROUP_KEY);
+
+
+ for (StatusBarNotification current: notifications) {
+ mGroupHelper.onNotificationPosted(current, true);
+ }
+
+ verify(mCallback, times(1))
+ .updateAutogroupSummary(anyString(), eq(true));
+
+ int userId = UserHandle.SYSTEM.getIdentifier();
+ assertEquals(mGroupHelper.getOngoingGroupCount(
+ userId, pkg, AUTOGROUP_KEY), 1);
+ }
+
+ @Test
+ public void testAutoGroupCount_UpdateNoneOngoing() {
+ final String pkg = "package";
+ ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
+ notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ }
+
+ for (StatusBarNotification sbn: notifications) {
+ sbn.setOverrideGroupKey(AUTOGROUP_KEY);
+ }
+
+ for (StatusBarNotification sbn: notifications) {
+ mGroupHelper.onNotificationPosted(sbn, true);
+ }
+
+ verify(mCallback, times(0))
+ .updateAutogroupSummary(anyString(), eq(true));
+
+ int userId = UserHandle.SYSTEM.getIdentifier();
+ assertEquals(mGroupHelper.getOngoingGroupCount(userId, pkg, AUTOGROUP_KEY), 0);
+ }
+
+
+ @Test
public void testDropToZeroRemoveGroup() throws Exception {
final String pkg = "package";
List<StatusBarNotification> posted = new ArrayList<>();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 2072ee5..4ea2fc0 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1182,6 +1182,36 @@
}
@Test
+ public void testAutobundledSummary_notificationAdded() {
+ NotificationRecord summary =
+ generateNotificationRecord(mTestNotificationChannel, 0, "pkg", true);
+ summary.getNotification().flags |= Notification.FLAG_AUTOGROUP_SUMMARY;
+ mService.addNotification(summary);
+ mService.mSummaryByGroupKey.put("pkg", summary);
+ mService.mAutobundledSummaries.put(0, new ArrayMap<>());
+ mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
+ mService.updateAutobundledSummaryFlags(0, "pkg", true);
+
+ assertTrue(summary.sbn.isOngoing());
+ }
+
+ @Test
+ public void testAutobundledSummary_notificationRemoved() {
+ NotificationRecord summary =
+ generateNotificationRecord(mTestNotificationChannel, 0, "pkg", true);
+ summary.getNotification().flags |= Notification.FLAG_AUTOGROUP_SUMMARY;
+ summary.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
+ mService.addNotification(summary);
+ mService.mAutobundledSummaries.put(0, new ArrayMap<>());
+ mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
+ mService.mSummaryByGroupKey.put("pkg", summary);
+
+ mService.updateAutobundledSummaryFlags(0, "pkg", false);
+
+ assertFalse(summary.sbn.isOngoing());
+ }
+
+ @Test
public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;