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

Commit 969978d3 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Fix forced-grouping on notification channel updates" into main

parents 94f910e5 3a80cdca
Loading
Loading
Loading
Loading
+122 −62
Original line number Diff line number Diff line
@@ -830,10 +830,34 @@ public class GroupHelper {
                }
            }

            // The list of notification operations required after the channel update
            final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();

            // Check any already auto-grouped notifications that may need to be re-grouped
            // after the channel update
            notificationsToMove.addAll(
                    getAutogroupedNotificationsMoveOps(userId, pkgName,
                        notificationsToCheck));

            // Check any ungrouped notifications that may need to be auto-grouped
            // after the channel update
            notificationsToMove.addAll(
                    getUngroupedNotificationsMoveOps(userId, pkgName, notificationsToCheck));

            // Batch move to new section
            if (!notificationsToMove.isEmpty()) {
                moveNotificationsToNewSection(userId, pkgName, notificationsToMove);
            }
        }
    }

    @GuardedBy("mAggregatedNotifications")
    private List<NotificationMoveOp> getAutogroupedNotificationsMoveOps(int userId, String pkgName,
            ArrayMap<String, NotificationRecord> notificationsToCheck) {
        final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
        final Set<FullyQualifiedGroupKey> oldGroups =
                new HashSet<>(mAggregatedNotifications.keySet());
        // Move auto-grouped updated notifications from the old groups to the new groups (section)
        for (FullyQualifiedGroupKey oldFullAggKey : oldGroups) {
            // Only check aggregate groups that match the same userId & packageName
            if (pkgName.equals(oldFullAggKey.pkg) && userId == oldFullAggKey.userId) {
@@ -847,8 +871,7 @@ public class GroupHelper {
                for (String key : notificationsInAggGroup.keySet()) {
                    if (notificationsToCheck.get(key) != null) {
                        // check if section changes
                            NotificationSectioner sectioner = getSection(
                                    notificationsToCheck.get(key));
                        NotificationSectioner sectioner = getSection(notificationsToCheck.get(key));
                        if (sectioner == null) {
                            continue;
                        }
@@ -861,36 +884,63 @@ public class GroupHelper {
                            notificationsToMove.add(
                                    new NotificationMoveOp(notificationsToCheck.get(key),
                                        oldFullAggKey, newFullAggregateGroupKey));
                            notificationsToCheck.remove(key);
                        }
                    }
                }
            }
        }
        return notificationsToMove;
    }

                    if (newFullAggregateGroupKey != null) {
                        // Add any notifications left ungrouped to the new section
                        ArrayMap<String, NotificationAttributes> ungrouped =
                            mUngroupedAbuseNotifications.get(newFullAggregateGroupKey);
                        if (ungrouped != null) {
                            for (NotificationRecord r : notificationList) {
                                if (ungrouped.containsKey(r.getKey())) {
                                    if (DEBUG) {
                                        Log.i(TAG, "Add previously ungrouped: " + r);
    @GuardedBy("mAggregatedNotifications")
    private List<NotificationMoveOp> getUngroupedNotificationsMoveOps(int userId, String pkgName,
            final ArrayMap<String, NotificationRecord> notificationsToCheck) {
        final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
        // Move any remaining ungrouped updated notifications from the old ungrouped list
        // to the new ungrouped section list, if necessary
        if (!notificationsToCheck.isEmpty()) {
            final Set<FullyQualifiedGroupKey> oldUngroupedSectionKeys =
                    new HashSet<>(mUngroupedAbuseNotifications.keySet());
            for (FullyQualifiedGroupKey oldFullAggKey : oldUngroupedSectionKeys) {
                // Only check aggregate groups that match the same userId & packageName
                if (pkgName.equals(oldFullAggKey.pkg) && userId == oldFullAggKey.userId) {
                    final ArrayMap<String, NotificationAttributes> ungroupedOld =
                            mUngroupedAbuseNotifications.get(oldFullAggKey);
                    if (ungroupedOld == null) {
                        continue;
                    }
                                    notificationsToMove.add(
                                        new NotificationMoveOp(r, null, newFullAggregateGroupKey));

                    FullyQualifiedGroupKey newFullAggregateGroupKey = null;
                    final Set<String> ungroupedKeys = new HashSet<>(ungroupedOld.keySet());
                    for (String key : ungroupedKeys) {
                        NotificationRecord record = notificationsToCheck.get(key);
                        if (record != null) {
                            // check if section changes
                            NotificationSectioner sectioner = getSection(record);
                            if (sectioner == null) {
                                continue;
                            }
                            newFullAggregateGroupKey = new FullyQualifiedGroupKey(userId, pkgName,
                                    sectioner);
                            if (!oldFullAggKey.equals(newFullAggregateGroupKey)) {
                                if (DEBUG) {
                                    Log.i(TAG, "Change ungrouped section: " + key);
                                }
                            //Cleanup mUngroupedAbuseNotifications
                            mUngroupedAbuseNotifications.remove(newFullAggregateGroupKey);
                                notificationsToMove.add(
                                        new NotificationMoveOp(record, oldFullAggKey,
                                            newFullAggregateGroupKey));
                                notificationsToCheck.remove(key);
                                //Remove from previous ungrouped list
                                ungroupedOld.remove(key);
                            }
                        }
                    }
                    mUngroupedAbuseNotifications.put(oldFullAggKey, ungroupedOld);
                }

            // Batch move to new section
            if (!notificationsToMove.isEmpty()) {
                moveNotificationsToNewSection(userId, pkgName, notificationsToMove);
            }
        }
        return notificationsToMove;
    }

    @GuardedBy("mAggregatedNotifications")
@@ -898,6 +948,7 @@ public class GroupHelper {
            final List<NotificationMoveOp> notificationsToMove) {
        record GroupUpdateOp(FullyQualifiedGroupKey groupKey, NotificationRecord record,
                             boolean hasSummary) { }
        // Bundled operations to apply to groups affected by the channel update
        ArrayMap<FullyQualifiedGroupKey, GroupUpdateOp> groupsToUpdate = new ArrayMap<>();

        for (NotificationMoveOp moveOp: notificationsToMove) {
@@ -927,31 +978,32 @@ public class GroupHelper {
                }
            }

            // Add/update aggregate summary for new group
            // Add moved notifications to the ungrouped list for new group and do grouping
            // after all notifications have been handled
            if (newFullAggregateGroupKey != null) {
                final ArrayMap<String, NotificationAttributes> newAggregatedNotificationsAttrs =
                        mAggregatedNotifications.getOrDefault(newFullAggregateGroupKey,
                            new ArrayMap<>());
                boolean newGroupExists = !newAggregatedNotificationsAttrs.isEmpty();
                newAggregatedNotificationsAttrs.put(record.getKey(),
                        new NotificationAttributes(record.getFlags(),
                boolean hasSummary = !newAggregatedNotificationsAttrs.isEmpty();
                ArrayMap<String, NotificationAttributes> ungrouped =
                        mUngroupedAbuseNotifications.getOrDefault(newFullAggregateGroupKey,
                            new ArrayMap<>());
                ungrouped.put(record.getKey(), new NotificationAttributes(
                        record.getFlags(),
                        record.getNotification().getSmallIcon(),
                        record.getNotification().color,
                        record.getNotification().visibility,
                        record.getNotification().getGroupAlertBehavior(),
                        record.getChannel().getId()));
                mAggregatedNotifications.put(newFullAggregateGroupKey,
                        newAggregatedNotificationsAttrs);
                mUngroupedAbuseNotifications.put(newFullAggregateGroupKey, ungrouped);

                record.setOverrideGroupKey(null);

                // Only add once, for triggering notification
                if (!groupsToUpdate.containsKey(newFullAggregateGroupKey)) {
                    groupsToUpdate.put(newFullAggregateGroupKey,
                            new GroupUpdateOp(newFullAggregateGroupKey, record, newGroupExists));
                        new GroupUpdateOp(newFullAggregateGroupKey, record, hasSummary));
                }

                // Add notification to new group. do not request resort
                record.setOverrideGroupKey(null);
                mCallback.addAutoGroup(record.getKey(), newFullAggregateGroupKey.toString(), false);
            }
        }

@@ -959,18 +1011,26 @@ public class GroupHelper {
        for (FullyQualifiedGroupKey groupKey : groupsToUpdate.keySet()) {
            final ArrayMap<String, NotificationAttributes> aggregatedNotificationsAttrs =
                    mAggregatedNotifications.getOrDefault(groupKey, new ArrayMap<>());
            if (aggregatedNotificationsAttrs.isEmpty()) {
                mCallback.removeAutoGroupSummary(userId, pkgName, groupKey.toString());
                mAggregatedNotifications.remove(groupKey);
            } else {
            final ArrayMap<String, NotificationAttributes> ungrouped =
                    mUngroupedAbuseNotifications.getOrDefault(groupKey, new ArrayMap<>());

            NotificationRecord triggeringNotification = groupsToUpdate.get(groupKey).record;
            boolean hasSummary = groupsToUpdate.get(groupKey).hasSummary;
            //Group needs to be created/updated
            if (ungrouped.size() >= mAutoGroupAtCount
                    || (hasSummary && !aggregatedNotificationsAttrs.isEmpty())) {
                NotificationSectioner sectioner = getSection(triggeringNotification);
                if (sectioner == null) {
                    continue;
                }
                updateAggregateAppGroup(groupKey, triggeringNotification.getKey(), hasSummary,
                        sectioner.mSummaryId);
                aggregateUngroupedNotifications(groupKey, triggeringNotification.getKey(),
                        ungrouped, hasSummary, sectioner.mSummaryId);
            } else {
                // Remove empty groups
                if (aggregatedNotificationsAttrs.isEmpty() && hasSummary) {
                    mCallback.removeAutoGroupSummary(userId, pkgName, groupKey.toString());
                    mAggregatedNotifications.remove(groupKey);
                }
            }
        }
    }
+65 −9
Original line number Diff line number Diff line
@@ -2204,7 +2204,7 @@ public class GroupHelperTest extends UiServiceTestCase {
        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
                eq(expectedGroupKey_silent), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
                eq(expectedGroupKey_silent), eq(false));
                eq(expectedGroupKey_silent), eq(true));

        // Check that the alerting section group is removed
        verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
@@ -2264,13 +2264,15 @@ public class GroupHelperTest extends UiServiceTestCase {
                notificationList);

        // Check that channel1's notifications are moved to the silent section group
        expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
                mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
                "TEST_CHANNEL_ID1");
        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
                eq(expectedGroupKey_silent), anyInt(), eq(expectedSummaryAttr));
        verify(mCallback, times(AUTOGROUP_AT_COUNT/2 + 1)).addAutoGroup(anyString(),
                eq(expectedGroupKey_silent), eq(false));
        // But not enough to auto-group => remove override group key
        verify(mCallback, never()).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
                anyString(), anyInt(), any());
        verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
        for (NotificationRecord record: notificationList) {
            if (record.getChannel().getId().equals(channel1.getId())) {
                assertThat(record.getSbn().getOverrideGroupKey()).isNull();
            }
        }

        // Check that the alerting section group is not removed, only updated
        expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
@@ -2343,7 +2345,7 @@ public class GroupHelperTest extends UiServiceTestCase {
        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
                eq(expectedGroupKey_silent), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
        verify(mCallback, times(numSilentGroupNotifications)).addAutoGroup(anyString(),
                eq(expectedGroupKey_silent), eq(false));
                eq(expectedGroupKey_silent), eq(true));

        // Check that the alerting section group is removed
        verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
@@ -2352,6 +2354,60 @@ public class GroupHelperTest extends UiServiceTestCase {
                any());
    }

    @Test
    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
    public void testAutogroup_updateChannel_reachedMinAutogroupCount() {
        final String pkg = "package";
        final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1",
                "TEST_CHANNEL_ID1", IMPORTANCE_DEFAULT);
        final NotificationChannel channel2 = new NotificationChannel("TEST_CHANNEL_ID2",
                "TEST_CHANNEL_ID2", IMPORTANCE_LOW);
        final List<NotificationRecord> notificationList = new ArrayList<>();
        // Post notifications with different channels that would autogroup in different sections
        NotificationRecord r;
        // Not enough notifications to autogroup initially
        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
            if (i % 2 == 0) {
                r = getNotificationRecord(pkg, i, String.valueOf(i),
                    UserHandle.SYSTEM, null, false, channel1);
            } else {
                r = getNotificationRecord(pkg, i, String.valueOf(i),
                    UserHandle.SYSTEM, null, false, channel2);
            }
            notificationList.add(r);
            mGroupHelper.onNotificationPosted(r, false);
        }
        verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
                anyString(), anyInt(), any());
        verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
        verify(mCallback, never()).removeAutoGroup(anyString());
        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
                any());
        Mockito.reset(mCallback);

        // Update channel1's importance
        final String expectedGroupKey_silent = GroupHelper.getFullAggregateGroupKey(pkg,
                AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
        channel1.setImportance(IMPORTANCE_LOW);
        for (NotificationRecord record: notificationList) {
            if (record.getChannel().getId().equals(channel1.getId())) {
                record.updateNotificationChannel(channel1);
            }
        }
        mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel1,
                notificationList);

        // Check that channel1's notifications are moved to the silent section & autogroup all
        NotificationAttributes expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
                mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
                "TEST_CHANNEL_ID1");
        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
                eq(expectedGroupKey_silent), eq(true));
        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
                eq(expectedGroupKey_silent), anyInt(), eq(expectedSummaryAttr));
    }

    @Test
    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
            Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})