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

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

Regroup autogrouped notifications into app group on summary is posted

 When posting a group summary, find any ungrouped or force-grouped notifications from that group:
  - remove them from the ungrouped list or autogroup
  - regroup them into the app-provided group

Flag: EXEMPT bug fix
Test: atest GroupHelperTest

Bug: 408388738

Change-Id: I0b15bc6f86dd1aefe71c96ca71fe5e6ff1d3d6ac
parent 9db297f7
Loading
Loading
Loading
Loading
+44 −31
Original line number Diff line number Diff line
@@ -681,8 +681,9 @@ public class GroupHelper {

    /**
     * A notification was added that is app-grouped.
     * @return true if the notification was previously auto-grouped
     */
    private void maybeUngroupOnAppGrouped(NotificationRecord record) {
    private boolean maybeUngroupOnAppGrouped(NotificationRecord record) {
        FullyQualifiedGroupKey currentSectionKey = getSectionGroupKeyWithFallback(record);

        // The notification was part of a different section => trigger regrouping
@@ -694,7 +695,7 @@ public class GroupHelper {
            currentSectionKey = prevSectionKey;
        }

        maybeUngroupWithSections(record, currentSectionKey);
        return maybeUngroupWithSections(record, currentSectionKey);
    }

    /**
@@ -709,16 +710,19 @@ public class GroupHelper {
     * This method implements autogrouping with sections support.
     *
     * And updates the internal state of un-app-grouped notifications and their flags.
     *
     * @return true if the notification was previously auto-grouped
     */
    private void maybeUngroupWithSections(NotificationRecord record,
    private boolean maybeUngroupWithSections(NotificationRecord record,
            @Nullable FullyQualifiedGroupKey fullAggregateGroupKey) {
        boolean wasUnAggregated = false;
        if (fullAggregateGroupKey == null) {
            if (DEBUG) {
                Slog.i(TAG,
                        "Skipping maybeUngroupWithSections for " + record
                            + " no valid section found.");
            }
            return;
            return false;
        }

        final StatusBarNotification sbn = record.getSbn();
@@ -738,6 +742,7 @@ public class GroupHelper {
            if (aggregatedNotificationsAttrs.containsKey(record.getKey())) {
                aggregatedNotificationsAttrs.remove(sbn.getKey());
                mAggregatedNotifications.put(fullAggregateGroupKey, aggregatedNotificationsAttrs);
                wasUnAggregated = true;

                if (DEBUG) {
                    Slog.i(TAG, "maybeUngroup removeAutoGroup: " + record);
@@ -761,6 +766,7 @@ public class GroupHelper {
                }
            }
        }
        return wasUnAggregated;
    }

    /**
@@ -795,6 +801,10 @@ public class GroupHelper {
            return;
        }

        // If any formerly-ungrouped or autogrouped notifications will be grouped by this summary,
        // update grouping
        onGroupSummaryAdded(record, notificationList);

        final NotificationSectioner sectioner = getSection(record);
        if (sectioner == null) {
            if (DEBUG) {
@@ -1097,45 +1107,48 @@ public class GroupHelper {
    }

    /**
     * Called when a group summary is posted. If there are any ungrouped notifications that are
     * in that group, remove them as they are no longer candidates for autogrouping.
     * Called when a group summary is posted.
     * If there are any ungrouped or force-grouped notifications from that group,
     * remove them from the ungrouped list or autogroup
     * and regroup them into the app-provided group.
     *
     * @param summaryRecord the NotificationRecord for the newly posted group summary
     * @param notificationList the full notification list from NotificationManagerService
     */
    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
    protected void onGroupSummaryAdded(final NotificationRecord summaryRecord,
    private void onGroupSummaryAdded(final NotificationRecord summaryRecord,
            final List<NotificationRecord> notificationList) {
        String groupKey = summaryRecord.getSbn().getGroup();
        synchronized (mAggregatedNotifications) {
            final NotificationSectioner sectioner = getSection(summaryRecord);
            if (sectioner == null) {
                Slog.w(TAG, "onGroupSummaryAdded " + summaryRecord + ": no valid section found");
        if (!summaryRecord.getNotification().isGroupSummary()) {
            return;
        }

            FullyQualifiedGroupKey aggregateGroupKey = FullyQualifiedGroupKey.forRecord(
                    summaryRecord, sectioner);
            ArrayMap<String, NotificationAttributes> ungrouped =
                    mUngroupedAbuseNotifications.getOrDefault(aggregateGroupKey,
                            new ArrayMap<>());
            if (ungrouped.isEmpty()) {
                // don't bother looking through the notification list if there are no pending
                // ungrouped notifications in this section (likely to be the most common case)
        if (GroupHelper.isAggregatedGroup(summaryRecord)) {
            return;
        }
        synchronized (mAggregatedNotifications) {
            if (DEBUG) {
                Log.i(TAG, "onGroupSummaryAdded: " + summaryRecord);
            }
            final String summaryGroupKey = summaryRecord.getGroupKey();

            // Look through full notification list for any notifications belonging to this group;
            // remove from ungrouped map if needed, as the presence of the summary means they will
            // now be grouped
            for (NotificationRecord r : notificationList) {
                final String oldGroupKey = GroupHelper.getFullAggregateGroupKey(
                        r.getSbn().getPackageName(), r.getOriginalGroupKey(), r.getUserId());
                if (!r.getNotification().isGroupSummary()
                        && groupKey.equals(r.getSbn().getGroup())
                        && ungrouped.containsKey(r.getKey())) {
                    ungrouped.remove(r.getKey());
                        && (r.mOriginalFlags & FLAG_GROUP_SUMMARY) == 0
                        && summaryGroupKey.equals(oldGroupKey)) {
                    final NotificationSectioner sectioner = getSection(r);
                    if (sectioner == null || NOTIFICATION_BUNDLE_SECTIONS.contains(sectioner)) {
                        if (DEBUG) {
                            Slog.i(TAG, "onGroupSummaryAdded skip bundled child: " + r);
                        }
                        continue;
                    }
                    if (maybeUngroupOnAppGrouped(r)) {
                        // Cleanup override group key here so that onNotificationPostedWithDelay
                        // has the right state synchronously
                        r.setOverrideGroupKey(null);
                    }
                }
            }
            mUngroupedAbuseNotifications.put(aggregateGroupKey, ungrouped);
        }
    }

+0 −6
Original line number Diff line number Diff line
@@ -10317,12 +10317,6 @@ public class NotificationManagerService extends SystemService {
        }
        if (isSummary) {
            mSummaryByGroupKey.put(group, r);
            if (notificationForceGrouping()) {
                // If any formerly-ungrouped notifications will be grouped by this summary, update
                // accordingly.
                mGroupHelper.onGroupSummaryAdded(r, mNotificationList);
            }
        }
        FlagChecker childrenFlagChecker = (flags) -> {
+183 −2
Original line number Diff line number Diff line
@@ -4176,7 +4176,6 @@ public class GroupHelperTest extends UiServiceTestCase {
                mUser, "specialGroup", true, IMPORTANCE_DEFAULT);
        notifList.add(groupSummary);
        summaryByGroupKey.put(groupSummary.getSbn().getGroupKey(), groupSummary);
        mGroupHelper.onGroupSummaryAdded(groupSummary, notifList);
        mGroupHelper.onNotificationPostedWithDelay(groupSummary, notifList, summaryByGroupKey);

        // One more notification posted to the group; because its summary already exists, it should
@@ -4222,7 +4221,6 @@ public class GroupHelperTest extends UiServiceTestCase {
                "summaryGroup", true, IMPORTANCE_DEFAULT);
        notifList.add(summary);
        summaryByGroupKey.put(summary.getSbn().getKey(), summary);
        mGroupHelper.onGroupSummaryAdded(summary, notifList);
        mGroupHelper.onNotificationPostedWithDelay(summary, notifList, summaryByGroupKey);

        // all of the above posted notifications should be autogrouped
@@ -4234,4 +4232,187 @@ public class GroupHelperTest extends UiServiceTestCase {
        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
                eq(expectedGroupKey), anyBoolean());
    }

    @Test
    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
    public void testGroupSummaryAdded_unAggregatesAutogroupedNotifications() {
        // Scenario:
        //  * child notifications posted before summary and force-grouped
        //  * summary posted => remove child notifications from autogroup and regroup into app group

        List<NotificationRecord> notifList = new ArrayList<>();
        Map<String, NotificationRecord> summaryByGroupKey = new HashMap<>();

        final String groupName = "testGrp";
        final int numChildNotif = AUTOGROUP_AT_COUNT;
        for (int i = 0; i < numChildNotif; i++) {
            NotificationRecord child = getNotificationRecord(mPkg, i, "", mUser, groupName, false,
                    IMPORTANCE_DEFAULT);
            notifList.add(child);
            mGroupHelper.onNotificationPostedWithDelay(child, notifList, summaryByGroupKey);
        }

        // Check that notifications were autogrouped
        final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(mPkg,
                AGGREGATE_GROUP_KEY + "AlertingSection", mUser.getIdentifier());
        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(mPkg), anyString(),
                eq(expectedGroupKey_alerting), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
        verify(mCallback, times(numChildNotif)).addAutoGroup(anyString(),
                eq(expectedGroupKey_alerting), eq(true));
        verify(mCallback, never()).removeAutoGroup(anyString());
        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
                any());

        for (NotificationRecord record: notifList) {
            if (record.getNotification().isGroupChild()) {
                record.setOverrideGroupKey(expectedGroupKey_alerting);
            }
        }

        // Post group summary
        Mockito.reset(mCallback);
        NotificationRecord groupSummary = getNotificationRecord(mPkg, 4242, "",
                mUser, groupName, true, IMPORTANCE_DEFAULT);
        notifList.add(groupSummary);
        summaryByGroupKey.put(groupSummary.getSbn().getGroupKey(), groupSummary);
        mGroupHelper.onNotificationPostedWithDelay(groupSummary, notifList, summaryByGroupKey);

        // Check that the updated notification was removed from the autogroup
        verify(mCallback, times(numChildNotif)).removeAutoGroup(anyString());
        verify(mCallback, times(numChildNotif - 1)).updateAutogroupSummary(anyInt(), anyString(),
                eq(expectedGroupKey_alerting), any());
        verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(mPkg),
                eq(expectedGroupKey_alerting));

        for (NotificationRecord record: notifList) {
            if (record.getNotification().isGroupChild()) {
                assertThat(record.getGroupKey()).isEqualTo(groupSummary.getGroupKey());
            }
        }
    }

    @Test
    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
    public void testGroupSummaryAddedDifferentChannel_unAggregatesAutogroupedNotifications() {
        // Scenario:
        //  * child notifications posted before summary and force-grouped
        //  * summary posted in different channel/section
        //  => remove child notifications from autogroup and regroup into app group

        List<NotificationRecord> notifList = new ArrayList<>();
        Map<String, NotificationRecord> summaryByGroupKey = new HashMap<>();

        final String groupName = "testGrp";
        final int numChildNotif = AUTOGROUP_AT_COUNT;
        for (int i = 0; i < numChildNotif; i++) {
            NotificationRecord child = getNotificationRecord(mPkg, i, "", mUser, groupName, false,
                    IMPORTANCE_DEFAULT);
            notifList.add(child);
            mGroupHelper.onNotificationPostedWithDelay(child, notifList, summaryByGroupKey);
        }

        // Check that notifications were autogrouped
        final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(mPkg,
                AGGREGATE_GROUP_KEY + "AlertingSection", mUser.getIdentifier());
        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(mPkg), anyString(),
                eq(expectedGroupKey_alerting), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
        verify(mCallback, times(numChildNotif)).addAutoGroup(anyString(),
                eq(expectedGroupKey_alerting), eq(true));
        verify(mCallback, never()).removeAutoGroup(anyString());
        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
                any());

        for (NotificationRecord record: notifList) {
            if (record.getNotification().isGroupChild()) {
                record.setOverrideGroupKey(expectedGroupKey_alerting);
            }
        }

        // Post group summary
        Mockito.reset(mCallback);
        NotificationRecord groupSummary = getNotificationRecord(mPkg, 4242, "",
                mUser, groupName, true, IMPORTANCE_LOW);
        notifList.add(groupSummary);
        summaryByGroupKey.put(groupSummary.getSbn().getGroupKey(), groupSummary);
        mGroupHelper.onNotificationPostedWithDelay(groupSummary, notifList, summaryByGroupKey);

        // Check that the updated notification was removed from the autogroup
        verify(mCallback, times(numChildNotif)).removeAutoGroup(anyString());
        verify(mCallback, times(numChildNotif - 1)).updateAutogroupSummary(anyInt(), anyString(),
                eq(expectedGroupKey_alerting), any());
        verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(mPkg),
                eq(expectedGroupKey_alerting));

        for (NotificationRecord record: notifList) {
            if (record.getNotification().isGroupChild()) {
                assertThat(record.getGroupKey()).isEqualTo(groupSummary.getGroupKey());
            }
        }
    }

    @Test
    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_CLASSIFICATION})
    public void testGroupSummaryAdded_doesnNotUnAggregateBundledNotifications() {
        // Scenario:
        //  * child notifications posted before summary and classified (bundled) and force-grouped
        //  * summary posted => child notifications are not removed from bundle autogroup

        List<NotificationRecord> notifList = new ArrayList<>();
        Map<String, NotificationRecord> summaryByGroupKey = new HashMap<>();

        final String groupName = "testGrp";
        final int numChildNotif = AUTOGROUP_AT_COUNT;
        final NotificationChannel socialChannel = new NotificationChannel(
                NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
                IMPORTANCE_LOW);
        for (int i = 0; i < numChildNotif; i++) {
            NotificationRecord child = getNotificationRecord(mPkg, i, "", mUser, groupName, false,
                    IMPORTANCE_DEFAULT);
            notifList.add(child);
            child.updateNotificationChannel(socialChannel);
            mGroupHelper.onNotificationPostedWithDelay(child, notifList, summaryByGroupKey);
        }

        // Check that notifications were autogrouped in the social section
        final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(mPkg,
                AGGREGATE_GROUP_KEY + "SocialSection", mUser.getIdentifier());
        final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
                BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
                NotificationChannel.SOCIAL_MEDIA_ID);
        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(mPkg), anyString(),
                eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
        verify(mCallback, times(numChildNotif)).addAutoGroup(anyString(),
                eq(expectedGroupKey_social), eq(true));
        verify(mCallback, never()).removeAutoGroup(anyString());
        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
        verify(mCallback, times(numChildNotif - AUTOGROUP_BUNDLES_AT_COUNT))
                .updateAutogroupSummary(anyInt(), anyString(), anyString(), any());

        for (NotificationRecord record: notifList) {
            if (record.getNotification().isGroupChild()) {
                record.setOverrideGroupKey(expectedGroupKey_social);
            }
        }

        // Post group summary
        Mockito.reset(mCallback);
        NotificationRecord groupSummary = getNotificationRecord(mPkg, 4242, "",
                mUser, groupName, true, IMPORTANCE_DEFAULT);
        notifList.add(groupSummary);
        summaryByGroupKey.put(groupSummary.getSbn().getGroupKey(), groupSummary);
        mGroupHelper.onNotificationPostedWithDelay(groupSummary, notifList, summaryByGroupKey);

        // Check that the updated notification were not removed from the autogroup
        verify(mCallback, never()).removeAutoGroup(anyString());
        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(),
                anyString(), any());
        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
        for (NotificationRecord record: notifList) {
            if (record.getNotification().isGroupChild()) {
                assertThat(record.getGroupKey()).isEqualTo(expectedGroupKey_social);
            }
        }
    }
}