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

Commit f62bda69 authored by Valentin Iftime's avatar Valentin Iftime Committed by Iavor-Valentin Iftime
Browse files

Use the correct groupKey when canceling group child notifications

 Add 2 GroupChildCheckers:
 - Use the current group key when canceling regular/autogroup children
 - Use the original group key when canceling children of sparse groups that were aggregated (force-group singletons)

Flag: android.service.notification.notification_force_grouping
Flag: com.android.server.notification.notification_force_group_singletons
Test: atest NotificationManagerServiceTest
Bug: 357844384
Bug: 353929341
Change-Id: I63f52ddc5aae1307672adfdf72cc4675a88be092
parent 9ac72828
Loading
Loading
Loading
Loading
+49 −14
Original line number Diff line number Diff line
@@ -2963,8 +2963,9 @@ public class NotificationManagerService extends SystemService {
                    };
                    cancelGroupChildrenLocked(userId, pkg, Binder.getCallingUid(),
                            Binder.getCallingPid(), null,
                            false, childrenFlagChecker, groupKey,
                            REASON_APP_CANCEL, SystemClock.elapsedRealtime());
                            false, childrenFlagChecker,
                            NotificationManagerService::wasChildOfForceRegroupedGroupChecker,
                            groupKey, REASON_APP_CANCEL, SystemClock.elapsedRealtime());
                }
            }
        });
@@ -8663,8 +8664,8 @@ public class NotificationManagerService extends SystemService {
                    if (r.getNotification().isGroupSummary()) {
                        cancelGroupChildrenLocked(mUserId, mPkg, mCallingUid, mCallingPid,
                                listenerName, mSendDelete, childrenFlagChecker,
                                r.getNotification().getGroup(), mReason,
                                mCancellationElapsedTimeMs);
                                NotificationManagerService::isChildOfCurrentGroupChecker,
                                r.getGroupKey(), mReason, mCancellationElapsedTimeMs);
                    }
                    mAttentionHelper.updateLightsLocked();
                    if (mShortcutHelper != null) {
@@ -9386,8 +9387,8 @@ public class NotificationManagerService extends SystemService {
        if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) {
            cancelGroupChildrenLocked(old.getUserId(), old.getSbn().getPackageName(), callingUid,
                    callingPid, null, false /* sendDelete */, childrenFlagChecker,
                    old.getNotification().getGroup(), REASON_APP_CANCEL,
                    SystemClock.elapsedRealtime());
                    NotificationManagerService::isChildOfCurrentGroupChecker, old.getGroupKey(),
                    REASON_APP_CANCEL, SystemClock.elapsedRealtime());
        }
    }
@@ -10368,13 +10369,45 @@ public class NotificationManagerService extends SystemService {
        public boolean apply(int flags);
    }
    private static boolean isChildOfGroup(final NotificationRecord childRecord, int userId,
    @FunctionalInterface
    private interface GroupChildChecker {
        // Returns true if the childRecord is a child of the group defined
        // by the rest of the parameters
        boolean apply(NotificationRecord childRecord, int userId, String pkg, String groupKey);
    }
    /**
     * Checks that the notification is currently a child of the group
     * @param childRecord the notification to check
     * @param userId userId of the group
     * @param pkg package name of the group
     * @param groupKey group key for a current group
     * @return true if the childRecord is currently a child of the group
     */
    private static boolean isChildOfCurrentGroupChecker(NotificationRecord childRecord, int userId,
            String pkg, String groupKey) {
        return (childRecord.getUser().getIdentifier() == userId
            && childRecord.getSbn().getPackageName().equals(pkg)
            && childRecord.getSbn().isGroup()
            && !childRecord.getNotification().isGroupSummary()
            && TextUtils.equals(groupKey, childRecord.getNotification().getGroup()));
            && TextUtils.equals(groupKey, childRecord.getGroupKey()));
    }
    /**
     * Checks that the notification was originally a child of the group
     * @param childRecord the notification to check
     * @param userId userId of the group
     * @param pkg package name of the group
     * @param groupKey original/initial group key for a group that was force grouped
     * @return true if the childRecord was originally a child of the group
     */
    private static boolean wasChildOfForceRegroupedGroupChecker(NotificationRecord childRecord,
            int userId, String pkg, String groupKey) {
        return (childRecord.getUser().getIdentifier() == userId
            && childRecord.getSbn().getPackageName().equals(pkg)
            && childRecord.getSbn().isGroup()
            && !childRecord.getNotification().isGroupSummary()
            && TextUtils.equals(groupKey, childRecord.getOriginalGroupKey()));
    }
    @GuardedBy("mNotificationLock")
