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

Commit e9fe4839 authored by Matías Hernández's avatar Matías Hernández
Browse files

Fire deleteIntent of cached group summaries when all their children are gone

The posting app expects that, if the sparse group had remained, so let's keep the observable behavior.

Fixes: 393353103
Test: atest GroupHelperTest NotificationManagerServiceTest
Flag: android.service.notification.notification_force_grouping
Change-Id: Ia8a4d91cdbdee8aff24379d8443fa465fecf64f3
parent bb13b093
Loading
Loading
Loading
Loading
+38 −9
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -130,8 +131,10 @@ public class GroupHelper {
            mUngroupedAbuseNotifications = new ArrayMap<>();

    // Contains the list of group summaries that were canceled when "singleton groups" were
    // force grouped. Used to remove the original group's children when an app cancels the
    // already removed summary. Key is userId|packageName|g:OriginalGroupName
    // force grouped. Key is userId|packageName|g:OriginalGroupName. Used to:
    // 1) remove the original group's children when an app cancels the already removed summary.
    // 2) perform the same side effects that would happen if the group is removed because
    //    all its force-regrouped children are removed (e.g. firing its deleteIntent).
    @GuardedBy("mAggregatedNotifications")
    private final ArrayMap<FullyQualifiedGroupKey, CachedSummary>
            mCanceledSummaries = new ArrayMap<>();
@@ -278,7 +281,11 @@ public class GroupHelper {
    public void onNotificationRemoved(NotificationRecord record) {
        try {
            if (notificationForceGrouping()) {
                onNotificationRemoved(record, new ArrayList<>());
                Slog.wtf(TAG,
                        "This overload of onNotificationRemoved() should not be called if "
                                + "notification_force_grouping is enabled!",
                        new Exception("call stack"));
                onNotificationRemoved(record, new ArrayList<>(), false);
            } else {
                final StatusBarNotification sbn = record.getSbn();
                maybeUngroup(sbn, true, sbn.getUserId());
@@ -926,10 +933,12 @@ public class GroupHelper {
     *
     * @param record the removed notification
     * @param notificationList the full notification list from NotificationManagerService
     * @param sendingDelete whether the removed notification is being removed in a way that sends
     *                     its {@code deleteIntent}
     */
    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
    protected void onNotificationRemoved(final NotificationRecord record,
                final List<NotificationRecord> notificationList) {
            final List<NotificationRecord> notificationList, boolean sendingDelete) {
        final StatusBarNotification sbn = record.getSbn();
        final String pkgName = sbn.getPackageName();
        final int userId = record.getUserId();
@@ -973,9 +982,11 @@ public class GroupHelper {
                }

                // Try to cleanup cached summaries if notification was canceled (not snoozed)
                // If the notification was cancelled by an action that fires its delete intent,
                // also fire it for the cached summary.
                if (record.isCanceled) {
                    maybeClearCanceledSummariesCache(pkgName, userId,
                            record.getNotification().getGroup(), notificationList);
                            record.getNotification().getGroup(), notificationList, sendingDelete);
                }
            }
        }
@@ -1759,13 +1770,18 @@ public class GroupHelper {
    private void cacheCanceledSummary(NotificationRecord record) {
        final FullyQualifiedGroupKey groupKey = new FullyQualifiedGroupKey(record.getUserId(),
                record.getSbn().getPackageName(), record.getNotification().getGroup());
        mCanceledSummaries.put(groupKey, new CachedSummary(record.getSbn().getId(),
                record.getSbn().getTag(), record.getNotification().getGroup(), record.getKey()));
        mCanceledSummaries.put(groupKey, new CachedSummary(
                record.getSbn().getId(),
                record.getSbn().getTag(),
                record.getNotification().getGroup(),
                record.getKey(),
                record.getNotification().deleteIntent));
    }

    @GuardedBy("mAggregatedNotifications")
    private void maybeClearCanceledSummariesCache(String pkgName, int userId,
            String groupName, List<NotificationRecord> notificationList) {
            String groupName, List<NotificationRecord> notificationList,
            boolean sendSummaryDelete) {
        final FullyQualifiedGroupKey findKey = new FullyQualifiedGroupKey(userId, pkgName,
                groupName);
        CachedSummary summary = mCanceledSummaries.get(findKey);
@@ -1786,6 +1802,9 @@ public class GroupHelper {
            }
            if (!stillHasChildren) {
                removeCachedSummary(pkgName, userId, summary);
                if (sendSummaryDelete && summary.deleteIntent != null) {
                    mCallback.sendAppProvidedSummaryDeleteIntent(pkgName, summary.deleteIntent);
                }
            }
        }
    }
@@ -1965,7 +1984,8 @@ public class GroupHelper {
        }
    }

    record CachedSummary(int id, String tag, String originalGroupKey, String key) {}
    record CachedSummary(int id, String tag, String originalGroupKey, String key,
                         @Nullable PendingIntent deleteIntent) { }

    protected static class NotificationAttributes {
        public final int flags;
@@ -2035,6 +2055,15 @@ public class GroupHelper {
        // New callbacks for API abuse grouping
        void removeAppProvidedSummary(String key);

        /**
         * Send a cached summary's deleteIntent, when the last of its original children is removed.
         *
         * <p>While technically the group summary was "canceled" much earlier (because it was the
         * summary of a sparse group and its children got reparented), the posting package expected
         * the summary's deleteIntent to fire when the summary is auto-dismissed.
         */
        void sendAppProvidedSummaryDeleteIntent(String pkg, PendingIntent deleteIntent);

        void removeNotificationFromCanceledGroup(int userId, String pkg, String groupKey,
                int cancelReason);

+24 −16
Original line number Diff line number Diff line
@@ -3210,6 +3210,11 @@ public class NotificationManagerService extends SystemService {
                }
            }
            @Override
            public void sendAppProvidedSummaryDeleteIntent(String pkg, PendingIntent deleteIntent) {
                sendDeleteIntent(deleteIntent, pkg);
            }
            @Override
            public void removeNotificationFromCanceledGroup(int userId, String pkg,
                    String groupKey, int cancelReason) {
@@ -9898,7 +9903,8 @@ public class NotificationManagerService extends SystemService {
                            if (notificationForceGrouping()) {
                                mHandler.post(() -> {
                                    synchronized (mNotificationLock) {
                                        mGroupHelper.onNotificationRemoved(r, mNotificationList);
                                        mGroupHelper.onNotificationRemoved(r, mNotificationList,
                                                /* sendingDelete= */ false);
                                    }
                                });
                            } else {
@@ -10826,20 +10832,7 @@ public class NotificationManagerService extends SystemService {
        // tell the app
        if (sendDelete) {
            final PendingIntent deleteIntent = r.getNotification().deleteIntent;
            if (deleteIntent != null) {
                try {
                    // make sure deleteIntent cannot be used to start activities from background
                    LocalServices.getService(ActivityManagerInternal.class)
                            .clearPendingIntentAllowBgActivityStarts(deleteIntent.getTarget(),
                                    ALLOWLIST_TOKEN);
                    deleteIntent.send();
                } catch (PendingIntent.CanceledException ex) {
                    // do nothing - there's no relevant way to recover, and
                    //     no reason to let this propagate
                    Slog.w(TAG, "canceled PendingIntent for " + r.getSbn().getPackageName(), ex);
                }
            }
            sendDeleteIntent(r.getNotification().deleteIntent, r.getSbn().getPackageName());
        }
        // Only cancel these if this notification actually got to be posted.
@@ -10854,7 +10847,7 @@ public class NotificationManagerService extends SystemService {
                    mHandler.removeCallbacksAndEqualMessages(r.getKey());
                    mHandler.post(() -> {
                        synchronized (NotificationManagerService.this.mNotificationLock) {
                            mGroupHelper.onNotificationRemoved(r, mNotificationList);
                            mGroupHelper.onNotificationRemoved(r, mNotificationList, sendDelete);
                        }
                    });
@@ -10952,6 +10945,21 @@ public class NotificationManagerService extends SystemService {
        }
    }
    private static void sendDeleteIntent(@Nullable PendingIntent deleteIntent, String fromPkg) {
        if (deleteIntent != null) {
            try {
                // make sure deleteIntent cannot be used to start activities from background
                LocalServices.getService(ActivityManagerInternal.class)
                        .clearPendingIntentAllowBgActivityStarts(deleteIntent.getTarget(),
                                ALLOWLIST_TOKEN);
                deleteIntent.send();
            } catch (PendingIntent.CanceledException ex) {
                // There's no relevant way to recover, and no reason to let this propagate
                Slog.w(TAG, "canceled PendingIntent for " + fromPkg, ex);
            }
        }
    }
    @VisibleForTesting
    void updateUriPermissions(@Nullable NotificationRecord newRecord,
            @Nullable NotificationRecord oldRecord, String targetPkg, int targetUserId) {
+62 −18
Original line number Diff line number Diff line
@@ -66,6 +66,8 @@ import static org.mockito.Mockito.when;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.AdaptiveIconDrawable;
@@ -620,6 +622,7 @@ public class GroupHelperTest extends UiServiceTestCase {
    }

    @Test
    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
    public void testAutoGrouped_singleOngoing_removeOngoingChild() {
        final String pkg = "package";

@@ -639,7 +642,7 @@ public class GroupHelperTest extends UiServiceTestCase {
        }

        // remove ongoing
        mGroupHelper.onNotificationRemoved(notifications.get(0));
        mGroupHelper.onNotificationRemoved(notifications.get(0), new ArrayList<>(), false);

        // Summary is no longer ongoing
        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), anyString(),
@@ -776,7 +779,7 @@ public class GroupHelperTest extends UiServiceTestCase {
        }

        // remove ongoing
        mGroupHelper.onNotificationRemoved(notifications.get(1));
        mGroupHelper.onNotificationRemoved(notifications.get(1), new ArrayList<>(), false);

        // Summary is still ongoing
        verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
@@ -936,7 +939,7 @@ public class GroupHelperTest extends UiServiceTestCase {
            mGroupHelper.onNotificationPosted(r, false);
        }

        mGroupHelper.onNotificationRemoved(notifications.get(0));
        mGroupHelper.onNotificationRemoved(notifications.get(0), new ArrayList<>(), false);

        // Summary should still be autocancelable
        verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
@@ -944,6 +947,7 @@ public class GroupHelperTest extends UiServiceTestCase {
    }

    @Test
    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
    @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
    public void testDropToZeroRemoveGroup_disableFlag() {
        final String pkg = "package";
@@ -963,19 +967,20 @@ public class GroupHelperTest extends UiServiceTestCase {
        Mockito.reset(mCallback);

        for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
            mGroupHelper.onNotificationRemoved(posted.remove(0));
            mGroupHelper.onNotificationRemoved(posted.remove(0), new ArrayList<>(), false);
        }
        verify(mCallback, never()).removeAutoGroup(anyString());
        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
        Mockito.reset(mCallback);

        mGroupHelper.onNotificationRemoved(posted.remove(0));
        mGroupHelper.onNotificationRemoved(posted.remove(0), new ArrayList<>(), false);
        verify(mCallback, never()).removeAutoGroup(anyString());
        verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString(), anyString());
    }

    @Test
    @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
            android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
    public void testDropToZeroRemoveGroup() {
        final String pkg = "package";
        ArrayList<NotificationRecord> posted = new ArrayList<>();
@@ -994,13 +999,13 @@ public class GroupHelperTest extends UiServiceTestCase {
        Mockito.reset(mCallback);

        for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
            mGroupHelper.onNotificationRemoved(posted.remove(0));
            mGroupHelper.onNotificationRemoved(posted.remove(0), new ArrayList<>(), false);
        }
        verify(mCallback, never()).removeAutoGroup(anyString());
        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
        Mockito.reset(mCallback);

        mGroupHelper.onNotificationRemoved(posted.remove(0));
        mGroupHelper.onNotificationRemoved(posted.remove(0), new ArrayList<>(), false);
        verify(mCallback, never()).removeAutoGroup(anyString());
        verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString(), anyString());
    }
@@ -1072,6 +1077,7 @@ public class GroupHelperTest extends UiServiceTestCase {
    }

    @Test
    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
    @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
    public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled_alwaysGroup() {
        final String pkg = "package";
@@ -1091,7 +1097,7 @@ public class GroupHelperTest extends UiServiceTestCase {
        Mockito.reset(mCallback);

        for (int i = posted.size() - 2; i >= 0; i--) {
            mGroupHelper.onNotificationRemoved(posted.remove(i));
            mGroupHelper.onNotificationRemoved(posted.remove(i), new ArrayList<>(), false);
        }
        verify(mCallback, never()).removeAutoGroup(anyString());
        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
@@ -1114,7 +1120,8 @@ public class GroupHelperTest extends UiServiceTestCase {
    }

    @Test
    @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
            android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
    public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() {
        final String pkg = "package";
        ArrayList<NotificationRecord> posted = new ArrayList<>();
@@ -1134,7 +1141,7 @@ public class GroupHelperTest extends UiServiceTestCase {
        Mockito.reset(mCallback);

        for (int i = posted.size() - 2; i >= 0; i--) {
            mGroupHelper.onNotificationRemoved(posted.remove(i));
            mGroupHelper.onNotificationRemoved(posted.remove(i), new ArrayList<>(), false);
        }
        verify(mCallback, never()).removeAutoGroup(anyString());
        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
@@ -1481,7 +1488,7 @@ public class GroupHelperTest extends UiServiceTestCase {
        }

        // Remove last notification (the only one with different icon and color)
        mGroupHelper.onNotificationRemoved(notifications.get(lastIdx));
        mGroupHelper.onNotificationRemoved(notifications.get(lastIdx), new ArrayList<>(), false);

        // Summary should be updated to the common icon and color
        verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
@@ -2162,7 +2169,7 @@ public class GroupHelperTest extends UiServiceTestCase {
            NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
                    UserHandle.SYSTEM, "testGrp " + i, false);
            r.setOverrideGroupKey(expectedGroupKey);
            mGroupHelper.onNotificationRemoved(r, notificationList);
            mGroupHelper.onNotificationRemoved(r, notificationList, false);
        }
        // Check that the autogroup summary is removed
        verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
@@ -2206,7 +2213,7 @@ public class GroupHelperTest extends UiServiceTestCase {
        Mockito.reset(mCallback);
        for (NotificationRecord r: childrenToRemove) {
            notificationList.remove(r);
            mGroupHelper.onNotificationRemoved(r, notificationList);
            mGroupHelper.onNotificationRemoved(r, notificationList, false);
        }
        // Only call onGroupedNotificationRemovedWithDelay with the summary notification
        mGroupHelper.onGroupedNotificationRemovedWithDelay(summary, notificationList,
@@ -2270,7 +2277,7 @@ public class GroupHelperTest extends UiServiceTestCase {
        Mockito.reset(mCallback);
        for (NotificationRecord r: childrenToRemove) {
            notificationList.remove(r);
            mGroupHelper.onNotificationRemoved(r, notificationList);
            mGroupHelper.onNotificationRemoved(r, notificationList, false);
        }
        // Only call onGroupedNotificationRemovedWithDelay with the summary notification
        mGroupHelper.onGroupedNotificationRemovedWithDelay(summary, notificationList,
@@ -2327,7 +2334,7 @@ public class GroupHelperTest extends UiServiceTestCase {
        for (NotificationRecord r: notificationList) {
            if (r.getGroupKey().contains(groupToRemove)) {
                r.isCanceled = true;
                mGroupHelper.onNotificationRemoved(r, notificationList);
                mGroupHelper.onNotificationRemoved(r, notificationList, false);
            }
        }
        // Only call onGroupedNotificationRemovedWithDelay with the summary notification
@@ -2452,7 +2459,7 @@ public class GroupHelperTest extends UiServiceTestCase {
                true);
        assertThat(GroupHelper.getSection(notifToInvalidate)).isNull();
        notificationList.remove(notifToInvalidate);
        mGroupHelper.onNotificationRemoved(notifToInvalidate, notificationList);
        mGroupHelper.onNotificationRemoved(notifToInvalidate, notificationList, false);

        // Check that the autogroup was updated
        verify(mCallback, never()).removeAutoGroup(anyString());
@@ -4023,7 +4030,7 @@ public class GroupHelperTest extends UiServiceTestCase {
        //Cancel child 0 => remove cached summary
        childToRemove.isCanceled = true;
        notificationListAfterGrouping.remove(childToRemove);
        mGroupHelper.onNotificationRemoved(childToRemove, notificationListAfterGrouping);
        mGroupHelper.onNotificationRemoved(childToRemove, notificationListAfterGrouping, false);
        CachedSummary cachedSummary = mGroupHelper.findCanceledSummary(pkg, String.valueOf(id), id,
                UserHandle.SYSTEM.getIdentifier());
        assertThat(cachedSummary).isNull();
@@ -4399,4 +4406,41 @@ public class GroupHelperTest extends UiServiceTestCase {
                "PeopleSection(priority)");
    }

    @Test
    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
    public void onNotificationRemoved_lastChildOfCachedSummary_firesCachedSummaryDeleteIntent() {
        final List<NotificationRecord> notificationList = new ArrayList<>();
        final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
        final String pkg = "package";
        NotificationRecord onlyChildOfFirstGroup = null;
        PendingIntent deleteIntentofFirstSummary = PendingIntent.getActivity(mContext, 1,
                new Intent(), PendingIntent.FLAG_IMMUTABLE);
        // Post singleton groups, above forced group limit, so they are force grouped
        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
            NotificationRecord summary = getNotificationRecord(pkg, i,
                    String.valueOf(i), UserHandle.SYSTEM, "testGrp " + i, true);
            notificationList.add(summary);
            NotificationRecord child = getNotificationRecord(pkg, i + 42,
                    String.valueOf(i + 42), UserHandle.SYSTEM, "testGrp " + i, false);
            notificationList.add(child);
            summaryByGroup.put(summary.getGroupKey(), summary);
            if (i == 0) {
                onlyChildOfFirstGroup = child;
                summary.getNotification().deleteIntent = deleteIntentofFirstSummary;
            }
            mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
            summary.isCanceled = true;  // simulate removing the app summary
            mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
        }
        // Sparse group autogrouping would've removed the summary.
        notificationList.remove(0);

        // Now remove the only child of the first (force-grouped, cuz sparse) group.
        notificationList.remove(0);
        onlyChildOfFirstGroup.isCanceled = true;
        mGroupHelper.onNotificationRemoved(onlyChildOfFirstGroup, notificationList, true);

        verify(mCallback).sendAppProvidedSummaryDeleteIntent(eq(pkg),
                eq(deleteIntentofFirstSummary));
    }
}
+43 −10
Original line number Diff line number Diff line
@@ -349,6 +349,7 @@ import com.android.server.wm.WindowManagerInternal;
import com.google.android.collect.Lists;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -2788,8 +2789,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
                nr1.getSbn().getId(), nr1.getSbn().getUserId());
        waitForIdle();
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any());
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any());
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any(), eq(false));
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any(), eq(false));
        // GroupHelper would send 'remove summary' event
        mService.clearAutogroupSummaryLocked(nr1.getUserId(), nr1.getSbn().getPackageName(),
@@ -3155,8 +3156,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        waitForIdle();
        // Check that onGroupedNotificationRemovedWithDelay was called only once
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any());
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any());
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any(), eq(false));
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any(), eq(false));
        verify(mGroupHelper, times(1)).onGroupedNotificationRemovedWithDelay(eq(summary), any(),
                any());
    }
@@ -3201,9 +3202,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        waitForIdle();
        // Check that onGroupedNotificationRemovedWithDelay was never called: summary was canceled
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any());
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any());
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(summary), any());
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any(), eq(false));
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any(), eq(false));
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(summary), any(), eq(false));
        verify(mGroupHelper, never()).onGroupedNotificationRemovedWithDelay(any(), any(), any());
    }
