Loading services/core/java/com/android/server/notification/NotificationManagerService.java +49 −14 Original line number Diff line number Diff line Loading @@ -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()); } } }); Loading Loading @@ -8667,8 +8668,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) { Loading Loading @@ -9390,8 +9391,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()); } } Loading Loading @@ -10372,13 +10373,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") Loading Loading @@ -10539,18 +10572,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); } Loading @@ -10558,12 +10592,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(), Loading services/core/java/com/android/server/notification/NotificationRecord.java +15 −0 Original line number Diff line number Diff line Loading @@ -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; } Loading services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +94 −0 Original line number Diff line number Diff line Loading @@ -13062,6 +13062,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 { Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +49 −14 Original line number Diff line number Diff line Loading @@ -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()); } } }); Loading Loading @@ -8667,8 +8668,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) { Loading Loading @@ -9390,8 +9391,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()); } } Loading Loading @@ -10372,13 +10373,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") Loading Loading @@ -10539,18 +10572,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); } Loading @@ -10558,12 +10592,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(), Loading
services/core/java/com/android/server/notification/NotificationRecord.java +15 −0 Original line number Diff line number Diff line Loading @@ -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; } Loading
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +94 −0 Original line number Diff line number Diff line Loading @@ -13062,6 +13062,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 {