@@ -10535,18 +10568,19 @@ public class NotificationManagerService extends SystemService {
    // Warning: The caller is responsible for invoking updateLightsLocked().
    @GuardedBy("mNotificationLock")
    private void cancelGroupChildrenLocked(int userId, String pkg, int callingUid, int callingPid,
            String listenerName, boolean sendDelete, FlagChecker flagChecker, String groupKey,
            int reason, @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
            String listenerName, boolean sendDelete, FlagChecker flagChecker,
            GroupChildChecker groupChildChecker, String groupKey, int reason,
            @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
        if (pkg == null) {
            if (DBG) Slog.e(TAG, "No package for group summary");
            return;
        }
        cancelGroupChildrenByListLocked(mNotificationList, userId, pkg, callingUid, callingPid,
                listenerName, sendDelete, true, flagChecker, groupKey,
                listenerName, sendDelete, true, flagChecker, groupChildChecker, groupKey,
                reason, cancellationElapsedTimeMs);
        cancelGroupChildrenByListLocked(mEnqueuedNotifications, userId, pkg, callingUid, callingPid,
                listenerName, sendDelete, false, flagChecker, groupKey,
                listenerName, sendDelete, false, flagChecker, groupChildChecker, groupKey,
                reason, cancellationElapsedTimeMs);
    }
@@ -10554,12 +10588,13 @@ public class NotificationManagerService extends SystemService {
    private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList,
            int userId, String pkg, int callingUid, int callingPid,
            String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker,
            String groupKey, int reason, @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
            GroupChildChecker grouChildChecker, String groupKey, int reason,
            @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
        final int childReason = REASON_GROUP_SUMMARY_CANCELED;
        for (int i = notificationList.size() - 1; i >= 0; i--) {
            final NotificationRecord childR = notificationList.get(i);
            final StatusBarNotification childSbn = childR.getSbn();
            if (isChildOfGroup(childR, userId, pkg, groupKey)
            if (grouChildChecker.apply(childR, userId, pkg, groupKey)
                && (flagChecker == null || flagChecker.apply(childR.getFlags()))
                && (!childR.getChannel().isImportantConversation() || reason != REASON_CANCEL)) {
                EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
+15 −0
Original line number Diff line number Diff line
@@ -1163,6 +1163,21 @@ public final class NotificationRecord {
        getSbn().setOverrideGroupKey(overrideGroupKey);
    }

    /**
     * Get the original group key that was set via {@link Notification.Builder#setGroup}
     *
     * This value is different than the value returned by {@link #getGroupKey()} as it does
     * not contain any userId or package name.
     *
     * This value is different than the value returned
     * by {@link StatusBarNotification#getGroup()} if the notification group
     * was overridden: by NotificationAssistantService or by autogrouping.
     */
    @Nullable
    public String getOriginalGroupKey() {
        return getSbn().getNotification().getGroup();
    }

    public NotificationChannel getChannel() {
        return mChannel;
    }
+94 −0
Original line number Diff line number Diff line
@@ -13047,6 +13047,100 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        assertTrue("expect to find a redacted notification", foundRedactedSbn);
    }
    @Test
    @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
    public void testCancelAutogroupSummary_cancelsAllChildren() throws Exception {
        final String originalGroupName = "originalGroup";
        final String aggregateGroupName = "Aggregate_Test";
        final int summaryId = Integer.MAX_VALUE;
        // Add 2 group notifications without a summary
        NotificationRecord nr0 =
                generateNotificationRecord(mTestNotificationChannel, 0, originalGroupName, false);
        NotificationRecord nr1 =
                generateNotificationRecord(mTestNotificationChannel, 1, originalGroupName, false);
        mService.addNotification(nr0);
        mService.addNotification(nr1);
        mService.mSummaryByGroupKey.remove(nr0.getGroupKey());
        // GroupHelper is a mock, so make the calls it would make
        // Add aggregate group summary
        NotificationAttributes attr = new NotificationAttributes(GroupHelper.BASE_FLAGS,
                mock(Icon.class), 0, VISIBILITY_PRIVATE, GROUP_ALERT_CHILDREN,
                nr0.getChannel().getId());
        NotificationRecord aggregateSummary = mService.createAutoGroupSummary(nr0.getUserId(),
                nr0.getSbn().getPackageName(), nr0.getKey(), aggregateGroupName, summaryId, attr);
        mService.addNotification(aggregateSummary);
        nr0.setOverrideGroupKey(aggregateGroupName);
        nr1.setOverrideGroupKey(aggregateGroupName);
        final String fullAggregateGroupKey = nr0.getGroupKey();
        // Check that the aggregate group summary was created
        assertThat(aggregateSummary.getNotification().getGroup()).isEqualTo(aggregateGroupName);
        assertThat(aggregateSummary.getNotification().getChannelId()).isEqualTo(
                nr0.getChannel().getId());
        assertThat(mService.mSummaryByGroupKey.containsKey(fullAggregateGroupKey)).isTrue();
        // Cancel aggregate group summary
        mBinderService.cancelNotificationWithTag(mPkg, mPkg, aggregateSummary.getSbn().getTag(),
                aggregateSummary.getSbn().getId(), aggregateSummary.getSbn().getUserId());
        waitForIdle();
        // Check that child notifications are also removed
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(aggregateSummary));
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0));
        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1));
        // Make sure the summary was removed and not re-posted
        assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
    }
    @Test
    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
    public void testCancelAutogroupSummary_forceGrouping_cancelsAllChildren() throws Exception {
        final String originalGroupName = "originalGroup";
        final String aggregateGroupName = "Aggregate_Test";
        final int summaryId = Integer.MAX_VALUE;
        // Add 2 group notifications without a summary
        NotificationRecord nr0 =
                generateNotificationRecord(mTestNotificationChannel, 0, originalGroupName, false);
        NotificationRecord nr1 =
                generateNotificationRecord(mTestNotificationChannel, 1, originalGroupName, false);
        mService.addNotification(nr0);
        mService.addNotification(nr1);
        mService.mSummaryByGroupKey.remove(nr0.getGroupKey());
        // GroupHelper is a mock, so make the calls it would make
        // Add aggregate group summary
        NotificationAttributes attr = new NotificationAttributes(GroupHelper.BASE_FLAGS,
                mock(Icon.class), 0, VISIBILITY_PRIVATE, GROUP_ALERT_CHILDREN,
                nr0.getChannel().getId());
        NotificationRecord aggregateSummary = mService.createAutoGroupSummary(nr0.getUserId(),
                nr0.getSbn().getPackageName(), nr0.getKey(), aggregateGroupName, summaryId, attr);
        mService.addNotification(aggregateSummary);
        nr0.setOverrideGroupKey(aggregateGroupName);
        nr1.setOverrideGroupKey(aggregateGroupName);
        final String fullAggregateGroupKey = nr0.getGroupKey();
        // Check that the aggregate group summary was created
        assertThat(aggregateSummary.getNotification().getGroup()).isEqualTo(aggregateGroupName);
        assertThat(aggregateSummary.getNotification().getChannelId()).isEqualTo(
                nr0.getChannel().getId());
        assertThat(mService.mSummaryByGroupKey.containsKey(fullAggregateGroupKey)).isTrue();
        // Cancel aggregate group summary
        mBinderService.cancelNotificationWithTag(mPkg, mPkg, aggregateSummary.getSbn().getTag(),
                aggregateSummary.getSbn().getId(), aggregateSummary.getSbn().getUserId());
        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());
        // Make sure the summary was removed and not re-posted
        assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
    }
    @Test
    @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
    public void testUngroupingOngoingAutoSummary() throws Exception {