@@ -14122,9 +14123,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        waitForIdle();
        // Check that child notifications are also removed
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(aggregateSummary), any());
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any());
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any());
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(aggregateSummary), any(),
                eq(false));
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any(), eq(false));
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any(), eq(false));
        // Make sure the summary was removed and not re-posted
        assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
@@ -18659,4 +18661,35 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        }
    }
    @Test
    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
    public void clearAll_fromUser_willSendDeleteIntentForCachedSummaries() throws Exception {
        NotificationRecord n = generateNotificationRecord(
                mTestNotificationChannel, 1, "group", true);
        mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag",
                n.getSbn().getId(), n.getSbn().getNotification(), n.getSbn().getUserId());
        waitForIdle();
        n = Iterables.getOnlyElement(mService.mNotificationList);
        mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(), n.getUserId());
        waitForIdle();
        verify(mGroupHelper).onNotificationRemoved(eq(n), any(), eq(true));
    }
    @Test
    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
    public void cancel_fromApp_willNotSendDeleteIntentForCachedSummaries() throws Exception {
        NotificationRecord n = generateNotificationRecord(
                mTestNotificationChannel, 1, "group", true);
        mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag",
                n.getSbn().getId(), n.getSbn().getNotification(), n.getSbn().getUserId());
        waitForIdle();
        n = Iterables.getOnlyElement(mService.mNotificationList);
        mBinderService.cancelAllNotifications(mPkg, mUserId);
        waitForIdle();
        verify(mGroupHelper).onNotificationRemoved(eq(n), any(), eq(false));
    }
}