Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit efe1c928 authored by Jay Aliomer's avatar Jay Aliomer
Browse files

"Clear all" causes auto-grouped persistent notifications losing the group summary

When an automatically generated notification group has 4+ persistent
notifications, hitting "clear all" will cause the group to lose its
automatically generated group summary notification. The solution is to
count the number of notifications belonging to a specific group as well
as the number of ongoing notifications. if the number of grouped
notifications > min before grouping limit & the group has at least one
ongoing notification, then the group summary notification will be marked
as ongoing with the flag FLAG_ONGOING_EVENT. otherwise the flag will
be removed.

Bug: 129562864
Change-Id: I65412935fbb4a74de6ac4cf159d4fd2ebe96dae8
Fix: 129562864
Test: atest NotificationManagerServiceTest
parent 0cdb7e95
Loading
Loading
Loading
Loading
+53 −0
Original line number Diff line number Diff line
@@ -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 @@ public class GroupHelper {
    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 @@ public class GroupHelper {
        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 class GroupHelper {

    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 @@ public class GroupHelper {
        void removeAutoGroup(String key);
        void addAutoGroupSummary(int userId, String pkg, String triggeringKey);
        void removeAutoGroupSummary(int user, String pkg);
        void updateAutogroupSummary(String key, boolean needsOngoingFlag);
    }
}
+47 −1
Original line number Diff line number Diff line
@@ -595,6 +595,40 @@ public class NotificationManagerService extends SystemService {
        }
    }

    /**
     * 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);
@@ -1948,7 +1982,6 @@ public class NotificationManagerService extends SystemService {
                });
    }


    private GroupHelper getGroupHelper() {
        mAutoGroupAtCount =
                getContext().getResources().getInteger(R.integer.config_autoGroupAtCount);
@@ -1978,6 +2011,15 @@ public class NotificationManagerService extends SystemService {
                    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);
                }
            }
        });
    }

@@ -5792,6 +5834,10 @@ public class NotificationManagerService extends SystemService {
                                            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);
+192 −0
Original line number Diff line number Diff line
@@ -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;

@@ -154,6 +156,196 @@ public class GroupHelperTest extends UiServiceTestCase {
        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
    }

    @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";
+30 −0
Original line number Diff line number Diff line
@@ -1179,6 +1179,36 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        assertEquals(0, mService.getNotificationRecordCount());
    }

    @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;