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;