Notification API hardening: forced auto-grouping
NotificationManagerService will post a group summary on the app's behalf
and group notifications based on several scenarios:
1. Groups without summary
2. Summaries without child notifications
3. Singleton groups (summaries with single or low number of children)
Flag: android.service.notification.notification_force_grouping
Flag: com.android.server.notification.notification_force_group_singletons
Test: atest GroupHelperTest NotificationManagerServiceTest
Bug: 336488844
Change-Id: Ie1c0727c5de5e3dc55ca2a1c177d9b4660ad588d
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index aea15e1..007fa5d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4123,6 +4123,17 @@
}
/**
+ * Sets which type of notifications in a group are responsible for audibly alerting the
+ * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN},
+ * {@link #GROUP_ALERT_SUMMARY}.
+ * @param groupAlertBehavior
+ * @hide
+ */
+ public void setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) {
+ mGroupAlertBehavior = groupAlertBehavior;
+ }
+
+ /**
* Returns the bubble metadata that will be used to display app content in a floating window
* over the existing foreground activity.
*/
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 264b53c..146c2b6 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -176,7 +176,11 @@
private String groupKey() {
if (overrideGroupKey != null) {
- return user.getIdentifier() + "|" + pkg + "|" + "g:" + overrideGroupKey;
+ if (Flags.notificationForceGrouping()) {
+ return overrideGroupKey;
+ } else {
+ return user.getIdentifier() + "|" + pkg + "|" + "g:" + overrideGroupKey;
+ }
}
final String group = getNotification().getGroup();
final String sortKey = getNotification().getSortKey();
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
index bdef041..51961a8 100644
--- a/core/java/android/service/notification/flags.aconfig
+++ b/core/java/android/service/notification/flags.aconfig
@@ -43,4 +43,18 @@
namespace: "systemui"
description: "Allows the NAS to classify notifications"
bug: "343988084"
+}
+
+flag {
+ name: "notification_force_grouping"
+ namespace: "systemui"
+ description: "This flag controls the forced auto-grouping feature"
+ bug: "336488844"
+}
+
+flag {
+ name: "notification_silent_flag"
+ namespace: "systemui"
+ description: "Guards the new FLAG_SILENT Notification flag"
+ bug: "336488844"
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 13cc99c..1cdab44 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -24,9 +24,14 @@
import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.Notification.VISIBILITY_PRIVATE;
import static android.app.Notification.VISIBILITY_PUBLIC;
+import static android.service.notification.Flags.notificationForceGrouping;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -34,7 +39,9 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.Log;
import android.util.Slog;
import com.android.internal.R;
@@ -42,14 +49,20 @@
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.Set;
+import java.util.function.Predicate;
/**
* NotificationManagerService helper for auto-grouping notifications.
*/
public class GroupHelper {
private static final String TAG = "GroupHelper";
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
protected static final String AUTOGROUP_KEY = "ranker_group";
@@ -63,8 +76,16 @@
// Flags that autogroup summaries inherits if any child has them
private static final int ANY_CHILDREN_FLAGS = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR;
+ protected static final String AGGREGATE_GROUP_KEY = "Aggregate_";
+
+ // If an app posts more than NotificationManagerService.AUTOGROUP_SPARSE_GROUPS_AT_COUNT groups
+ // with less than this value, they will be forced grouped
+ private static final int MIN_CHILD_COUNT_TO_AVOID_FORCE_GROUPING = 3;
+
+
private final Callback mCallback;
private final int mAutoGroupAtCount;
+ private final int mAutogroupSparseGroupsAtCount;
private final Context mContext;
private final PackageManager mPackageManager;
@@ -75,12 +96,41 @@
private final ArrayMap<String, ArrayMap<String, NotificationAttributes>> mUngroupedNotifications
= new ArrayMap<>();
+ // Contains the list of notifications that should be aggregated (forced grouping)
+ // but there are less than mAutoGroupAtCount per section for a package.
+ // The primary map's key is the full aggregated group key: userId|pkgName|g:groupName
+ // The internal map's key is the notification record key
+ @GuardedBy("mAggregatedNotifications")
+ private final ArrayMap<FullyQualifiedGroupKey, ArrayMap<String, NotificationAttributes>>
+ 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
+ @GuardedBy("mAggregatedNotifications")
+ private final ArrayMap<FullyQualifiedGroupKey, CachedSummary>
+ mCanceledSummaries = new ArrayMap<>();
+
+ // Represents the current state of the aggregated (forced grouped) notifications
+ // Key is the full aggregated group key: userId|pkgName|g:groupName
+ // And groupName is "Aggregate_"+sectionName
+ @GuardedBy("mAggregatedNotifications")
+ private final ArrayMap<FullyQualifiedGroupKey, ArrayMap<String, NotificationAttributes>>
+ mAggregatedNotifications = new ArrayMap<>();
+
+ private static final List<NotificationSectioner> NOTIFICATION_SHADE_SECTIONS = List.of(
+ new NotificationSectioner("AlertingSection", 0, (record) ->
+ record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT),
+ new NotificationSectioner("SilentSection", 1, (record) ->
+ record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT));
+
public GroupHelper(Context context, PackageManager packageManager, int autoGroupAtCount,
- Callback callback) {
+ int autoGroupSparseGroupsAtCount, Callback callback) {
mAutoGroupAtCount = autoGroupAtCount;
mCallback = callback;
mContext = context;
mPackageManager = packageManager;
+ mAutogroupSparseGroupsAtCount = autoGroupSparseGroupsAtCount;
}
private String generatePackageKey(int userId, String pkg) {
@@ -88,40 +138,50 @@
}
@VisibleForTesting
- @GuardedBy("mUngroupedNotifications")
- protected int getAutogroupSummaryFlags(
- @NonNull final ArrayMap<String, NotificationAttributes> children) {
+ protected static int getAutogroupSummaryFlags(
+ @NonNull final ArrayMap<String, NotificationAttributes> childrenMap) {
+ final Collection<NotificationAttributes> children = childrenMap.values();
boolean allChildrenHasFlag = children.size() > 0;
int anyChildFlagSet = 0;
- for (int i = 0; i < children.size(); i++) {
- if (!hasAnyFlag(children.valueAt(i).flags, ALL_CHILDREN_FLAG)) {
+ for (NotificationAttributes childAttr: children) {
+ if (!hasAnyFlag(childAttr.flags, ALL_CHILDREN_FLAG)) {
allChildrenHasFlag = false;
}
- if (hasAnyFlag(children.valueAt(i).flags, ANY_CHILDREN_FLAGS)) {
- anyChildFlagSet |= (children.valueAt(i).flags & ANY_CHILDREN_FLAGS);
+ if (hasAnyFlag(childAttr.flags, ANY_CHILDREN_FLAGS)) {
+ anyChildFlagSet |= (childAttr.flags & ANY_CHILDREN_FLAGS);
}
}
return BASE_FLAGS | (allChildrenHasFlag ? ALL_CHILDREN_FLAG : 0) | anyChildFlagSet;
}
- private boolean hasAnyFlag(int flags, int mask) {
+ private static boolean hasAnyFlag(int flags, int mask) {
return (flags & mask) != 0;
}
/**
* Called when a notification is newly posted. Checks whether that notification, and all other
* active notifications should be grouped or ungrouped atuomatically, and returns whether.
- * @param sbn The posted notification.
+ * @param record The posted notification.
* @param autogroupSummaryExists Whether a summary for this notification already exists.
* @return Whether the provided notification should be autogrouped synchronously.
*/
- public boolean onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) {
+ public boolean onNotificationPosted(NotificationRecord record, boolean autogroupSummaryExists) {
boolean sbnToBeAutogrouped = false;
try {
- if (!sbn.isAppGroup()) {
- sbnToBeAutogrouped = maybeGroup(sbn, autogroupSummaryExists);
+ if (notificationForceGrouping()) {
+ final StatusBarNotification sbn = record.getSbn();
+ if (!sbn.isAppGroup()) {
+ sbnToBeAutogrouped = maybeGroupWithSections(record, autogroupSummaryExists);
+ } else {
+ maybeUngroupWithSections(record);
+ }
} else {
- maybeUngroup(sbn, false, sbn.getUserId());
+ final StatusBarNotification sbn = record.getSbn();
+ if (!sbn.isAppGroup()) {
+ sbnToBeAutogrouped = maybeGroup(sbn, autogroupSummaryExists);
+ } else {
+ maybeUngroup(sbn, false, sbn.getUserId());
+ }
}
} catch (Exception e) {
Slog.e(TAG, "Failure processing new notification", e);
@@ -129,9 +189,20 @@
return sbnToBeAutogrouped;
}
- public void onNotificationRemoved(StatusBarNotification sbn) {
+ /**
+ * Called when a notification was removed. Checks if that notification was part of an autogroup
+ * and triggers any necessary cleanups: summary removal, clearing caches etc.
+ *
+ * @param record The removed notification.
+ */
+ public void onNotificationRemoved(NotificationRecord record) {
try {
- maybeUngroup(sbn, true, sbn.getUserId());
+ if (notificationForceGrouping()) {
+ onNotificationRemoved(record, new ArrayList<>());
+ } else {
+ final StatusBarNotification sbn = record.getSbn();
+ maybeUngroup(sbn, true, sbn.getUserId());
+ }
} catch (Exception e) {
Slog.e(TAG, "Error processing canceled notification", e);
}
@@ -156,10 +227,10 @@
String packageKey = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
final ArrayMap<String, NotificationAttributes> children =
mUngroupedNotifications.getOrDefault(packageKey, new ArrayMap<>());
-
NotificationAttributes attr = new NotificationAttributes(sbn.getNotification().flags,
sbn.getNotification().getSmallIcon(), sbn.getNotification().color,
- sbn.getNotification().visibility);
+ sbn.getNotification().visibility, Notification.GROUP_ALERT_CHILDREN,
+ sbn.getNotification().getChannelId());
children.put(sbn.getKey(), attr);
mUngroupedNotifications.put(packageKey, children);
@@ -173,17 +244,20 @@
if (autogroupSummaryExists) {
NotificationAttributes attr = new NotificationAttributes(flags,
sbn.getNotification().getSmallIcon(), sbn.getNotification().color,
- VISIBILITY_PRIVATE);
+ VISIBILITY_PRIVATE, Notification.GROUP_ALERT_CHILDREN,
+ sbn.getNotification().getChannelId());
if (Flags.autogroupSummaryIconUpdate()) {
attr = updateAutobundledSummaryAttributes(sbn.getPackageName(), childrenAttr,
attr);
}
- mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), attr);
+ mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(),
+ AUTOGROUP_KEY, attr);
} else {
Icon summaryIcon = sbn.getNotification().getSmallIcon();
int summaryIconColor = sbn.getNotification().color;
int summaryVisibility = VISIBILITY_PRIVATE;
+ String summaryChannelId = sbn.getNotification().getChannelId();
if (Flags.autogroupSummaryIconUpdate()) {
// Calculate the initial summary icon, icon color and visibility
NotificationAttributes iconAttr = getAutobundledSummaryAttributes(
@@ -191,12 +265,14 @@
summaryIcon = iconAttr.icon;
summaryIconColor = iconAttr.iconColor;
summaryVisibility = iconAttr.visibility;
+ summaryChannelId = iconAttr.channelId;
}
NotificationAttributes attr = new NotificationAttributes(flags, summaryIcon,
- summaryIconColor, summaryVisibility);
+ summaryIconColor, summaryVisibility, Notification.GROUP_ALERT_CHILDREN,
+ summaryChannelId);
mCallback.addAutoGroupSummary(sbn.getUserId(), sbn.getPackageName(), sbn.getKey(),
- attr);
+ AUTOGROUP_KEY, Integer.MAX_VALUE, attr);
}
for (String keyToGroup : notificationsToGroup) {
if (android.app.Flags.checkAutogroupBeforePost()) {
@@ -204,10 +280,10 @@
// Autogrouping for the provided notification is to be done synchronously.
sbnToBeAutogrouped = true;
} else {
- mCallback.addAutoGroup(keyToGroup, /*requestSort=*/true);
+ mCallback.addAutoGroup(keyToGroup, AUTOGROUP_KEY, /*requestSort=*/true);
}
} else {
- mCallback.addAutoGroup(keyToGroup, /*requestSort=*/true);
+ mCallback.addAutoGroup(keyToGroup, AUTOGROUP_KEY, /*requestSort=*/true);
}
}
}
@@ -263,11 +339,12 @@
}
if (removeSummary) {
- mCallback.removeAutoGroupSummary(userId, sbn.getPackageName());
+ mCallback.removeAutoGroupSummary(userId, sbn.getPackageName(), AUTOGROUP_KEY);
} else {
NotificationAttributes attr = new NotificationAttributes(summaryFlags,
sbn.getNotification().getSmallIcon(), sbn.getNotification().color,
- VISIBILITY_PRIVATE);
+ VISIBILITY_PRIVATE, Notification.GROUP_ALERT_CHILDREN,
+ sbn.getNotification().getChannelId());
boolean attributesUpdated = false;
if (Flags.autogroupSummaryIconUpdate()) {
NotificationAttributes newAttr = updateAutobundledSummaryAttributes(
@@ -279,7 +356,7 @@
}
if (updateSummaryFlags || attributesUpdated) {
- mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), attr);
+ mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), AUTOGROUP_KEY, attr);
}
}
if (removeAutogroupOverlay) {
@@ -287,16 +364,6 @@
}
}
- @VisibleForTesting
- int getNotGroupedByAppCount(int userId, String pkg) {
- synchronized (mUngroupedNotifications) {
- String key = generatePackageKey(userId, pkg);
- final ArrayMap<String, NotificationAttributes> children =
- mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
- return children.size();
- }
- }
-
NotificationAttributes getAutobundledSummaryAttributes(@NonNull String packageName,
@NonNull List<NotificationAttributes> childrenAttr) {
Icon newIcon = null;
@@ -338,7 +405,20 @@
newColor = COLOR_DEFAULT;
}
- return new NotificationAttributes(0, newIcon, newColor, newVisibility);
+ // Use GROUP_ALERT_CHILDREN
+ // Unless all children have GROUP_ALERT_SUMMARY => avoid muting all notifications in group
+ int newGroupAlertBehavior = Notification.GROUP_ALERT_SUMMARY;
+ for (NotificationAttributes attr: childrenAttr) {
+ if (attr.groupAlertBehavior != Notification.GROUP_ALERT_SUMMARY) {
+ newGroupAlertBehavior = Notification.GROUP_ALERT_CHILDREN;
+ break;
+ }
+ }
+
+ String channelId = !childrenAttr.isEmpty() ? childrenAttr.get(0).channelId : null;
+
+ return new NotificationAttributes(0, newIcon, newColor, newVisibility,
+ newGroupAlertBehavior, channelId);
}
NotificationAttributes updateAutobundledSummaryAttributes(@NonNull String packageName,
@@ -348,14 +428,28 @@
childrenAttr);
Icon newIcon = newAttr.icon;
int newColor = newAttr.iconColor;
+ String newChannelId = newAttr.channelId;
if (newAttr.icon == null) {
newIcon = oldAttr.icon;
}
if (newAttr.iconColor == Notification.COLOR_INVALID) {
newColor = oldAttr.iconColor;
}
+ if (newAttr.channelId == null) {
+ newChannelId = oldAttr.channelId;
+ }
- return new NotificationAttributes(oldAttr.flags, newIcon, newColor, newAttr.visibility);
+ return new NotificationAttributes(oldAttr.flags, newIcon, newColor, newAttr.visibility,
+ oldAttr.groupAlertBehavior, newChannelId);
+ }
+
+ private NotificationAttributes getSummaryAttributes(String pkgName,
+ ArrayMap<String, NotificationAttributes> childrenMap) {
+ int flags = getAutogroupSummaryFlags(childrenMap);
+ NotificationAttributes attr = getAutobundledSummaryAttributes(pkgName,
+ childrenMap.values().stream().toList());
+ return new NotificationAttributes(flags, attr.icon, attr.iconColor, attr.visibility,
+ attr.groupAlertBehavior, attr.channelId);
}
/**
@@ -388,17 +482,865 @@
}
}
+ /**
+ * A non-app grouped notification has been added or updated
+ * Evaluate if:
+ * (a) an existing autogroup summary needs updated attributes
+ * (b) a new autogroup summary needs to be added with correct attributes
+ * (c) other non-app grouped children need to be moved to the autogroup
+ *
+ * This method implements autogrouping with sections support.
+ *
+ * And stores the list of upgrouped notifications & their flags
+ */
+ private boolean maybeGroupWithSections(NotificationRecord record,
+ boolean autogroupSummaryExists) {
+ final StatusBarNotification sbn = record.getSbn();
+ boolean sbnToBeAutogrouped = false;
+
+ final NotificationSectioner sectioner = getSection(record);
+ if (sectioner == null) {
+ if (DEBUG) {
+ Log.i(TAG, "Skipping autogrouping for " + record + " no valid section found.");
+ }
+ return false;
+ }
+
+ final String pkgName = sbn.getPackageName();
+ final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey(
+ record.getUserId(), pkgName, sectioner);
+
+ // This notification is already aggregated
+ if (record.getGroupKey().equals(fullAggregateGroupKey.toString())) {
+ return false;
+ }
+
+ synchronized (mAggregatedNotifications) {
+ ArrayMap<String, NotificationAttributes> ungrouped =
+ mUngroupedAbuseNotifications.getOrDefault(fullAggregateGroupKey, 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()));
+ mUngroupedAbuseNotifications.put(fullAggregateGroupKey, ungrouped);
+
+ // scenario 0: ungrouped notifications
+ if (ungrouped.size() >= mAutoGroupAtCount || autogroupSummaryExists) {
+ if (DEBUG) {
+ if (ungrouped.size() >= mAutoGroupAtCount) {
+ Log.i(TAG,
+ "Found >=" + mAutoGroupAtCount
+ + " ungrouped notifications => force grouping");
+ } else {
+ Log.i(TAG, "Found aggregate summary => force grouping");
+ }
+ }
+
+ final ArrayMap<String, NotificationAttributes> aggregatedNotificationsAttrs =
+ mAggregatedNotifications.getOrDefault(fullAggregateGroupKey, new ArrayMap<>());
+ aggregatedNotificationsAttrs.putAll(ungrouped);
+ mAggregatedNotifications.put(fullAggregateGroupKey, aggregatedNotificationsAttrs);
+
+ // add/update aggregate summary
+ updateAggregateAppGroup(fullAggregateGroupKey, record.getKey(),
+ autogroupSummaryExists, sectioner.mSummaryId);
+
+ // add notification to aggregate group
+ for (String keyToGroup : ungrouped.keySet()) {
+ if (android.app.Flags.checkAutogroupBeforePost()) {
+ if (keyToGroup.equals(record.getKey())) {
+ // Autogrouping for the posted notification is to be done synchronously.
+ sbnToBeAutogrouped = true;
+ } else {
+ mCallback.addAutoGroup(keyToGroup, fullAggregateGroupKey.toString(),
+ true);
+ }
+ } else {
+ mCallback.addAutoGroup(keyToGroup, fullAggregateGroupKey.toString(), true);
+ }
+ }
+
+ //cleanup mUngroupedAbuseNotifications
+ mUngroupedAbuseNotifications.remove(fullAggregateGroupKey);
+ }
+ }
+
+ return sbnToBeAutogrouped;
+ }
+
+ /**
+ * A notification was added that's app grouped.
+ * Evaluate whether:
+ * (a) an existing autogroup summary needs updated attributes
+ * (b) if we need to remove our autogroup overlay for this notification
+ * (c) we need to remove the autogroup summary
+ *
+ * This method implements autogrouping with sections support.
+ *
+ * And updates the internal state of un-app-grouped notifications and their flags.
+ */
+ private void maybeUngroupWithSections(NotificationRecord record) {
+ final StatusBarNotification sbn = record.getSbn();
+ final String pkgName = sbn.getPackageName();
+ final int userId = record.getUserId();
+ final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey(userId,
+ pkgName, getSection(record));
+
+ synchronized (mAggregatedNotifications) {
+ // if this notification still exists and has an autogroup overlay, but is now
+ // grouped by the app, clear the overlay
+ ArrayMap<String, NotificationAttributes> ungrouped =
+ mUngroupedAbuseNotifications.getOrDefault(fullAggregateGroupKey, new ArrayMap<>());
+ ungrouped.remove(sbn.getKey());
+ mUngroupedAbuseNotifications.put(fullAggregateGroupKey, ungrouped);
+
+ final ArrayMap<String, NotificationAttributes> aggregatedNotificationsAttrs =
+ mAggregatedNotifications.getOrDefault(fullAggregateGroupKey, new ArrayMap<>());
+ // check if the removed notification was part of the aggregate group
+ if (aggregatedNotificationsAttrs.containsKey(record.getKey())) {
+ aggregatedNotificationsAttrs.remove(sbn.getKey());
+ mAggregatedNotifications.put(fullAggregateGroupKey, aggregatedNotificationsAttrs);
+
+ if (DEBUG) {
+ Log.i(TAG, "maybeUngroup removeAutoGroup: " + record);
+ }
+
+ mCallback.removeAutoGroup(sbn.getKey());
+
+ if (aggregatedNotificationsAttrs.isEmpty()) {
+ if (DEBUG) {
+ Log.i(TAG, "Aggregate group is empty: " + fullAggregateGroupKey);
+ }
+ mCallback.removeAutoGroupSummary(userId, pkgName,
+ fullAggregateGroupKey.toString());
+ mAggregatedNotifications.remove(fullAggregateGroupKey);
+ } else {
+ if (DEBUG) {
+ Log.i(TAG, "Aggregate group not empty, updating: " + fullAggregateGroupKey);
+ }
+ updateAggregateAppGroup(fullAggregateGroupKey, sbn.getKey(), true, 0);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called when a notification is newly posted, after some delay, so that the app
+ * has a chance to post a group summary or children (complete a group).
+ * Checks whether that notification and other active notifications should be forced grouped
+ * because their grouping is incorrect:
+ * - missing summary
+ * - only summaries
+ * - sparse groups == multiple groups with very few notifications
+ *
+ * @param record the notification that was posted
+ * @param notificationList the full notification list from NotificationManagerService
+ * @param summaryByGroupKey the map of group summaries from NotificationManagerService
+ */
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
+ protected void onNotificationPostedWithDelay(final NotificationRecord record,
+ final List<NotificationRecord> notificationList,
+ final Map<String, NotificationRecord> summaryByGroupKey) {
+ // Ungrouped notifications are handled separately in
+ // {@link #onNotificationPosted(StatusBarNotification, boolean)}
+ final StatusBarNotification sbn = record.getSbn();
+ if (!sbn.isAppGroup()) {
+ return;
+ }
+
+ if (record.isCanceled) {
+ return;
+ }
+
+ final NotificationSectioner sectioner = getSection(record);
+ if (sectioner == null) {
+ if (DEBUG) {
+ Log.i(TAG, "Skipping autogrouping for " + record + " no valid section found.");
+ }
+ return;
+ }
+
+ final String pkgName = sbn.getPackageName();
+ final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey(
+ record.getUserId(), pkgName, sectioner);
+
+ // This notification is already aggregated
+ if (record.getGroupKey().equals(fullAggregateGroupKey.toString())) {
+ return;
+ }
+
+ synchronized (mAggregatedNotifications) {
+ // scenario 1: group w/o summary
+ // scenario 2: summary w/o children
+ if (isGroupChildWithoutSummary(record, summaryByGroupKey) ||
+ isGroupSummaryWithoutChildren(record, notificationList)) {
+ if (DEBUG) {
+ Log.i(TAG, "isGroupChildWithoutSummary OR isGroupSummaryWithoutChild"
+ + record);
+ }
+
+ ArrayMap<String, NotificationAttributes> ungrouped =
+ mUngroupedAbuseNotifications.getOrDefault(fullAggregateGroupKey,
+ 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()));
+ mUngroupedAbuseNotifications.put(fullAggregateGroupKey, ungrouped);
+ // Create/update summary and group if >= mAutoGroupAtCount notifications
+ // or if aggregate group exists
+ boolean hasSummary = !mAggregatedNotifications.getOrDefault(fullAggregateGroupKey,
+ new ArrayMap<>()).isEmpty();
+ if (ungrouped.size() >= mAutoGroupAtCount || hasSummary) {
+ if (DEBUG) {
+ if (ungrouped.size() >= mAutoGroupAtCount) {
+ Log.i(TAG,
+ "Found >=" + mAutoGroupAtCount
+ + " ungrouped notifications => force grouping");
+ } else {
+ Log.i(TAG, "Found aggregate summary => force grouping");
+ }
+ }
+ aggregateUngroupedNotifications(fullAggregateGroupKey, sbn.getKey(),
+ ungrouped, hasSummary, sectioner.mSummaryId);
+ }
+
+ return;
+ }
+
+ // scenario 3: sparse/singleton groups
+ if (Flags.notificationForceGroupSingletons()) {
+ groupSparseGroups(record, notificationList, summaryByGroupKey, sectioner,
+ fullAggregateGroupKey);
+ }
+ }
+ }
+
+ /**
+ * Called when a notification is removed, so that this helper can adjust the aggregate groups:
+ * - Removes the autogroup summary of the notification's section
+ * if the record was the last child.
+ * - Recalculates the autogroup summary "attributes":
+ * icon, icon color, visibility, groupAlertBehavior, flags - if the removed record was
+ * part of an autogroup.
+ * - Removes the saved summary of the original group, if the record was the last remaining
+ * child of a sparse group that was forced auto-grouped.
+ *
+ * see also {@link #onNotificationPostedWithDelay(NotificationRecord, List, Map)}
+ *
+ * @param record the removed notification
+ * @param notificationList the full notification list from NotificationManagerService
+ */
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
+ protected void onNotificationRemoved(final NotificationRecord record,
+ final List<NotificationRecord> notificationList) {
+ final StatusBarNotification sbn = record.getSbn();
+ final String pkgName = sbn.getPackageName();
+ final int userId = record.getUserId();
+ final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey(userId,
+ pkgName, getSection(record));
+
+ synchronized (mAggregatedNotifications) {
+ ArrayMap<String, NotificationAttributes> ungrouped =
+ mUngroupedAbuseNotifications.getOrDefault(fullAggregateGroupKey, new ArrayMap<>());
+ ungrouped.remove(record.getKey());
+ mUngroupedAbuseNotifications.put(fullAggregateGroupKey, ungrouped);
+
+ final ArrayMap<String, NotificationAttributes> aggregatedNotificationsAttrs =
+ mAggregatedNotifications.getOrDefault(fullAggregateGroupKey, new ArrayMap<>());
+ // check if the removed notification was part of the aggregate group
+ if (record.getGroupKey().equals(fullAggregateGroupKey.toString())
+ || aggregatedNotificationsAttrs.containsKey(record.getKey())) {
+ aggregatedNotificationsAttrs.remove(record.getKey());
+ mAggregatedNotifications.put(fullAggregateGroupKey, aggregatedNotificationsAttrs);
+
+ if (aggregatedNotificationsAttrs.isEmpty()) {
+ if (DEBUG) {
+ Log.i(TAG, "Aggregate group is empty: " + fullAggregateGroupKey);
+ }
+ mCallback.removeAutoGroupSummary(userId, pkgName,
+ fullAggregateGroupKey.toString());
+ mAggregatedNotifications.remove(fullAggregateGroupKey);
+ } else {
+ if (DEBUG) {
+ Log.i(TAG, "Aggregate group not empty, updating: " + fullAggregateGroupKey);
+ }
+ updateAggregateAppGroup(fullAggregateGroupKey, sbn.getKey(), true, 0);
+ }
+
+ // Try to cleanup cached summaries if notification was canceled (not snoozed)
+ if (record.isCanceled) {
+ maybeClearCanceledSummariesCache(pkgName, userId,
+ record.getNotification().getGroup(), notificationList);
+ }
+ }
+ }
+ }
+
+ private record NotificationMoveOp(NotificationRecord record, FullyQualifiedGroupKey oldGroup,
+ FullyQualifiedGroupKey newGroup) { }
+
+ /**
+ * Called when a notification channel is updated, so that this helper can adjust
+ * the aggregate groups by moving children if their section has changed.
+ * see {@link #onNotificationPostedWithDelay(NotificationRecord, List, Map)}
+ * @param userId the userId of the channel
+ * @param pkgName the channel's package
+ * @param channel the channel that was updated
+ * @param notificationList the full notification list from NotificationManagerService
+ */
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void onChannelUpdated(final int userId, final String pkgName,
+ final NotificationChannel channel, final List<NotificationRecord> notificationList) {
+ synchronized (mAggregatedNotifications) {
+ ArrayMap<String, NotificationRecord> notificationsToCheck = new ArrayMap<>();
+ for (NotificationRecord r : notificationList) {
+ if (r.getChannel().getId().equals(channel.getId())
+ && r.getSbn().getPackageName().equals(pkgName)
+ && r.getUserId() == userId) {
+ notificationsToCheck.put(r.getKey(), r);
+ }
+ }
+
+ final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
+
+ final Set<FullyQualifiedGroupKey> oldGroups =
+ new HashSet<>(mAggregatedNotifications.keySet());
+ for (FullyQualifiedGroupKey oldFullAggKey : oldGroups) {
+ // Only check aggregate groups that match the same userId & packageName
+ if (pkgName.equals(oldFullAggKey.pkg) && userId == oldFullAggKey.userId) {
+ final ArrayMap<String, NotificationAttributes> notificationsInAggGroup =
+ mAggregatedNotifications.get(oldFullAggKey);
+ if (notificationsInAggGroup == null) {
+ continue;
+ }
+
+ FullyQualifiedGroupKey newFullAggregateGroupKey = null;
+ for (String key : notificationsInAggGroup.keySet()) {
+ if (notificationsToCheck.get(key) != null) {
+ // check if section changes
+ NotificationSectioner sectioner = getSection(
+ notificationsToCheck.get(key));
+ if (sectioner == null) {
+ continue;
+ }
+ newFullAggregateGroupKey = new FullyQualifiedGroupKey(userId, pkgName,
+ sectioner);
+ if (!oldFullAggKey.equals(newFullAggregateGroupKey)) {
+ if (DEBUG) {
+ Log.i(TAG, "Change section on channel update: " + key);
+ }
+ notificationsToMove.add(
+ new NotificationMoveOp(notificationsToCheck.get(key),
+ oldFullAggKey, newFullAggregateGroupKey));
+ }
+ }
+ }
+
+ 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);
+ }
+ notificationsToMove.add(
+ new NotificationMoveOp(r, null, newFullAggregateGroupKey));
+ }
+ }
+ //Cleanup mUngroupedAbuseNotifications
+ mUngroupedAbuseNotifications.remove(newFullAggregateGroupKey);
+ }
+ }
+ }
+ }
+
+ // Batch move to new section
+ if (!notificationsToMove.isEmpty()) {
+ moveNotificationsToNewSection(userId, pkgName, notificationsToMove);
+ }
+ }
+ }
+
+ @GuardedBy("mAggregatedNotifications")
+ private void moveNotificationsToNewSection(final int userId, final String pkgName,
+ final List<NotificationMoveOp> notificationsToMove) {
+ record GroupUpdateOp(FullyQualifiedGroupKey groupKey, NotificationRecord record,
+ boolean hasSummary) { }
+ ArrayMap<FullyQualifiedGroupKey, GroupUpdateOp> groupsToUpdate = new ArrayMap<>();
+
+ for (NotificationMoveOp moveOp: notificationsToMove) {
+ final NotificationRecord record = moveOp.record;
+ final FullyQualifiedGroupKey oldFullAggregateGroupKey = moveOp.oldGroup;
+ final FullyQualifiedGroupKey newFullAggregateGroupKey = moveOp.newGroup;
+
+ if (DEBUG) {
+ Log.i(TAG,
+ "moveNotificationToNewSection: " + record + " " + newFullAggregateGroupKey
+ + " from: " + oldFullAggregateGroupKey);
+ }
+
+ // Update/remove aggregate summary for old group
+ if (oldFullAggregateGroupKey != null) {
+ final ArrayMap<String, NotificationAttributes> oldAggregatedNotificationsAttrs =
+ mAggregatedNotifications.getOrDefault(oldFullAggregateGroupKey,
+ new ArrayMap<>());
+ oldAggregatedNotificationsAttrs.remove(record.getKey());
+ mAggregatedNotifications.put(oldFullAggregateGroupKey,
+ oldAggregatedNotificationsAttrs);
+
+ // Only add once, for triggering notification
+ if (!groupsToUpdate.containsKey(oldFullAggregateGroupKey)) {
+ groupsToUpdate.put(oldFullAggregateGroupKey,
+ new GroupUpdateOp(oldFullAggregateGroupKey, record, true));
+ }
+ }
+
+ // Add/update aggregate summary for new group
+ 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(),
+ record.getNotification().getSmallIcon(),
+ record.getNotification().color,
+ record.getNotification().visibility,
+ record.getNotification().getGroupAlertBehavior(),
+ record.getChannel().getId()));
+ mAggregatedNotifications.put(newFullAggregateGroupKey,
+ newAggregatedNotificationsAttrs);
+
+ // Only add once, for triggering notification
+ if (!groupsToUpdate.containsKey(newFullAggregateGroupKey)) {
+ groupsToUpdate.put(newFullAggregateGroupKey,
+ new GroupUpdateOp(newFullAggregateGroupKey, record, newGroupExists));
+ }
+
+ // Add notification to new group. do not request resort
+ record.setOverrideGroupKey(null);
+ mCallback.addAutoGroup(record.getKey(), newFullAggregateGroupKey.toString(), false);
+ }
+ }
+
+ // Update groups (sections)
+ 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 {
+ NotificationRecord triggeringNotification = groupsToUpdate.get(groupKey).record;
+ boolean hasSummary = groupsToUpdate.get(groupKey).hasSummary;
+ NotificationSectioner sectioner = getSection(triggeringNotification);
+ if (sectioner == null) {
+ continue;
+ }
+ updateAggregateAppGroup(groupKey, triggeringNotification.getKey(), hasSummary,
+ sectioner.mSummaryId);
+ }
+ }
+ }
+
+ static String getFullAggregateGroupKey(String pkgName,
+ String groupName, int userId) {
+ return new FullyQualifiedGroupKey(userId, pkgName, groupName).toString();
+ }
+
+ /**
+ * Returns the full aggregate group key, which contains the userId and package name
+ * in addition to the aggregate group key (name).
+ * Equivalent to {@link StatusBarNotification#groupKey()}
+ */
+ static String getFullAggregateGroupKey(NotificationRecord record) {
+ return new FullyQualifiedGroupKey(record.getUserId(), record.getSbn().getPackageName(),
+ getSection(record)).toString();
+ }
+
+ protected static boolean isAggregatedGroup(NotificationRecord record) {
+ return (record.mOriginalFlags & Notification.FLAG_AUTOGROUP_SUMMARY) != 0;
+ }
+
+ private static int getNumChildrenForGroup(@NonNull final String groupKey,
+ final List<NotificationRecord> notificationList) {
+ //TODO (b/349072751): track grouping state in GroupHelper -> do not use notificationList
+ int numChildren = 0;
+ // find children for this summary
+ for (NotificationRecord r : notificationList) {
+ if (!r.getNotification().isGroupSummary()
+ && groupKey.equals(r.getSbn().getGroup())) {
+ numChildren++;
+ }
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "getNumChildrenForGroup " + groupKey + " numChild: " + numChildren);
+ }
+ return numChildren;
+ }
+
+ private static boolean isGroupSummaryWithoutChildren(final NotificationRecord record,
+ final List<NotificationRecord> notificationList) {
+ final StatusBarNotification sbn = record.getSbn();
+ final String groupKey = record.getSbn().getGroup();
+
+ // ignore non app groups and non summaries
+ if (!sbn.isAppGroup() || !record.getNotification().isGroupSummary()) {
+ return false;
+ }
+
+ return getNumChildrenForGroup(groupKey, notificationList) == 0;
+ }
+
+ private static boolean isGroupChildWithoutSummary(final NotificationRecord record,
+ final Map<String, NotificationRecord> summaryByGroupKey) {
+ final StatusBarNotification sbn = record.getSbn();
+ final String groupKey = record.getSbn().getGroupKey();
+
+ if (!sbn.isAppGroup()) {
+ return false;
+ }
+
+ if (record.getNotification().isGroupSummary()) {
+ return false;
+ }
+
+ if (summaryByGroupKey.containsKey(groupKey)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @GuardedBy("mAggregatedNotifications")
+ private void aggregateUngroupedNotifications(FullyQualifiedGroupKey fullAggregateGroupKey,
+ String triggeringNotifKey, Map<String, NotificationAttributes> ungrouped,
+ final boolean hasSummary, int summaryId) {
+ final ArrayMap<String, NotificationAttributes> aggregatedNotificationsAttrs =
+ mAggregatedNotifications.getOrDefault(fullAggregateGroupKey, new ArrayMap<>());
+ aggregatedNotificationsAttrs.putAll(ungrouped);
+ mAggregatedNotifications.put(fullAggregateGroupKey, aggregatedNotificationsAttrs);
+
+ // add/update aggregate summary
+ updateAggregateAppGroup(fullAggregateGroupKey, triggeringNotifKey, hasSummary, summaryId);
+
+ // add notification to aggregate group
+ for (String key: ungrouped.keySet()) {
+ mCallback.addAutoGroup(key, fullAggregateGroupKey.toString(), true);
+ }
+
+ //cleanup mUngroupedAbuseNotifications
+ mUngroupedAbuseNotifications.remove(fullAggregateGroupKey);
+ }
+
+ @GuardedBy("mAggregatedNotifications")
+ private void updateAggregateAppGroup(FullyQualifiedGroupKey fullAggregateGroupKey,
+ String triggeringNotifKey, boolean hasSummary, int summaryId) {
+ final ArrayMap<String, NotificationAttributes> aggregatedNotificationsAttrs =
+ mAggregatedNotifications.getOrDefault(fullAggregateGroupKey, new ArrayMap<>());
+ NotificationAttributes attr = getSummaryAttributes(fullAggregateGroupKey.pkg,
+ aggregatedNotificationsAttrs);
+ String channelId = hasSummary ? attr.channelId
+ : aggregatedNotificationsAttrs.get(triggeringNotifKey).channelId;
+ NotificationAttributes summaryAttr = new NotificationAttributes(attr.flags, attr.icon,
+ attr.iconColor, attr.visibility, attr.groupAlertBehavior, channelId);
+
+ if (!hasSummary) {
+ if (DEBUG) {
+ Log.i(TAG, "Create aggregate summary: " + fullAggregateGroupKey);
+ }
+ mCallback.addAutoGroupSummary(fullAggregateGroupKey.userId, fullAggregateGroupKey.pkg,
+ triggeringNotifKey, fullAggregateGroupKey.toString(), summaryId, summaryAttr);
+ } else {
+ if (DEBUG) {
+ Log.i(TAG, "Update aggregate summary: " + fullAggregateGroupKey);
+ }
+ mCallback.updateAutogroupSummary(fullAggregateGroupKey.userId,
+ fullAggregateGroupKey.pkg, fullAggregateGroupKey.toString(), summaryAttr);
+ }
+ }
+
+ @GuardedBy("mAggregatedNotifications")
+ private void groupSparseGroups(final NotificationRecord record,
+ final List<NotificationRecord> notificationList,
+ final Map<String, NotificationRecord> summaryByGroupKey,
+ final NotificationSectioner sectioner,
+ final FullyQualifiedGroupKey fullAggregateGroupKey) {
+ final ArrayMap<String, NotificationRecord> sparseGroupSummaries = getSparseGroups(
+ fullAggregateGroupKey, notificationList, summaryByGroupKey, sectioner);
+ if (sparseGroupSummaries.size() >= mAutogroupSparseGroupsAtCount) {
+ if (DEBUG) {
+ Log.i(TAG,
+ "Aggregate sparse groups for: " + record.getSbn().getPackageName()
+ + " Section: " + sectioner.mName);
+ }
+
+ ArrayMap<String, NotificationAttributes> ungrouped =
+ mUngroupedAbuseNotifications.getOrDefault(
+ fullAggregateGroupKey, new ArrayMap<>());
+ final ArrayMap<String, NotificationAttributes> aggregatedNotificationsAttrs =
+ mAggregatedNotifications.getOrDefault(fullAggregateGroupKey, new ArrayMap<>());
+ final boolean hasSummary = !aggregatedNotificationsAttrs.isEmpty();
+ for (NotificationRecord r : notificationList) {
+ // Add notifications for detected sparse groups
+ if (sparseGroupSummaries.containsKey(r.getGroupKey())) {
+ // Move child notifications to aggregate group
+ if (!r.getNotification().isGroupSummary()) {
+ if (DEBUG) {
+ Log.i(TAG, "Aggregate notification (sparse group): " + r);
+ }
+ mCallback.addAutoGroup(r.getKey(), fullAggregateGroupKey.toString(), true);
+ aggregatedNotificationsAttrs.put(r.getKey(),
+ new NotificationAttributes(r.getFlags(),
+ r.getNotification().getSmallIcon(), r.getNotification().color,
+ r.getNotification().visibility,
+ r.getNotification().getGroupAlertBehavior(),
+ r.getChannel().getId()));
+
+ } else if (r.getNotification().isGroupSummary()) {
+ // Remove summary notifications
+ if (DEBUG) {
+ Log.i(TAG, "Remove app summary (sparse group): " + r);
+ }
+ mCallback.removeAppProvidedSummary(r.getKey());
+ cacheCanceledSummary(r);
+ }
+ } else {
+ // Add any notifications left ungrouped
+ if (ungrouped.containsKey(r.getKey())) {
+ if (DEBUG) {
+ Log.i(TAG, "Aggregate ungrouped (sparse group): " + r);
+ }
+ mCallback.addAutoGroup(r.getKey(), fullAggregateGroupKey.toString(), true);
+ aggregatedNotificationsAttrs.put(r.getKey(),ungrouped.get(r.getKey()));
+ }
+ }
+ }
+
+ mAggregatedNotifications.put(fullAggregateGroupKey, aggregatedNotificationsAttrs);
+ // add/update aggregate summary
+ updateAggregateAppGroup(fullAggregateGroupKey, record.getKey(), hasSummary,
+ sectioner.mSummaryId);
+
+ //cleanup mUngroupedAbuseNotifications
+ mUngroupedAbuseNotifications.remove(fullAggregateGroupKey);
+ }
+ }
+
+ private ArrayMap<String, NotificationRecord> getSparseGroups(
+ final FullyQualifiedGroupKey fullAggregateGroupKey,
+ final List<NotificationRecord> notificationList,
+ final Map<String, NotificationRecord> summaryByGroupKey,
+ final NotificationSectioner sectioner) {
+ ArrayMap<String, NotificationRecord> sparseGroups = new ArrayMap<>();
+ for (NotificationRecord summary : summaryByGroupKey.values()) {
+ if (summary != null && sectioner.isInSection(summary)) {
+ if (summary.getSbn().getPackageName().equalsIgnoreCase(fullAggregateGroupKey.pkg)
+ && summary.getUserId() == fullAggregateGroupKey.userId
+ && summary.getSbn().isAppGroup()
+ && !summary.getGroupKey().equals(fullAggregateGroupKey.toString())) {
+ int numChildren = getNumChildrenForGroup(summary.getSbn().getGroup(),
+ notificationList);
+ if (numChildren > 0 && numChildren < MIN_CHILD_COUNT_TO_AVOID_FORCE_GROUPING) {
+ sparseGroups.put(summary.getGroupKey(), summary);
+ }
+ }
+ }
+ }
+ return sparseGroups;
+ }
+
+ @GuardedBy("mAggregatedNotifications")
+ 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()));
+ }
+
+ @GuardedBy("mAggregatedNotifications")
+ private void maybeClearCanceledSummariesCache(String pkgName, int userId,
+ String groupName, List<NotificationRecord> notificationList) {
+ final FullyQualifiedGroupKey findKey = new FullyQualifiedGroupKey(userId, pkgName,
+ groupName);
+ CachedSummary summary = mCanceledSummaries.get(findKey);
+ // Check if any notifications from original group remain
+ if (summary != null) {
+ if (DEBUG) {
+ Log.i(TAG, "Try removing cached summary: " + summary);
+ }
+ boolean stillHasChildren = false;
+ //TODO (b/349072751): track grouping state in GroupHelper -> do not use notificationList
+ for (NotificationRecord r : notificationList) {
+ if (summary.originalGroupKey.equals(r.getNotification().getGroup())
+ && r.getUser().getIdentifier() == userId
+ && r.getSbn().getPackageName().equals(pkgName)) {
+ stillHasChildren = true;
+ break;
+ }
+ }
+ if (!stillHasChildren) {
+ removeCachedSummary(pkgName, userId, summary);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mAggregatedNotifications")
+ protected CachedSummary findCanceledSummary(String pkgName, String tag, int id, int userId) {
+ for (FullyQualifiedGroupKey key: mCanceledSummaries.keySet()) {
+ if (pkgName.equals(key.pkg) && userId == key.userId) {
+ CachedSummary summary = mCanceledSummaries.get(key);
+ if (summary != null && summary.id == id && TextUtils.equals(tag, summary.tag)) {
+ return summary;
+ }
+ }
+ }
+ return null;
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mAggregatedNotifications")
+ protected CachedSummary findCanceledSummary(String pkgName, String tag, int id, int userId,
+ String groupName) {
+ final FullyQualifiedGroupKey findKey = new FullyQualifiedGroupKey(userId, pkgName,
+ groupName);
+ CachedSummary summary = mCanceledSummaries.get(findKey);
+ if (summary != null && summary.id == id && TextUtils.equals(tag, summary.tag)) {
+ return summary;
+ } else {
+ return null;
+ }
+ }
+
+ @GuardedBy("mAggregatedNotifications")
+ private void removeCachedSummary(String pkgName, int userId, CachedSummary summary) {
+ final FullyQualifiedGroupKey key = new FullyQualifiedGroupKey(userId, pkgName,
+ summary.originalGroupKey);
+ mCanceledSummaries.remove(key);
+ }
+
+ protected boolean isUpdateForCanceledSummary(final NotificationRecord record) {
+ synchronized (mAggregatedNotifications) {
+ if (record.getSbn().isAppGroup() && record.getNotification().isGroupSummary()) {
+ CachedSummary cachedSummary = findCanceledSummary(record.getSbn().getPackageName(),
+ record.getSbn().getTag(), record.getSbn().getId(), record.getUserId(),
+ record.getNotification().getGroup());
+ return cachedSummary != null;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Cancels the original group's children when an app cancels a summary that was 'maybe'
+ * previously removed due to forced grouping of a "sparse group".
+ *
+ * @param pkgName packageName
+ * @param tag original summary notification tag
+ * @param id original summary notification id
+ * @param userId original summary userId
+ */
+ @FlaggedApi(Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS)
+ public void maybeCancelGroupChildrenForCanceledSummary(String pkgName, String tag, int id,
+ int userId, int cancelReason) {
+ synchronized (mAggregatedNotifications) {
+ final CachedSummary summary = findCanceledSummary(pkgName, tag, id, userId);
+ if (summary != null) {
+ if (DEBUG) {
+ Log.i(TAG, "Found cached summary: " + summary.key);
+ }
+ mCallback.removeNotificationFromCanceledGroup(userId, pkgName,
+ summary.originalGroupKey, cancelReason);
+ removeCachedSummary(pkgName, userId, summary);
+ }
+ }
+ }
+
+ static NotificationSectioner getSection(final NotificationRecord record) {
+ for (NotificationSectioner sectioner: NOTIFICATION_SHADE_SECTIONS) {
+ if (sectioner.isInSection(record)) {
+ return sectioner;
+ }
+ }
+ return null;
+ }
+
+ record FullyQualifiedGroupKey(int userId, String pkg, String groupName) {
+ FullyQualifiedGroupKey(int userId, String pkg, @Nullable NotificationSectioner sectioner) {
+ this(userId, pkg, AGGREGATE_GROUP_KEY + (sectioner != null ? sectioner.mName : ""));
+ }
+
+ @Override
+ public String toString() {
+ return userId + "|" + pkg + "|" + "g:" + groupName;
+ }
+ }
+
+ protected static class NotificationSectioner {
+ final String mName;
+ final int mSummaryId;
+ private final Predicate<NotificationRecord> mSectionChecker;
+
+ public NotificationSectioner(String name, int summaryId,
+ Predicate<NotificationRecord> sectionChecker) {
+ mName = name;
+ mSummaryId = summaryId;
+ mSectionChecker = sectionChecker;
+ }
+
+ boolean isInSection(final NotificationRecord record) {
+ return isNotificationGroupable(record) && mSectionChecker.test(record);
+ }
+
+ private boolean isNotificationGroupable(final NotificationRecord record) {
+ if (record.isConversation()) {
+ return false;
+ }
+
+ Notification notification = record.getSbn().getNotification();
+ boolean isColorizedFGS = notification.isForegroundService()
+ && notification.isColorized()
+ && record.getImportance() > NotificationManager.IMPORTANCE_MIN;
+ boolean isCall = record.getImportance() > NotificationManager.IMPORTANCE_MIN
+ && notification.isStyle(Notification.CallStyle.class);
+ if (isColorizedFGS || isCall) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ record CachedSummary(int id, String tag, String originalGroupKey, String key) {}
+
protected static class NotificationAttributes {
public final int flags;
public final int iconColor;
public final Icon icon;
public final int visibility;
+ public final int groupAlertBehavior;
+ public final String channelId;
- public NotificationAttributes(int flags, Icon icon, int iconColor, int visibility) {
+ public NotificationAttributes(int flags, Icon icon, int iconColor, int visibility,
+ int groupAlertBehavior, String channelId) {
this.flags = flags;
this.icon = icon;
this.iconColor = iconColor;
this.visibility = visibility;
+ this.groupAlertBehavior = groupAlertBehavior;
+ this.channelId = channelId;
}
public NotificationAttributes(@NonNull NotificationAttributes attr) {
@@ -406,6 +1348,8 @@
this.icon = attr.icon;
this.iconColor = attr.iconColor;
this.visibility = attr.visibility;
+ this.groupAlertBehavior = attr.groupAlertBehavior;
+ this.channelId = attr.channelId;
}
@Override
@@ -417,22 +1361,39 @@
return false;
}
return flags == that.flags && iconColor == that.iconColor && icon.sameAs(that.icon)
- && visibility == that.visibility;
+ && visibility == that.visibility
+ && groupAlertBehavior == that.groupAlertBehavior
+ && channelId.equals(that.channelId);
}
@Override
public int hashCode() {
- return Objects.hash(flags, iconColor, icon, visibility);
+ return Objects.hash(flags, iconColor, icon, visibility, groupAlertBehavior, channelId);
+ }
+
+ @Override
+ public String toString() {
+ return "NotificationAttributes: flags: " + flags + " icon: " + icon + " color: "
+ + iconColor + " vis: " + visibility + " groupAlertBehavior: "
+ + groupAlertBehavior + " channelId: " + channelId;
}
}
protected interface Callback {
- void addAutoGroup(String key, boolean requestSort);
+ void addAutoGroup(String key, String groupName, boolean requestSort);
void removeAutoGroup(String key);
- void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
+ void addAutoGroupSummary(int userId, String pkg, String triggeringKey, String groupName,
+ int summaryId, NotificationAttributes summaryAttr);
+ void removeAutoGroupSummary(int user, String pkg, String groupKey);
+
+ void updateAutogroupSummary(int userId, String pkg, String groupKey,
NotificationAttributes summaryAttr);
- void removeAutoGroupSummary(int user, String pkg);
- void updateAutogroupSummary(int userId, String pkg, NotificationAttributes summaryAttr);
+
+ // New callbacks for API abuse grouping
+ void removeAppProvidedSummary(String key);
+
+ void removeNotificationFromCanceledGroup(int userId, String pkg, String groupKey,
+ int cancelReason);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a4f534e..9e49827 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -39,6 +39,7 @@
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
+import static android.app.Notification.FLAG_GROUP_SUMMARY;
import static android.app.Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_NO_DISMISS;
@@ -108,6 +109,7 @@
import static android.service.notification.Adjustment.TYPE_PROMOTION;
import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
import static android.service.notification.Flags.callstyleCallbackApi;
+import static android.service.notification.Flags.notificationForceGrouping;
import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners;
import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
@@ -371,6 +373,7 @@
import com.android.server.wm.BackgroundActivityStartCallback;
import com.android.server.wm.WindowManagerInternal;
+import java.util.function.BiPredicate;
import libcore.io.IoUtils;
import org.json.JSONException;
@@ -512,6 +515,8 @@
private static final long DELAY_FOR_ASSISTANT_TIME = 200;
+ private static final long DELAY_FORCE_REGROUP_TIME = 3000;
+
private static final String ACTION_NOTIFICATION_TIMEOUT =
NotificationManagerService.class.getSimpleName() + ".TIMEOUT";
private static final int REQUEST_CODE_TIMEOUT = 1;
@@ -608,6 +613,9 @@
static final long NOTIFICATION_MAX_AGE_AT_POST = Duration.ofDays(14).toMillis();
+ // Minium number of sparse groups for a package before autogrouping them
+ private static final int AUTOGROUP_SPARSE_GROUPS_AT_COUNT = 3;
+
private IActivityManager mAm;
private ActivityTaskManagerInternal mAtm;
private ActivityManager mActivityManager;
@@ -1001,17 +1009,25 @@
* icons are different.
* @param userId user id of the autogroup summary
* @param pkg package of the autogroup summary
+ * @param groupKey group key of the autogroup summary
* @param summaryAttr the new flags and/or icon & color for this summary
* @param isAppForeground true if the app is currently in the foreground.
*/
@GuardedBy("mNotificationLock")
- protected void updateAutobundledSummaryLocked(int userId, String pkg,
- NotificationAttributes summaryAttr, boolean isAppForeground) {
+ protected void updateAutobundledSummaryLocked(int userId, String pkg, String groupKey,
+ NotificationAttributes summaryAttr, boolean isAppForeground) {
ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
if (summaries == null) {
return;
}
- String summaryKey = summaries.get(pkg);
+ final String autbundledGroupKey;
+ if (notificationForceGrouping()) {
+ autbundledGroupKey = groupKey;
+ } else {
+ autbundledGroupKey = pkg;
+ }
+
+ String summaryKey = summaries.get(autbundledGroupKey);
if (summaryKey == null) {
return;
}
@@ -1019,12 +1035,26 @@
if (summary == null) {
return;
}
+
int oldFlags = summary.getSbn().getNotification().flags;
boolean attributesUpdated =
!summaryAttr.icon.sameAs(summary.getSbn().getNotification().getSmallIcon())
|| summaryAttr.iconColor != summary.getSbn().getNotification().color
- || summaryAttr.visibility != summary.getSbn().getNotification().visibility;
+ || summaryAttr.visibility != summary.getSbn().getNotification().visibility
+ || summaryAttr.groupAlertBehavior !=
+ summary.getSbn().getNotification().getGroupAlertBehavior();
+
+ if (notificationForceGrouping()) {
+ if (!summary.getChannel().getId().equals(summaryAttr.channelId)) {
+ NotificationChannel newChannel = mPreferencesHelper.getNotificationChannel(pkg,
+ summary.getUid(), summaryAttr.channelId, false);
+ if (newChannel != null) {
+ summary.updateNotificationChannel(newChannel);
+ attributesUpdated = true;
+ }
+ }
+ }
if (oldFlags != summaryAttr.flags || attributesUpdated) {
summary.getSbn().getNotification().flags =
@@ -1032,6 +1062,8 @@
summary.getSbn().getNotification().setSmallIcon(summaryAttr.icon);
summary.getSbn().getNotification().color = summaryAttr.iconColor;
summary.getSbn().getNotification().visibility = summaryAttr.visibility;
+ summary.getSbn().getNotification()
+ .setGroupAlertBehavior(summaryAttr.groupAlertBehavior);
mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
/* isAppProvided= */ false, mPostNotificationTrackerFactory.newTracker(null)));
}
@@ -2836,12 +2868,17 @@
mAutoGroupAtCount =
getContext().getResources().getInteger(R.integer.config_autoGroupAtCount);
return new GroupHelper(getContext(), getContext().getPackageManager(),
- mAutoGroupAtCount, new GroupHelper.Callback() {
+ mAutoGroupAtCount, AUTOGROUP_SPARSE_GROUPS_AT_COUNT, new GroupHelper.Callback() {
@Override
- public void addAutoGroup(String key, boolean requestSort) {
- synchronized (mNotificationLock) {
- addAutogroupKeyLocked(key, requestSort);
- }
+ public void addAutoGroup(String key, String groupName, boolean requestSort) {
+ synchronized (mNotificationLock) {
+ if (notificationForceGrouping()) {
+ convertSummaryToNotificationLocked(key);
+ addAutogroupKeyLocked(key, groupName, requestSort);
+ } else {
+ addAutogroupKeyLocked(key, groupName, requestSort);
+ }
+ }
}
@Override
@@ -2853,10 +2890,9 @@
@Override
public void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
- NotificationAttributes summaryAttr) {
+ String groupName, int summaryId, NotificationAttributes summaryAttr) {
NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey,
- summaryAttr.flags, summaryAttr.icon, summaryAttr.iconColor,
- summaryAttr.visibility);
+ groupName, summaryId, summaryAttr);
if (r != null) {
final boolean isAppForeground =
mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
@@ -2867,19 +2903,56 @@
}
@Override
- public void removeAutoGroupSummary(int userId, String pkg) {
+ public void removeAutoGroupSummary(int userId, String pkg, String groupKey) {
synchronized (mNotificationLock) {
- clearAutogroupSummaryLocked(userId, pkg);
+ clearAutogroupSummaryLocked(userId, pkg, groupKey);
}
}
@Override
- public void updateAutogroupSummary(int userId, String pkg,
+ public void updateAutogroupSummary(int userId, String pkg, String groupKey,
NotificationAttributes summaryAttr) {
boolean isAppForeground = pkg != null
&& mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
synchronized (mNotificationLock) {
- updateAutobundledSummaryLocked(userId, pkg, summaryAttr, isAppForeground);
+ updateAutobundledSummaryLocked(userId, pkg, groupKey, summaryAttr,
+ isAppForeground);
+ }
+ }
+
+ @Override
+ public void removeAppProvidedSummary(String key) {
+ synchronized (mNotificationLock) {
+ removeAppSummaryLocked(key);
+ }
+ }
+
+ @Override
+ public void removeNotificationFromCanceledGroup(int userId, String pkg,
+ String groupKey, int cancelReason) {
+ synchronized (mNotificationLock) {
+ final int mustNotHaveFlags;
+ if (lifetimeExtensionRefactor()) {
+ // Also don't allow client apps to cancel lifetime extended notifs.
+ mustNotHaveFlags = (FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB
+ | FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+ } else {
+ mustNotHaveFlags = (FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB);
+ }
+ FlagChecker childrenFlagChecker = (flags) -> {
+ if (cancelReason == REASON_CANCEL
+ || cancelReason == REASON_CLICK
+ || cancelReason == REASON_CANCEL_ALL) {
+ if ((flags & FLAG_BUBBLE) != 0) {
+ return false;
+ }
+ }
+ return (flags & mustNotHaveFlags) == 0;
+ };
+ cancelGroupChildrenLocked(userId, pkg, Binder.getCallingUid(),
+ Binder.getCallingPid(), null,
+ false, childrenFlagChecker, groupKey,
+ REASON_APP_CANCEL, SystemClock.elapsedRealtime());
}
}
});
@@ -3107,6 +3180,18 @@
modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
}
+ if (notificationForceGrouping()) {
+ final NotificationChannel updatedChannel = mPreferencesHelper.getNotificationChannel(
+ pkg, uid, channel.getId(), false);
+ mHandler.postDelayed(() -> {
+ synchronized (mNotificationLock) {
+ mGroupHelper.onChannelUpdated(
+ UserHandle.getUserHandleForUid(uid).getIdentifier(), pkg,
+ updatedChannel, mNotificationList);
+ }
+ }, DELAY_FORCE_REGROUP_TIME);
+ }
+
handleSavePolicyFile();
}
@@ -6652,18 +6737,33 @@
}
}
+ @SuppressWarnings("GuardedBy")
@GuardedBy("mNotificationLock")
- void addAutogroupKeyLocked(String key, boolean requestSort) {
+ void addAutogroupKeyLocked(String key, String groupName, boolean requestSort) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r == null) {
return;
}
if (r.getSbn().getOverrideGroupKey() == null) {
- addAutoGroupAdjustment(r, GroupHelper.AUTOGROUP_KEY);
+ if (notificationForceGrouping()) {
+ if (r.getSbn().isAppGroup()) {
+ // Override group key early for forced grouped notifications
+ r.setOverrideGroupKey(groupName);
+ }
+ }
+
+ addAutoGroupAdjustment(r, groupName);
EventLogTags.writeNotificationAutogrouped(key);
+
if (!android.app.Flags.checkAutogroupBeforePost() || requestSort) {
mRankingHandler.requestSort();
}
+
+ if (notificationForceGrouping()) {
+ if (r.getSbn().isAppGroup()) {
+ mListeners.notifyPostedLocked(r, r);
+ }
+ }
}
}
@@ -6692,27 +6792,57 @@
// Clears the 'fake' auto-group summary.
@VisibleForTesting
@GuardedBy("mNotificationLock")
- void clearAutogroupSummaryLocked(int userId, String pkg) {
+ void clearAutogroupSummaryLocked(int userId, String pkg, String groupKey) {
+ final String autbundledGroupKey;
+ if (notificationForceGrouping()) {
+ autbundledGroupKey = groupKey;
+ } else {
+ autbundledGroupKey = pkg;
+ }
ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
- if (summaries != null && summaries.containsKey(pkg)) {
- final NotificationRecord removed = findNotificationByKeyLocked(summaries.remove(pkg));
+ if (summaries != null && summaries.containsKey(autbundledGroupKey)) {
+ final NotificationRecord removed = findNotificationByKeyLocked(
+ summaries.remove(autbundledGroupKey));
if (removed != null) {
final StatusBarNotification sbn = removed.getSbn();
cancelNotification(MY_UID, MY_PID, pkg, sbn.getTag(), sbn.getId(), 0, 0, false,
- userId, REASON_UNAUTOBUNDLED, null);
+ userId, REASON_UNAUTOBUNDLED, null);
}
}
}
@GuardedBy("mNotificationLock")
- private boolean hasAutoGroupSummaryLocked(StatusBarNotification sbn) {
- ArrayMap<String, String> summaries = mAutobundledSummaries.get(sbn.getUserId());
- return summaries != null && summaries.containsKey(sbn.getPackageName());
+ void removeAppSummaryLocked(String key) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ if (r == null) {
+ return;
+ }
+ if (convertSummaryToNotificationLocked(key)) {
+ r.isCanceled = true;
+ cancelNotification(Binder.getCallingUid(),
+ Binder.getCallingPid(), r.getSbn().getPackageName(),
+ r.getSbn().getTag(), r.getSbn().getId(), 0, 0,
+ false, r.getUserId(),
+ NotificationListenerService.REASON_GROUP_OPTIMIZATION, null);
+ }
+ }
+
+ @GuardedBy("mNotificationLock")
+ private boolean hasAutoGroupSummaryLocked(NotificationRecord record) {
+ final String autbundledGroupKey;
+ if (notificationForceGrouping()) {
+ autbundledGroupKey = GroupHelper.getFullAggregateGroupKey(record);
+ } else {
+ autbundledGroupKey = record.getSbn().getPackageName();
+ }
+
+ ArrayMap<String, String> summaries = mAutobundledSummaries.get(record.getUserId());
+ return summaries != null && summaries.containsKey(autbundledGroupKey);
}
// Creates a 'fake' summary for a package that has exceeded the solo-notification limit.
NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey,
- int flagsToSet, Icon summaryIcon, int summaryIconColor, int summaryVisibilty) {
+ String groupKey, int summaryId, NotificationAttributes summaryAttr) {
NotificationRecord summaryRecord = null;
boolean isPermissionFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
synchronized (mNotificationLock) {
@@ -6730,24 +6860,35 @@
summaries = new ArrayMap<>();
}
mAutobundledSummaries.put(userId, summaries);
- if (!summaries.containsKey(pkg)) {
+
+ boolean hasSummary;
+ String channelId;
+ if (notificationForceGrouping()) {
+ hasSummary = summaries.containsKey(groupKey);
+ channelId = summaryAttr.channelId;
+ } else {
+ hasSummary = summaries.containsKey(pkg);
+ channelId = notificationRecord.getChannel().getId();
+ }
+
+ if (!hasSummary) {
// Add summary
final ApplicationInfo appInfo =
adjustedSbn.getNotification().extras.getParcelable(
EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
final Bundle extras = new Bundle();
extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, appInfo);
- final String channelId = notificationRecord.getChannel().getId();
+
final Notification summaryNotification =
new Notification.Builder(getContext(), channelId)
- .setSmallIcon(summaryIcon)
+ .setSmallIcon(summaryAttr.icon)
.setGroupSummary(true)
- .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
- .setGroup(GroupHelper.AUTOGROUP_KEY)
- .setFlag(flagsToSet, true)
- .setColor(summaryIconColor)
- .setVisibility(summaryVisibilty)
+ .setGroupAlertBehavior(summaryAttr.groupAlertBehavior)
+ .setGroup(groupKey)
+ .setFlag(summaryAttr.flags, true)
+ .setColor(summaryAttr.iconColor)
+ .setVisibility(summaryAttr.visibility)
.build();
summaryNotification.extras.putAll(extras);
Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg);
@@ -6759,17 +6900,22 @@
final StatusBarNotification summarySbn =
new StatusBarNotification(adjustedSbn.getPackageName(),
adjustedSbn.getOpPkg(),
- Integer.MAX_VALUE,
- GroupHelper.AUTOGROUP_KEY, adjustedSbn.getUid(),
+ summaryId,
+ groupKey, adjustedSbn.getUid(),
adjustedSbn.getInitialPid(), summaryNotification,
- adjustedSbn.getUser(), GroupHelper.AUTOGROUP_KEY,
+ adjustedSbn.getUser(), groupKey,
System.currentTimeMillis());
summaryRecord = new NotificationRecord(getContext(), summarySbn,
notificationRecord.getChannel());
summaryRecord.setImportanceFixed(isPermissionFixed);
summaryRecord.setIsAppImportanceLocked(
notificationRecord.getIsAppImportanceLocked());
- summaries.put(pkg, summarySbn.getKey());
+
+ if (notificationForceGrouping()) {
+ summaries.put(summarySbn.getGroupKey(), summarySbn.getKey());
+ } else {
+ summaries.put(pkg, summarySbn.getKey());
+ }
}
if (summaryRecord != null && checkDisqualifyingFeatures(userId, uid,
summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord,
@@ -6780,6 +6926,27 @@
return null;
}
+ @GuardedBy("mNotificationLock")
+ boolean convertSummaryToNotificationLocked(final String key) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ if (r == null) {
+ return false;
+ }
+ // Convert summary to regular notification
+ if (r.getSbn().isAppGroup() && r.getNotification().isGroupSummary()) {
+ String oldGroupKey = r.getGroupKey();
+ NotificationRecord groupSummary = mSummaryByGroupKey.get(oldGroupKey);
+ if (groupSummary != null && groupSummary.getKey().equals(r.getKey())) {
+ mSummaryByGroupKey.remove(oldGroupKey);
+ }
+ // Clear summary flag
+ StatusBarNotification sbn = r.getSbn();
+ sbn.getNotification().flags = (r.mOriginalFlags & ~FLAG_GROUP_SUMMARY);
+ return true;
+ }
+ return false;
+ }
+
// Gets packages that have requested notification permission, and whether that has been
// allowed/denied, for all users on the device.
// Returns a single map containing that info keyed by (uid, package name) for all users.
@@ -8347,8 +8514,15 @@
* They will be recreated as needed when the group children are unsnoozed
*/
private boolean isSnoozable(NotificationRecord record) {
- return !(record.getNotification().isGroupSummary() && GroupHelper.AUTOGROUP_KEY.equals(
- record.getNotification().getGroup()));
+ if (notificationForceGrouping()) {
+ boolean isExemptedSummary =
+ ((record.getFlags() & FLAG_AUTOGROUP_SUMMARY) != 0
+ || GroupHelper.isAggregatedGroup(record));
+ return !(record.getNotification().isGroupSummary() && isExemptedSummary);
+ } else {
+ return !(record.getNotification().isGroupSummary()
+ && GroupHelper.AUTOGROUP_KEY.equals(record.getNotification().getGroup()));
+ }
}
}
@@ -8471,9 +8645,12 @@
cancelNotificationLocked(
r, mSendDelete, mReason, mRank, mCount, wasPosted, listenerName,
mCancellationElapsedTimeMs);
- cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
- mSendDelete, childrenFlagChecker, mReason,
- mCancellationElapsedTimeMs);
+ if (r.getNotification().isGroupSummary()) {
+ cancelGroupChildrenLocked(mUserId, mPkg, mCallingUid, mCallingPid,
+ listenerName, mSendDelete, childrenFlagChecker,
+ r.getNotification().getGroup(), mReason,
+ mCancellationElapsedTimeMs);
+ }
mAttentionHelper.updateLightsLocked();
if (mShortcutHelper != null) {
mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
@@ -8481,6 +8658,14 @@
mHandler);
}
} else {
+ if (notificationForceGrouping()) {
+ // No notification was found => maybe it was canceled by forced grouping
+ if (Flags.notificationForceGroupSingletons()) {
+ mGroupHelper.maybeCancelGroupChildrenForCanceledSummary(mPkg, mTag,
+ mId, mUserId, mReason);
+ }
+ }
+
// No notification was found, assume that it is snoozed and cancel it.
if (mReason != REASON_SNOOZED) {
final boolean wasSnoozed = mSnoozeHelper.cancel(mUserId, mPkg, mTag, mId);
@@ -8708,7 +8893,7 @@
boolean appBanned = !areNotificationsEnabledForPackageInt(pkg, uid);
boolean isCallNotification = isCallNotification(pkg, uid);
boolean posted = false;
- synchronized (mNotificationLock) {
+ synchronized (NotificationManagerService.this.mNotificationLock) {
try {
NotificationRecord r = findNotificationByListLocked(mEnqueuedNotifications,
key);
@@ -8731,6 +8916,29 @@
return false;
}
+ if (notificationForceGrouping()) {
+ if (Flags.notificationForceGroupSingletons()) {
+ // Check if this is an updated for a summary for an aggregated sparse
+ // group and remove it because that summary has been canceled
+ if (mGroupHelper.isUpdateForCanceledSummary(r)) {
+ if (DBG) {
+ Log.w(TAG,
+ "Suppressing notification because summary was canceled: "
+ + r);
+ }
+
+ String groupKey = r.getGroupKey();
+ NotificationRecord groupSummary = mSummaryByGroupKey.get(groupKey);
+ if (groupSummary != null && groupSummary.getKey()
+ .equals(r.getKey())) {
+ mSummaryByGroupKey.remove(groupKey);
+ }
+ return false;
+ }
+ }
+ }
+
+
final boolean isPackageSuspended =
isPackagePausedOrSuspended(r.getSbn().getPackageName(), r.getUid());
r.setHidden(isPackageSuspended);
@@ -8788,18 +8996,38 @@
if (notification.getSmallIcon() != null && !isCritical(r)) {
StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
if (oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())
+ || !Objects.equals(oldSbn.getNotification().getGroup(),
+ n.getNotification().getGroup())
|| oldSbn.getNotification().flags
!= n.getNotification().flags) {
synchronized (mNotificationLock) {
- boolean willBeAutogrouped = mGroupHelper.onNotificationPosted(n,
- hasAutoGroupSummaryLocked(n));
+ final String autogroupName =
+ notificationForceGrouping() ?
+ GroupHelper.getFullAggregateGroupKey(r)
+ : GroupHelper.AUTOGROUP_KEY;
+ boolean willBeAutogrouped =
+ mGroupHelper.onNotificationPosted(r,
+ hasAutoGroupSummaryLocked(r));
if (willBeAutogrouped) {
// The newly posted notification will be autogrouped, but
// was not autogrouped onPost, to avoid an unnecessary sort.
// We add the autogroup key to the notification without a
// sort here, and it'll be sorted below with extractSignals.
- addAutogroupKeyLocked(key, /* requestSort= */false);
+ addAutogroupKeyLocked(key,
+ autogroupName, /*requestSort=*/false);
+ } else {
+ if (notificationForceGrouping()) {
+ // Wait 3 seconds so that the app has a chance to post
+ // a group summary or children (complete a group)
+ mHandler.postDelayed(() -> {
+ synchronized (mNotificationLock) {
+ mGroupHelper.onNotificationPostedWithDelay(
+ r, mNotificationList, mSummaryByGroupKey);
+ }
+ }, r.getKey(), DELAY_FORCE_REGROUP_TIME);
+ }
}
+
}
}
}
@@ -8835,9 +9063,18 @@
mHandler.post(() -> {
synchronized (mNotificationLock) {
mGroupHelper.onNotificationPosted(
- n, hasAutoGroupSummaryLocked(n));
+ r, hasAutoGroupSummaryLocked(r));
}
});
+
+ if (notificationForceGrouping()) {
+ mHandler.postDelayed(() -> {
+ synchronized (mNotificationLock) {
+ mGroupHelper.onNotificationPostedWithDelay(r,
+ mNotificationList, mSummaryByGroupKey);
+ }
+ }, r.getKey(), DELAY_FORCE_REGROUP_TIME);
+ }
}
}
}
@@ -8846,12 +9083,20 @@
if (old != null && !old.isCanceled) {
mListeners.notifyRemovedLocked(r,
REASON_ERROR, r.getStats());
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mGroupHelper.onNotificationRemoved(n);
- }
- });
+ if (notificationForceGrouping()) {
+ mHandler.post(() -> {
+ synchronized (mNotificationLock) {
+ mGroupHelper.onNotificationRemoved(r, mNotificationList);
+ }
+ });
+ } else {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mGroupHelper.onNotificationRemoved(r);
+ }
+ });
+ }
}
if (callstyleCallbackApi()) {
@@ -9082,6 +9327,18 @@
n.flags &= ~Notification.FLAG_GROUP_SUMMARY;
}
+ if (notificationForceGrouping()) {
+ if (old != null) {
+ // If this is an update to a summary that was forced grouped => remove summary flag
+ boolean wasSummary = (old.mOriginalFlags & FLAG_GROUP_SUMMARY) != 0;
+ boolean wasForcedGrouped = (old.getFlags() & FLAG_GROUP_SUMMARY) == 0
+ && old.getSbn().getOverrideGroupKey() != null;
+ if (n.isGroupSummary() && wasSummary && wasForcedGrouped) {
+ n.flags &= ~FLAG_GROUP_SUMMARY;
+ }
+ }
+ }
+
String group = sbn.getGroupKey();
boolean isSummary = n.isGroupSummary();
@@ -9114,8 +9371,10 @@
// notification was a summary and the new one isn't, or when the old
// notification was a summary and its group key changed.
if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) {
- cancelGroupChildrenLocked(old, callingUid, callingPid, null, false /* sendDelete */,
- childrenFlagChecker, REASON_APP_CANCEL, SystemClock.elapsedRealtime());
+ cancelGroupChildrenLocked(old.getUserId(), old.getSbn().getPackageName(), callingUid,
+ callingPid, null, false /* sendDelete */, childrenFlagChecker,
+ old.getNotification().getGroup(), REASON_APP_CANCEL,
+ SystemClock.elapsedRealtime());
}
}
@@ -9777,12 +10036,21 @@
r.isCanceled = true;
}
mListeners.notifyRemovedLocked(r, reason, r.getStats());
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mGroupHelper.onNotificationRemoved(r.getSbn());
- }
- });
+ if (notificationForceGrouping()) {
+ mHandler.removeCallbacksAndMessages(r.getKey());
+ mHandler.post(() -> {
+ synchronized (NotificationManagerService.this.mNotificationLock) {
+ mGroupHelper.onNotificationRemoved(r, mNotificationList);
+ }
+ });
+ } else {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mGroupHelper.onNotificationRemoved(r);
+ }
+ });
+ }
if (callstyleCallbackApi()) {
notifyCallNotificationEventListenerOnRemoved(r);
}
@@ -9815,9 +10083,15 @@
}
final ArrayMap<String, String> summaries =
mAutobundledSummaries.get(r.getSbn().getUserId());
+ final String autbundledGroupKey;
+ if (notificationForceGrouping()) {
+ autbundledGroupKey = groupKey;
+ } else {
+ autbundledGroupKey = r.getSbn().getPackageName();
+ }
if (summaries != null && r.getSbn().getKey().equals(
- summaries.get(r.getSbn().getPackageName()))) {
- summaries.remove(r.getSbn().getPackageName());
+ summaries.get(autbundledGroupKey))) {
+ summaries.remove(autbundledGroupKey);
}
// Save it for users of getHistoricalNotifications(), unless the whole channel was deleted
@@ -10081,6 +10355,15 @@
public boolean apply(int flags);
}
+ private static boolean isChildOfGroup(final 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()));
+ }
+
@GuardedBy("mNotificationLock")
private void cancelAllNotificationsByListLocked(ArrayList<NotificationRecord> notificationList,
@Nullable String pkg, boolean nullPkgIndicatesUserSwitch, @Nullable String channelId,
@@ -10238,43 +10521,34 @@
// Warning: The caller is responsible for invoking updateLightsLocked().
@GuardedBy("mNotificationLock")
- private void cancelGroupChildrenLocked(NotificationRecord r, int callingUid, int callingPid,
- String listenerName, boolean sendDelete, FlagChecker flagChecker, int reason,
- @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
- Notification n = r.getNotification();
- if (!n.isGroupSummary()) {
- return;
- }
-
- String pkg = r.getSbn().getPackageName();
-
+ private void cancelGroupChildrenLocked(int userId, String pkg, int callingUid, int callingPid,
+ String listenerName, boolean sendDelete, FlagChecker flagChecker, String groupKey,
+ int reason, @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
if (pkg == null) {
- if (DBG) Slog.e(TAG, "No package for group summary: " + r.getKey());
+ if (DBG) Slog.e(TAG, "No package for group summary");
return;
}
- cancelGroupChildrenByListLocked(mNotificationList, r, callingUid, callingPid, listenerName,
- sendDelete, true, flagChecker, reason, cancellationElapsedTimeMs);
- cancelGroupChildrenByListLocked(mEnqueuedNotifications, r, callingUid, callingPid,
- listenerName, sendDelete, false, flagChecker, reason, cancellationElapsedTimeMs);
+ cancelGroupChildrenByListLocked(mNotificationList, userId, pkg, callingUid, callingPid,
+ listenerName, sendDelete, true, flagChecker, groupKey,
+ reason, cancellationElapsedTimeMs);
+ cancelGroupChildrenByListLocked(mEnqueuedNotifications, userId, pkg, callingUid, callingPid,
+ listenerName, sendDelete, false, flagChecker, groupKey,
+ reason, cancellationElapsedTimeMs);
}
@GuardedBy("mNotificationLock")
private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList,
- NotificationRecord parentNotification, int callingUid, int callingPid,
+ int userId, String pkg, int callingUid, int callingPid,
String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker,
- int reason, @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
- final String pkg = parentNotification.getSbn().getPackageName();
- final int userId = parentNotification.getUserId();
+ 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 ((childSbn.isGroup() && !childSbn.getNotification().isGroupSummary()) &&
- childR.getGroupKey().equals(parentNotification.getGroupKey())
- && (flagChecker == null || flagChecker.apply(childR.getFlags()))
- && (!childR.getChannel().isImportantConversation()
- || reason != REASON_CANCEL)) {
+ if (isChildOfGroup(childR, userId, pkg, groupKey)
+ && (flagChecker == null || flagChecker.apply(childR.getFlags()))
+ && (!childR.getChannel().isImportantConversation() || reason != REASON_CANCEL)) {
EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
childSbn.getTag(), userId, 0, 0, childReason, listenerName);
notificationList.remove(i);
@@ -10354,6 +10628,7 @@
!= null) {
return r;
}
+
return null;
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 0d4bdf6..bd00901 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -449,9 +449,16 @@
mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
mCreationTimeMs = previous.mCreationTimeMs;
mVisibleSinceMs = previous.mVisibleSinceMs;
- if (previous.getSbn().getOverrideGroupKey() != null && !getSbn().isAppGroup()) {
- getSbn().setOverrideGroupKey(previous.getSbn().getOverrideGroupKey());
+ if (android.service.notification.Flags.notificationForceGrouping()) {
+ if (previous.getSbn().getOverrideGroupKey() != null) {
+ getSbn().setOverrideGroupKey(previous.getSbn().getOverrideGroupKey());
+ }
+ } else {
+ if (previous.getSbn().getOverrideGroupKey() != null && !getSbn().isAppGroup()) {
+ getSbn().setOverrideGroupKey(previous.getSbn().getOverrideGroupKey());
+ }
}
+
// Don't copy importance information or mGlobalSortKey, recompute them.
}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index bf6b652..7265cff 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -141,4 +141,11 @@
namespace: "systemui"
description: "This flag does not allow notifications older than 2 weeks old to be posted"
bug: "339833083"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "notification_force_group_singletons"
+ namespace: "systemui"
+ description: "This flag enables forced auto-grouping singleton groups"
+ bug: "336488844"
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 8a7d276..225c1dc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -20,13 +20,23 @@
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_CAN_COLORIZE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+import static android.app.Notification.FLAG_GROUP_SUMMARY;
import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_ONGOING_EVENT;
+import static android.app.Notification.GROUP_ALERT_ALL;
+import static android.app.Notification.GROUP_ALERT_CHILDREN;
+import static android.app.Notification.GROUP_ALERT_SUMMARY;
import static android.app.Notification.VISIBILITY_PRIVATE;
import static android.app.Notification.VISIBILITY_PUBLIC;
import static android.app.Notification.VISIBILITY_SECRET;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
+import static com.android.server.notification.GroupHelper.AGGREGATE_GROUP_KEY;
+import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY;
import static com.android.server.notification.GroupHelper.BASE_FLAGS;
import static com.google.common.truth.Truth.assertThat;
@@ -41,6 +51,7 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -49,6 +60,7 @@
import android.annotation.SuppressLint;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.AdaptiveIconDrawable;
@@ -66,6 +78,7 @@
import com.android.internal.R;
import com.android.server.UiServiceTestCase;
+import com.android.server.notification.GroupHelper.CachedSummary;
import com.android.server.notification.GroupHelper.NotificationAttributes;
import org.junit.Before;
@@ -90,11 +103,15 @@
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
private final int DEFAULT_VISIBILITY = VISIBILITY_PRIVATE;
+ private final int DEFAULT_GROUP_ALERT = GROUP_ALERT_CHILDREN;
+
+ private final String TEST_CHANNEL_ID = "TEST_CHANNEL_ID";
private @Mock GroupHelper.Callback mCallback;
private @Mock PackageManager mPackageManager;
private final static int AUTOGROUP_AT_COUNT = 7;
+ private final static int AUTOGROUP_SINGLETONS_AT_COUNT = 2;
private GroupHelper mGroupHelper;
private @Mock Icon mSmallIcon;
@@ -113,7 +130,7 @@
MockitoAnnotations.initMocks(this);
mGroupHelper = new GroupHelper(getContext(), mPackageManager, AUTOGROUP_AT_COUNT,
- mCallback);
+ AUTOGROUP_SINGLETONS_AT_COUNT, mCallback);
NotificationRecord r = mock(NotificationRecord.class);
StatusBarNotification sbn = getSbn("package", 0, "0", UserHandle.SYSTEM);
@@ -124,7 +141,7 @@
private StatusBarNotification getSbn(String pkg, int id, String tag,
UserHandle user, String groupKey, Icon smallIcon, int iconColor) {
- Notification.Builder nb = new Notification.Builder(getContext(), "test_channel_id")
+ Notification.Builder nb = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
.setContentTitle("A")
.setWhen(1205)
.setSmallIcon(smallIcon)
@@ -146,15 +163,54 @@
return getSbn(pkg, id, tag, user, null);
}
+ private NotificationRecord getNotificationRecord(String pkg, int id, String tag,
+ UserHandle user) {
+ return getNotificationRecord(pkg, id, tag, user, null, false);
+ }
+
+ private NotificationRecord getNotificationRecord(String pkg, int id, String tag,
+ UserHandle user, String groupKey, boolean isSummary) {
+ return getNotificationRecord(pkg, id, tag, user, groupKey, isSummary, IMPORTANCE_DEFAULT);
+ }
+
+ private NotificationRecord getNotificationRecord(String pkg, int id, String tag,
+ UserHandle user, String groupKey, boolean isSummary, int importance) {
+ return getNotificationRecord(pkg, id, tag, user, groupKey, isSummary,
+ new NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_ID, importance));
+ }
+
+ private NotificationRecord getNotificationRecord(String pkg, int id, String tag,
+ UserHandle user, String groupKey, boolean isSummary, NotificationChannel channel) {
+ StatusBarNotification sbn = getSbn(pkg, id, tag, user, groupKey);
+ if (isSummary) {
+ sbn.getNotification().flags |= FLAG_GROUP_SUMMARY;
+ }
+ return new NotificationRecord(getContext(), sbn, channel);
+ }
+
+ private NotificationRecord getNotificationRecord(StatusBarNotification sbn) {
+ return new NotificationRecord(getContext(), sbn,
+ new NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT));
+ }
+
private NotificationAttributes getNotificationAttributes(int flags) {
- return new NotificationAttributes(flags, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY);
+ return new NotificationAttributes(flags, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY,
+ DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID);
+ }
+
+ private String getExpectedAutogroupKey(final NotificationRecord record) {
+ if (android.service.notification.Flags.notificationForceGrouping()) {
+ return GroupHelper.getFullAggregateGroupKey(record);
+ } else {
+ return AUTOGROUP_KEY;
+ }
}
@Test
public void testGetAutogroupSummaryFlags_noChildren() {
ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
- assertEquals(BASE_FLAGS, mGroupHelper.getAutogroupSummaryFlags(children));
+ assertEquals(BASE_FLAGS, GroupHelper.getAutogroupSummaryFlags(children));
}
@Test
@@ -165,7 +221,7 @@
children.put("c", getNotificationAttributes(FLAG_BUBBLE));
assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
- mGroupHelper.getAutogroupSummaryFlags(children));
+ GroupHelper.getAutogroupSummaryFlags(children));
}
@Test
@@ -176,7 +232,7 @@
children.put("c", getNotificationAttributes(FLAG_BUBBLE));
assertEquals(FLAG_NO_CLEAR | FLAG_ONGOING_EVENT | BASE_FLAGS,
- mGroupHelper.getAutogroupSummaryFlags(children));
+ GroupHelper.getAutogroupSummaryFlags(children));
}
@Test
@@ -187,7 +243,7 @@
children.put("c", getNotificationAttributes(FLAG_BUBBLE));
assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
- mGroupHelper.getAutogroupSummaryFlags(children));
+ GroupHelper.getAutogroupSummaryFlags(children));
}
@Test
@@ -199,7 +255,7 @@
children.put("d", getNotificationAttributes(FLAG_ONGOING_EVENT));
assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
- mGroupHelper.getAutogroupSummaryFlags(children));
+ GroupHelper.getAutogroupSummaryFlags(children));
}
@Test
@@ -210,7 +266,7 @@
children.put("c", getNotificationAttributes(FLAG_BUBBLE));
assertEquals(BASE_FLAGS,
- mGroupHelper.getAutogroupSummaryFlags(children));
+ GroupHelper.getAutogroupSummaryFlags(children));
}
@Test
@@ -222,7 +278,7 @@
children.put("d", getNotificationAttributes(FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE));
assertEquals(FLAG_AUTO_CANCEL | BASE_FLAGS,
- mGroupHelper.getAutogroupSummaryFlags(children));
+ GroupHelper.getAutogroupSummaryFlags(children));
}
@Test
@@ -235,15 +291,16 @@
FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE | FLAG_ONGOING_EVENT));
assertEquals(FLAG_AUTO_CANCEL| FLAG_ONGOING_EVENT | BASE_FLAGS,
- mGroupHelper.getAutogroupSummaryFlags(children));
+ GroupHelper.getAutogroupSummaryFlags(children));
}
@Test
public void testNoGroup_postingUnderLimit() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
- mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
- false);
+ mGroupHelper.onNotificationPosted(
+ getNotificationRecord(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
+ false);
}
verifyZeroInteractions(mCallback);
}
@@ -253,11 +310,12 @@
final String pkg = "package";
final String pkg2 = "package2";
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
- mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
- false);
+ mGroupHelper.onNotificationPosted(
+ getNotificationRecord(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
+ false);
}
mGroupHelper.onNotificationPosted(
- getSbn(pkg2, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM), false);
+ getNotificationRecord(pkg2, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM), false);
verifyZeroInteractions(mCallback);
}
@@ -265,11 +323,12 @@
public void testNoGroup_multiUser() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
- mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
- false);
+ mGroupHelper.onNotificationPosted(
+ getNotificationRecord(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
+ false);
}
mGroupHelper.onNotificationPosted(
- getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.of(7)), false);
+ getNotificationRecord(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.of(7)), false);
verifyZeroInteractions(mCallback);
}
@@ -278,10 +337,11 @@
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
mGroupHelper.onNotificationPosted(
- getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false);
+ getNotificationRecord(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false);
}
mGroupHelper.onNotificationPosted(
- getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM, "a"), false);
+ getNotificationRecord(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM, "a", false),
+ false);
verifyZeroInteractions(mCallback);
}
@@ -289,185 +349,241 @@
@DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_alwaysAutogroup() {
final String pkg = "package";
+ final String autogroupKey = getExpectedAutogroupKey(
+ getNotificationRecord(pkg, 0, String.valueOf(0), UserHandle.SYSTEM));
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
mGroupHelper.onNotificationPosted(
- getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false);
+ getNotificationRecord(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false);
}
verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ anyInt(), eq(pkg), anyString(), eq(autogroupKey),
+ anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), eq(autogroupKey),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
}
@Test
@EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary() {
final String pkg = "package";
+ final String autogroupKey = getExpectedAutogroupKey(
+ getNotificationRecord(pkg, 0, String.valueOf(0), UserHandle.SYSTEM));
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
assertThat(mGroupHelper.onNotificationPosted(
- getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false)).isFalse();
+ getNotificationRecord(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
+ false)).isFalse();
}
assertThat(mGroupHelper.onNotificationPosted(
- getSbn(pkg, AUTOGROUP_AT_COUNT - 1, String.valueOf(AUTOGROUP_AT_COUNT - 1),
+ getNotificationRecord(pkg, AUTOGROUP_AT_COUNT - 1, String.valueOf(AUTOGROUP_AT_COUNT - 1),
UserHandle.SYSTEM), false)).isTrue();
- verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(autogroupKey), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyString(),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
}
@Test
@DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_oneChildOngoing_summaryOngoing_alwaysAutogroup() {
final String pkg = "package";
+ final String autogroupKey = getExpectedAutogroupKey(
+ getNotificationRecord(pkg, 0, String.valueOf(0), UserHandle.SYSTEM));
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
if (i == 0) {
- sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+ r.getNotification().flags |= FLAG_ONGOING_EVENT;
}
- mGroupHelper.onNotificationPosted(sbn, false);
+ mGroupHelper.onNotificationPosted(r, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(autogroupKey), anyInt(),
eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyString(),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
}
@Test
@EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_oneChildOngoing_summaryOngoing() {
final String pkg = "package";
+ final String autogroupKey = getExpectedAutogroupKey(
+ getNotificationRecord(pkg, 0, String.valueOf(0), UserHandle.SYSTEM));
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
if (i == 0) {
- sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+ r.getNotification().flags |= FLAG_ONGOING_EVENT;
}
- mGroupHelper.onNotificationPosted(sbn, false);
+ mGroupHelper.onNotificationPosted(r, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(autogroupKey), anyInt(),
eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyString(),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
}
@Test
@DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_oneChildAutoCancel_summaryNotAutoCancel_alwaysAutogroup() {
final String pkg = "package";
+ final String autogroupKey = getExpectedAutogroupKey(
+ getNotificationRecord(pkg, 0, String.valueOf(0), UserHandle.SYSTEM));
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
if (i == 0) {
- sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ r.getNotification().flags |= FLAG_AUTO_CANCEL;
}
- mGroupHelper.onNotificationPosted(sbn, false);
+ mGroupHelper.onNotificationPosted(r, false);
}
- verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(autogroupKey), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyString(),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
}
@Test
@EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_oneChildAutoCancel_summaryNotAutoCancel() {
final String pkg = "package";
+ final String autogroupKey = getExpectedAutogroupKey(
+ getNotificationRecord(pkg, 0, String.valueOf(0), UserHandle.SYSTEM));
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
if (i == 0) {
- sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ r.getNotification().flags |= FLAG_AUTO_CANCEL;
}
- mGroupHelper.onNotificationPosted(sbn, false);
+ mGroupHelper.onNotificationPosted(r, false);
}
- verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(autogroupKey), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(),
+ eq(autogroupKey), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
}
@Test
@DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_allChildrenAutoCancel_summaryAutoCancel_alwaysAutogroup() {
final String pkg = "package";
+ final String autogroupKey = getExpectedAutogroupKey(
+ getNotificationRecord(pkg, 0, String.valueOf(0), UserHandle.SYSTEM));
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
- sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
- mGroupHelper.onNotificationPosted(sbn, false);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ r.getNotification().flags |= FLAG_AUTO_CANCEL;
+ mGroupHelper.onNotificationPosted(r, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(autogroupKey), anyInt(),
eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), eq(autogroupKey),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
}
@Test
@EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_allChildrenAutoCancel_summaryAutoCancel() {
final String pkg = "package";
+ final String autogroupKey = getExpectedAutogroupKey(
+ getNotificationRecord(pkg, 0, String.valueOf(0), UserHandle.SYSTEM));
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
- sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
- mGroupHelper.onNotificationPosted(sbn, false);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ r.getNotification().flags |= FLAG_AUTO_CANCEL;
+ mGroupHelper.onNotificationPosted(r, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(autogroupKey), anyInt(),
eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(),
+ eq(autogroupKey), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
}
@Test
@DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_summaryAutoCancelNoClear_alwaysAutogroup() {
final String pkg = "package";
+ final String autogroupKey = getExpectedAutogroupKey(
+ getNotificationRecord(pkg, 0, String.valueOf(0), UserHandle.SYSTEM));
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
- sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ r.getNotification().flags |= FLAG_AUTO_CANCEL;
if (i == 0) {
- sbn.getNotification().flags |= FLAG_NO_CLEAR;
+ r.getNotification().flags |= FLAG_NO_CLEAR;
}
- mGroupHelper.onNotificationPosted(sbn, false);
+ mGroupHelper.onNotificationPosted(r, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(autogroupKey), anyInt(),
eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), eq(autogroupKey),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
}
@Test
@EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAddSummary_summaryAutoCancelNoClear() {
final String pkg = "package";
+ final String autogroupKey = getExpectedAutogroupKey(
+ getNotificationRecord(pkg, 0, String.valueOf(0), UserHandle.SYSTEM));
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
- sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ r.getNotification().flags |= FLAG_AUTO_CANCEL;
if (i == 0) {
- sbn.getNotification().flags |= FLAG_NO_CLEAR;
+ r.getNotification().flags |= FLAG_NO_CLEAR;
}
- mGroupHelper.onNotificationPosted(sbn, false);
+ mGroupHelper.onNotificationPosted(r, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(autogroupKey), anyInt(),
eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(),
+ eq(autogroupKey), anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
}
@Test
@@ -475,15 +591,16 @@
final String pkg = "package";
// Post AUTOGROUP_AT_COUNT ongoing notifications
- ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ ArrayList<NotificationRecord> notifications = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
- sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
- notifications.add(sbn);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ r.getNotification().flags |= FLAG_ONGOING_EVENT;
+ notifications.add(r);
}
- for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, false);
+ for (NotificationRecord r: notifications) {
+ mGroupHelper.onNotificationPosted(r, false);
}
// One notification is no longer ongoing
@@ -491,7 +608,7 @@
mGroupHelper.onNotificationPosted(notifications.get(0), true);
// Summary should keep FLAG_ONGOING_EVENT if any child has it
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), anyString(),
eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
}
@@ -500,24 +617,25 @@
final String pkg = "package";
// Post AUTOGROUP_AT_COUNT ongoing notifications
- ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ ArrayList<NotificationRecord> notifications = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
if (i == 0) {
- sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+ r.getNotification().flags |= FLAG_ONGOING_EVENT;
}
- notifications.add(sbn);
+ notifications.add(r);
}
- for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, false);
+ for (NotificationRecord r: notifications) {
+ mGroupHelper.onNotificationPosted(r, false);
}
// remove ongoing
mGroupHelper.onNotificationRemoved(notifications.get(0));
// Summary is no longer ongoing
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), anyString(),
eq(getNotificationAttributes(BASE_FLAGS)));
}
@@ -526,14 +644,15 @@
final String pkg = "package";
// Post AUTOGROUP_AT_COUNT ongoing notifications
- ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ ArrayList<NotificationRecord> notifications = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
- notifications.add(sbn);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ notifications.add(r);
}
- for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, false);
+ for (NotificationRecord r: notifications) {
+ mGroupHelper.onNotificationPosted(r, false);
}
// update to ongoing
@@ -541,7 +660,7 @@
mGroupHelper.onNotificationPosted(notifications.get(0), true);
// Summary is now ongoing
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), anyString(),
eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
}
@@ -550,23 +669,25 @@
final String pkg = "package";
// Post AUTOGROUP_AT_COUNT ongoing notifications
- ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ ArrayList<NotificationRecord> notifications = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
- notifications.add(sbn);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ notifications.add(r);
}
- for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, false);
+ for (NotificationRecord r: notifications) {
+ mGroupHelper.onNotificationPosted(r, false);
}
// add ongoing
- StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT + 1, null, UserHandle.SYSTEM);
- sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
- mGroupHelper.onNotificationPosted(sbn, true);
+ NotificationRecord r = getNotificationRecord(pkg, AUTOGROUP_AT_COUNT + 1, null,
+ UserHandle.SYSTEM);
+ r.getNotification().flags |= FLAG_ONGOING_EVENT;
+ mGroupHelper.onNotificationPosted(r, true);
// Summary is now ongoing
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), anyString(),
eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
}
@@ -575,51 +696,84 @@
final String pkg = "package";
// Post AUTOGROUP_AT_COUNT ongoing notifications
- ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ ArrayList<NotificationRecord> notifications = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
if (i == 0) {
- sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+ r.getNotification().flags |= FLAG_ONGOING_EVENT;
}
- notifications.add(sbn);
+ notifications.add(r);
}
- for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, false);
+ for (NotificationRecord r: notifications) {
+ mGroupHelper.onNotificationPosted(r, false);
}
// app group the ongoing child
- StatusBarNotification sbn = getSbn(pkg, 0, "0", UserHandle.SYSTEM, "app group now");
- mGroupHelper.onNotificationPosted(sbn, true);
+ NotificationRecord r = getNotificationRecord(pkg, 0, "0", UserHandle.SYSTEM,
+ "app group now", false);
+ mGroupHelper.onNotificationPosted(r, true);
// Summary is no longer ongoing
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), anyString(),
eq(getNotificationAttributes(BASE_FLAGS)));
}
@Test
+ @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testAutoGrouped_singleOngoing_removeNonOngoingChild() {
final String pkg = "package";
// Post AUTOGROUP_AT_COUNT ongoing notifications
- ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ ArrayList<NotificationRecord> notifications = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
if (i == 0) {
- sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+ r.getNotification().flags |= FLAG_ONGOING_EVENT;
}
- notifications.add(sbn);
+ notifications.add(r);
}
- for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, false);
+ for (NotificationRecord r: notifications) {
+ mGroupHelper.onNotificationPosted(r, false);
}
// remove ongoing
mGroupHelper.onNotificationRemoved(notifications.get(1));
// Summary is still ongoing
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testAutoGrouped_singleOngoing_removeNonOngoingChild_forceGrouping() {
+ final String pkg = "package";
+
+ // Post AUTOGROUP_AT_COUNT ongoing notifications
+ ArrayList<NotificationRecord> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ if (i == 0) {
+ r.getNotification().flags |= FLAG_ONGOING_EVENT;
+ }
+ notifications.add(r);
+ }
+
+ for (NotificationRecord r: notifications) {
+ mGroupHelper.onNotificationPosted(r, false);
+ }
+
+ // remove ongoing
+ mGroupHelper.onNotificationRemoved(notifications.get(1));
+
+ // Summary is still ongoing
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
}
@Test
@@ -627,15 +781,16 @@
final String pkg = "package";
// Post AUTOGROUP_AT_COUNT ongoing notifications
- ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ ArrayList<NotificationRecord> notifications = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
- sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
- notifications.add(sbn);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ r.getNotification().flags |= FLAG_AUTO_CANCEL;
+ notifications.add(r);
}
- for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, false);
+ for (NotificationRecord r: notifications) {
+ mGroupHelper.onNotificationPosted(r, false);
}
// One notification is no longer autocancelable
@@ -643,7 +798,7 @@
mGroupHelper.onNotificationPosted(notifications.get(0), true);
// Summary should no longer be autocancelable
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), anyString(),
eq(getNotificationAttributes(BASE_FLAGS)));
}
@@ -652,17 +807,18 @@
final String pkg = "package";
// Post AUTOGROUP_AT_COUNT ongoing notifications
- ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ ArrayList<NotificationRecord> notifications = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
if (i != 0) {
- sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ r.getNotification().flags |= FLAG_AUTO_CANCEL;
}
- notifications.add(sbn);
+ notifications.add(r);
}
- for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, false);
+ for (NotificationRecord r: notifications) {
+ mGroupHelper.onNotificationPosted(r, false);
}
// Missing notification is now autocancelable
@@ -670,254 +826,327 @@
mGroupHelper.onNotificationPosted(notifications.get(0), true);
// Summary should now autocancelable
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), anyString(),
eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL)));
}
@Test
+ @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testAutoGrouped_allAutoCancel_updateChildAppGrouped() {
final String pkg = "package";
// Post AUTOGROUP_AT_COUNT ongoing notifications
- ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ ArrayList<NotificationRecord> notifications = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
- sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
- notifications.add(sbn);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ r.getNotification().flags |= FLAG_AUTO_CANCEL;
+ notifications.add(r);
}
- for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, false);
+ for (NotificationRecord r: notifications) {
+ mGroupHelper.onNotificationPosted(r, false);
}
// One notification is now grouped by app
- StatusBarNotification sbn = getSbn(pkg, 0, "0", UserHandle.SYSTEM, "app group now");
- mGroupHelper.onNotificationPosted(sbn, true);
+ NotificationRecord r = getNotificationRecord(pkg, 0, "0", UserHandle.SYSTEM,
+ "app group now", false);
+ mGroupHelper.onNotificationPosted(r, true);
// Summary should be still be autocancelable
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testAutoGrouped_allAutoCancel_updateChildAppGrouped_forceGrouping() {
+ final String pkg = "package";
+
+ // Post AUTOGROUP_AT_COUNT ongoing notifications
+ ArrayList<NotificationRecord> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ r.getNotification().flags |= FLAG_AUTO_CANCEL;
+ notifications.add(r);
+ }
+
+ for (NotificationRecord r: notifications) {
+ mGroupHelper.onNotificationPosted(r, false);
+ }
+
+ // One notification is now grouped by app
+ NotificationRecord r = getNotificationRecord(pkg, 0, "0", UserHandle.SYSTEM,
+ "app group now", false);
+ mGroupHelper.onNotificationPosted(r, true);
+
+ // Summary should be still be autocancelable
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ }
+
+ @Test
+ @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testAutoGrouped_allAutoCancel_removeChild() {
final String pkg = "package";
// Post AUTOGROUP_AT_COUNT ongoing notifications
- ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ ArrayList<NotificationRecord> notifications = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
- sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
- notifications.add(sbn);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ r.getNotification().flags |= FLAG_AUTO_CANCEL;
+ notifications.add(r);
}
- for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, false);
+ for (NotificationRecord r: notifications) {
+ mGroupHelper.onNotificationPosted(r, false);
}
mGroupHelper.onNotificationRemoved(notifications.get(0));
// Summary should still be autocancelable
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testAutoGrouped_allAutoCancel_removeChild_forceGrouping() {
+ final String pkg = "package";
+
+ // Post AUTOGROUP_AT_COUNT ongoing notifications
+ ArrayList<NotificationRecord> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ r.getNotification().flags |= FLAG_AUTO_CANCEL;
+ notifications.add(r);
+ }
+
+ for (NotificationRecord r: notifications) {
+ mGroupHelper.onNotificationPosted(r, false);
+ }
+
+ mGroupHelper.onNotificationRemoved(notifications.get(0));
+
+ // Summary should still be autocancelable
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
}
@Test
@DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testDropToZeroRemoveGroup_disableFlag() {
final String pkg = "package";
- List<StatusBarNotification> posted = new ArrayList<>();
+ ArrayList<NotificationRecord> posted = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- final StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
- posted.add(sbn);
- mGroupHelper.onNotificationPosted(sbn, false);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ posted.add(r);
+ mGroupHelper.onNotificationPosted(r, false);
}
- verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), anyString(),
+ anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyString(),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
Mockito.reset(mCallback);
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
mGroupHelper.onNotificationRemoved(posted.remove(0));
}
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
Mockito.reset(mCallback);
mGroupHelper.onNotificationRemoved(posted.remove(0));
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString(), anyString());
}
@Test
@EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testDropToZeroRemoveGroup() {
final String pkg = "package";
- List<StatusBarNotification> posted = new ArrayList<>();
+ ArrayList<NotificationRecord> posted = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- final StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
- posted.add(sbn);
- mGroupHelper.onNotificationPosted(sbn, false);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ posted.add(r);
+ mGroupHelper.onNotificationPosted(r, false);
}
- verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), anyString(),
+ anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyString(),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
Mockito.reset(mCallback);
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
mGroupHelper.onNotificationRemoved(posted.remove(0));
}
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
Mockito.reset(mCallback);
mGroupHelper.onNotificationRemoved(posted.remove(0));
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString(), anyString());
}
@Test
@DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAppStartsGrouping_disableFlag() {
final String pkg = "package";
- List<StatusBarNotification> posted = new ArrayList<>();
+ ArrayList<NotificationRecord> posted = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- final StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
- posted.add(sbn);
- mGroupHelper.onNotificationPosted(sbn, false);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ posted.add(r);
+ mGroupHelper.onNotificationPosted(r, false);
}
- verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ anyString(), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyString(),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
Mockito.reset(mCallback);
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- final StatusBarNotification sbn =
- getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, "app group");
- sbn.setOverrideGroupKey("autogrouped");
- mGroupHelper.onNotificationPosted(sbn, true);
- verify(mCallback, times(1)).removeAutoGroup(sbn.getKey());
+ final NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "app group", false);
+ r.getSbn().setOverrideGroupKey("autogrouped");
+ mGroupHelper.onNotificationPosted(r, true);
+ verify(mCallback, times(1)).removeAutoGroup(r.getKey());
if (i < AUTOGROUP_AT_COUNT - 1) {
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(),
+ anyString());
}
}
- verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString(), anyString());
}
@Test
@EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testAppStartsGrouping() {
final String pkg = "package";
- List<StatusBarNotification> posted = new ArrayList<>();
+ ArrayList<NotificationRecord> posted = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- final StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
- posted.add(sbn);
- mGroupHelper.onNotificationPosted(sbn, false);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ posted.add(r);
+ mGroupHelper.onNotificationPosted(r, false);
}
- verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ anyString(), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyString(),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
Mockito.reset(mCallback);
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- final StatusBarNotification sbn =
- getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, "app group");
- sbn.setOverrideGroupKey("autogrouped");
- mGroupHelper.onNotificationPosted(sbn, true);
- verify(mCallback, times(1)).removeAutoGroup(sbn.getKey());
+ final NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "app group", false);
+ r.getSbn().setOverrideGroupKey("autogrouped");
+ mGroupHelper.onNotificationPosted(r, true);
+ verify(mCallback, times(1)).removeAutoGroup(r.getKey());
if (i < AUTOGROUP_AT_COUNT - 1) {
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(),
+ anyString());
}
}
- verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString(), anyString());
}
@Test
@DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled_alwaysGroup() {
final String pkg = "package";
- List<StatusBarNotification> posted = new ArrayList<>();
+ ArrayList<NotificationRecord> posted = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- final StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
- posted.add(sbn);
- mGroupHelper.onNotificationPosted(sbn, false);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ posted.add(r);
+ mGroupHelper.onNotificationPosted(r, false);
}
- verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ anyString(), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyString(),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
Mockito.reset(mCallback);
for (int i = posted.size() - 2; i >= 0; i--) {
mGroupHelper.onNotificationRemoved(posted.remove(i));
}
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
Mockito.reset(mCallback);
- // only one child remains
- assertEquals(1, mGroupHelper.getNotGroupedByAppCount(UserHandle.USER_SYSTEM, pkg));
-
// Add new notification; it should be autogrouped even though the total count is
// < AUTOGROUP_AT_COUNT
- final StatusBarNotification sbn = getSbn(pkg, 5, String.valueOf(5), UserHandle.SYSTEM);
- posted.add(sbn);
- assertThat(mGroupHelper.onNotificationPosted(sbn, true)).isFalse();
- verify(mCallback, times(1)).addAutoGroup(sbn.getKey(), true);
+ final NotificationRecord r = getNotificationRecord(pkg, 5, String.valueOf(5),
+ UserHandle.SYSTEM);
+ final String autogroupKey = getExpectedAutogroupKey(r);
+ posted.add(r);
+ assertThat(mGroupHelper.onNotificationPosted(r, true)).isFalse();
+ verify(mCallback, times(1)).addAutoGroup(r.getKey(), autogroupKey, true);
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), anyString(),
eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(), any());
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
}
@Test
@EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() {
final String pkg = "package";
- List<StatusBarNotification> posted = new ArrayList<>();
+ ArrayList<NotificationRecord> posted = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- final StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
- posted.add(sbn);
- mGroupHelper.onNotificationPosted(sbn, false);
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM);
+ posted.add(r);
+ mGroupHelper.onNotificationPosted(r, false);
}
- verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ anyString(), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyString(),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
Mockito.reset(mCallback);
for (int i = posted.size() - 2; i >= 0; i--) {
mGroupHelper.onNotificationRemoved(posted.remove(i));
}
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
Mockito.reset(mCallback);
- // only one child remains
- assertEquals(1, mGroupHelper.getNotGroupedByAppCount(UserHandle.USER_SYSTEM, pkg));
-
// Add new notification; it should be autogrouped even though the total count is
// < AUTOGROUP_AT_COUNT
- final StatusBarNotification sbn = getSbn(pkg, 5, String.valueOf(5), UserHandle.SYSTEM);
- posted.add(sbn);
- assertThat(mGroupHelper.onNotificationPosted(sbn, true)).isTrue();
+ final NotificationRecord r = getNotificationRecord(pkg, 5, String.valueOf(5),
+ UserHandle.SYSTEM);
+ posted.add(r);
+ assertThat(mGroupHelper.onNotificationPosted(r, true)).isTrue();
// addAutoGroup not called on sbn, because the autogrouping is expected to be done
// synchronously.
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), anyString(),
eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(), any());
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
}
@Test
@@ -929,29 +1158,32 @@
when(icon.sameAs(icon)).thenReturn(true);
final int iconColor = Color.BLUE;
final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor,
- DEFAULT_VISIBILITY);
+ DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID);
// Add notifications with same icon and color
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
- icon, iconColor);
- mGroupHelper.onNotificationPosted(sbn, false);
+ NotificationRecord r = getNotificationRecord(
+ getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null, icon, iconColor));
+ mGroupHelper.onNotificationPosted(r, false);
}
// Check that the summary would have the same icon and color
verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(attr));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ anyInt(), eq(pkg), anyString(), anyString(), anyInt(), eq(attr));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyString(),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
// After auto-grouping, add new notification with the same color
- StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
- String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor);
- mGroupHelper.onNotificationPosted(sbn, true);
+ NotificationRecord r = getNotificationRecord(
+ getSbn(pkg, AUTOGROUP_AT_COUNT, String.valueOf(AUTOGROUP_AT_COUNT),
+ UserHandle.SYSTEM,null, icon, iconColor));
+ mGroupHelper.onNotificationPosted(r, true);
// Check that the summary was updated
//NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, icon, iconColor);
- verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(attr));
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ eq(attr));
}
@Test
@@ -963,29 +1195,31 @@
when(icon.sameAs(icon)).thenReturn(true);
final int iconColor = Color.BLUE;
final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor,
- DEFAULT_VISIBILITY);
+ DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID);
// Add notifications with same icon and color
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
- icon, iconColor);
- mGroupHelper.onNotificationPosted(sbn, false);
+ NotificationRecord r = getNotificationRecord(
+ getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null, icon, iconColor));
+ mGroupHelper.onNotificationPosted(r, false);
}
// Check that the summary would have the same icon and color
- verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(attr));
- verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ anyString(), anyInt(), eq(attr));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyString(),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
// After auto-grouping, add new notification with the same color
- StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
- String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor);
- mGroupHelper.onNotificationPosted(sbn, true);
+ NotificationRecord r = getNotificationRecord(getSbn(pkg, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor));
+ mGroupHelper.onNotificationPosted(r, true);
// Check that the summary was updated
//NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, icon, iconColor);
- verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(attr));
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ eq(attr));
}
@Test
@@ -1004,33 +1238,37 @@
doReturn(monochromeIcon).when(groupHelper).getMonochromeAppIcon(eq(pkg));
final NotificationAttributes initialAttr = new NotificationAttributes(BASE_FLAGS,
- initialIcon, initialIconColor, DEFAULT_VISIBILITY);
+ initialIcon, initialIconColor, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ TEST_CHANNEL_ID);
// Add notifications with same icon and color
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
- initialIcon, initialIconColor);
- groupHelper.onNotificationPosted(sbn, false);
+ NotificationRecord r = getNotificationRecord(
+ getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ initialIcon, initialIconColor));
+ groupHelper.onNotificationPosted(r, false);
}
// Check that the summary would have the same icon and color
- verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(initialAttr));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ anyString(), anyInt(), eq(initialAttr));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyString(),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
// After auto-grouping, add new notification with a different color
final Icon newIcon = mock(Icon.class);
final int newIconColor = Color.YELLOW;
- StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
+ NotificationRecord r = getNotificationRecord(getSbn(pkg, AUTOGROUP_AT_COUNT,
String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, newIcon,
- newIconColor);
- groupHelper.onNotificationPosted(sbn, true);
+ newIconColor));
+ groupHelper.onNotificationPosted(r, true);
// Summary should be updated to the default color and the icon to the monochrome icon
NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, monochromeIcon,
- COLOR_DEFAULT, DEFAULT_VISIBILITY);
- verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(newAttr));
+ COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID);
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ eq(newAttr));
}
@Test
@@ -1049,33 +1287,37 @@
doReturn(monochromeIcon).when(groupHelper).getMonochromeAppIcon(eq(pkg));
final NotificationAttributes initialAttr = new NotificationAttributes(BASE_FLAGS,
- initialIcon, initialIconColor, DEFAULT_VISIBILITY);
+ initialIcon, initialIconColor, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ TEST_CHANNEL_ID);
// Add notifications with same icon and color
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
- initialIcon, initialIconColor);
- groupHelper.onNotificationPosted(sbn, false);
+ NotificationRecord r = getNotificationRecord(
+ getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ initialIcon, initialIconColor));
+ groupHelper.onNotificationPosted(r, false);
}
// Check that the summary would have the same icon and color
- verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(initialAttr));
- verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ anyString(), anyInt(), eq(initialAttr));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyString(),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
// After auto-grouping, add new notification with a different color
final Icon newIcon = mock(Icon.class);
final int newIconColor = Color.YELLOW;
- StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
- String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, newIcon,
- newIconColor);
- groupHelper.onNotificationPosted(sbn, true);
+ NotificationRecord r = getNotificationRecord(getSbn(pkg, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, newIcon,
+ newIconColor));
+ groupHelper.onNotificationPosted(r, true);
// Summary should be updated to the default color and the icon to the monochrome icon
NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, monochromeIcon,
- COLOR_DEFAULT, DEFAULT_VISIBILITY);
- verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(newAttr));
+ COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID);
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ eq(newAttr));
}
@Test
@@ -1087,32 +1329,35 @@
when(icon.sameAs(icon)).thenReturn(true);
final int iconColor = Color.BLUE;
final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor,
- VISIBILITY_PRIVATE);
+ VISIBILITY_PRIVATE, DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID);
// Add notifications with same icon and color and default visibility (private)
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
- icon, iconColor);
- mGroupHelper.onNotificationPosted(sbn, false);
+ NotificationRecord r = getNotificationRecord(
+ getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ icon, iconColor));
+ mGroupHelper.onNotificationPosted(r, false);
}
// Check that the summary has private visibility
verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(attr));
+ anyInt(), eq(pkg), anyString(), anyString(), anyInt(), eq(attr));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyString(),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
// After auto-grouping, add new notification with public visibility
- StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
- String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor);
- sbn.getNotification().visibility = VISIBILITY_PUBLIC;
- mGroupHelper.onNotificationPosted(sbn, true);
+ NotificationRecord r = getNotificationRecord(getSbn(pkg, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor));
+ r.getNotification().visibility = VISIBILITY_PUBLIC;
+ mGroupHelper.onNotificationPosted(r, true);
// Check that the summary visibility was updated
NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, icon, iconColor,
- VISIBILITY_PUBLIC);
- verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(newAttr));
+ VISIBILITY_PUBLIC, DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID);
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ eq(newAttr));
}
@Test
@@ -1124,71 +1369,116 @@
when(icon.sameAs(icon)).thenReturn(true);
final int iconColor = Color.BLUE;
final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor,
- VISIBILITY_PRIVATE);
+ VISIBILITY_PRIVATE, DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID);
// Add notifications with same icon and color and default visibility (private)
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
- icon, iconColor);
- assertThat(mGroupHelper.onNotificationPosted(sbn, false)).isFalse();
+ NotificationRecord r = getNotificationRecord(
+ getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ icon, iconColor));
+ assertThat(mGroupHelper.onNotificationPosted(r, false)).isFalse();
}
// The last notification added will reach the autogroup threshold.
- StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT - 1,
- String.valueOf(AUTOGROUP_AT_COUNT - 1), UserHandle.SYSTEM, null, icon, iconColor);
- assertThat(mGroupHelper.onNotificationPosted(sbn, false)).isTrue();
+ NotificationRecord r = getNotificationRecord(getSbn(pkg, AUTOGROUP_AT_COUNT - 1,
+ String.valueOf(AUTOGROUP_AT_COUNT - 1), UserHandle.SYSTEM, null, icon, iconColor));
+ assertThat(mGroupHelper.onNotificationPosted(r, false)).isTrue();
// Check that the summary has private visibility
- verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(attr));
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), anyString(),
+ anyInt(), eq(attr));
// The last sbn is expected to be added to autogroup synchronously.
- verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyString(),
+ anyBoolean());
verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
// After auto-grouping, add new notification with public visibility
- sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
- String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor);
- sbn.getNotification().visibility = VISIBILITY_PUBLIC;
- assertThat(mGroupHelper.onNotificationPosted(sbn, true)).isTrue();
+ r = getNotificationRecord(getSbn(pkg, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor));
+ r.getNotification().visibility = VISIBILITY_PUBLIC;
+ assertThat(mGroupHelper.onNotificationPosted(r, true)).isTrue();
// Check that the summary visibility was updated
NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, icon, iconColor,
- VISIBILITY_PUBLIC);
- verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(newAttr));
+ VISIBILITY_PUBLIC, DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID);
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ eq(newAttr));
}
@Test
@EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testAutoGrouped_diffIcon_diffColor_removeChild_updateTo_sameIcon_sameColor() {
final String pkg = "package";
final Icon initialIcon = mock(Icon.class);
when(initialIcon.sameAs(initialIcon)).thenReturn(true);
final int initialIconColor = Color.BLUE;
final NotificationAttributes initialAttr = new NotificationAttributes(
- GroupHelper.FLAG_INVALID, initialIcon, initialIconColor, DEFAULT_VISIBILITY);
+ GroupHelper.FLAG_INVALID, initialIcon, initialIconColor, DEFAULT_VISIBILITY,
+ DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID);
// Add AUTOGROUP_AT_COUNT-1 notifications with same icon and color
- ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ ArrayList<NotificationRecord> notifications = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
- StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
- initialIcon, initialIconColor);
- notifications.add(sbn);
+ NotificationRecord r = getNotificationRecord(
+ getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ initialIcon, initialIconColor));
+ notifications.add(r);
}
// And an additional notification with different icon and color
final int lastIdx = AUTOGROUP_AT_COUNT - 1;
- StatusBarNotification newSbn = getSbn(pkg, lastIdx,
+ NotificationRecord newRec = getNotificationRecord(getSbn(pkg, lastIdx,
String.valueOf(lastIdx), UserHandle.SYSTEM, null, mock(Icon.class),
- Color.YELLOW);
- notifications.add(newSbn);
- for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, false);
+ Color.YELLOW));
+ notifications.add(newRec);
+ for (NotificationRecord r: notifications) {
+ mGroupHelper.onNotificationPosted(r, false);
}
// Remove last notification (the only one with different icon and color)
mGroupHelper.onNotificationRemoved(notifications.get(lastIdx));
// Summary should be updated to the common icon and color
- verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(initialAttr));
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ eq(initialAttr));
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE,
+ FLAG_NOTIFICATION_FORCE_GROUPING})
+ public void testAutoGrouped_diffIcon_diffColor_removeChild_updateTo_sameIcon_sameColor_forceGrouping() {
+ final String pkg = "package";
+ final Icon initialIcon = mock(Icon.class);
+ when(initialIcon.sameAs(initialIcon)).thenReturn(true);
+ final int initialIconColor = Color.BLUE;
+ final NotificationAttributes initialAttr = new NotificationAttributes(
+ BASE_FLAGS, initialIcon, initialIconColor, DEFAULT_VISIBILITY,
+ DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID);
+
+ // Add AUTOGROUP_AT_COUNT-1 notifications with same icon and color
+ ArrayList<NotificationRecord> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ NotificationRecord r = getNotificationRecord(
+ getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ initialIcon, initialIconColor));
+ notifications.add(r);
+ }
+ // And an additional notification with different icon and color
+ final int lastIdx = AUTOGROUP_AT_COUNT - 1;
+ NotificationRecord newRec = getNotificationRecord(getSbn(pkg, lastIdx,
+ String.valueOf(lastIdx), UserHandle.SYSTEM, null, mock(Icon.class),
+ Color.YELLOW));
+ notifications.add(newRec);
+ for (NotificationRecord r: notifications) {
+ mGroupHelper.onNotificationPosted(r, false);
+ }
+
+ // Remove last notification (the only one with different icon and color)
+ mGroupHelper.onNotificationRemoved(notifications.get(lastIdx));
+
+ // Summary should be updated to the common icon and color
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ eq(initialAttr));
}
@Test
@@ -1202,7 +1492,7 @@
List<NotificationAttributes> childrenAttr = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
childrenAttr.add(new NotificationAttributes(0, icon, COLOR_DEFAULT,
- DEFAULT_VISIBILITY));
+ DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID));
}
//Check that the generated summary icon is the same as the child notifications'
@@ -1223,7 +1513,7 @@
List<NotificationAttributes> childrenAttr = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), COLOR_DEFAULT,
- DEFAULT_VISIBILITY));
+ DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID));
}
// Check that the generated summary icon is the monochrome icon
@@ -1240,7 +1530,7 @@
List<NotificationAttributes> childrenAttr = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor,
- DEFAULT_VISIBILITY));
+ DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID));
}
// Check that the generated summary icon color is the same as the child notifications'
@@ -1257,7 +1547,7 @@
List<NotificationAttributes> childrenAttr = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), i,
- DEFAULT_VISIBILITY));
+ DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID));
}
// Check that the generated summary icon color is the default color
@@ -1274,10 +1564,10 @@
// Create notifications with private and public visibility
List<NotificationAttributes> childrenAttr = new ArrayList<>();
childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor,
- VISIBILITY_PUBLIC));
+ VISIBILITY_PUBLIC, DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID));
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor,
- VISIBILITY_PRIVATE));
+ VISIBILITY_PRIVATE, DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID));
}
// Check that the generated summary visibility is public
@@ -1301,7 +1591,7 @@
visibility = VISIBILITY_SECRET;
}
childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor,
- visibility));
+ visibility, DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID));
}
// Check that the generated summary visibility is private
@@ -1311,6 +1601,90 @@
}
@Test
+ public void testAutobundledSummaryAlertBehavior_oneChildAlertChildren() {
+ final String pkg = "package";
+ final int iconColor = Color.BLUE;
+ // Create notifications with GROUP_ALERT_SUMMARY + one with GROUP_ALERT_CHILDREN
+ List<NotificationAttributes> childrenAttr = new ArrayList<>();
+ childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor,
+ VISIBILITY_PUBLIC, GROUP_ALERT_CHILDREN, TEST_CHANNEL_ID));
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor,
+ VISIBILITY_PRIVATE, GROUP_ALERT_SUMMARY, TEST_CHANNEL_ID));
+ }
+ // Check that the generated summary alert behavior is GROUP_ALERT_CHILDREN
+ int groupAlertBehavior = mGroupHelper.getAutobundledSummaryAttributes(pkg,
+ childrenAttr).groupAlertBehavior;
+ assertThat(groupAlertBehavior).isEqualTo(GROUP_ALERT_CHILDREN);
+ }
+
+ @Test
+ public void testAutobundledSummaryAlertBehavior_oneChildAlertAll() {
+ final String pkg = "package";
+ final int iconColor = Color.BLUE;
+ // Create notifications with GROUP_ALERT_SUMMARY + one with GROUP_ALERT_ALL
+ List<NotificationAttributes> childrenAttr = new ArrayList<>();
+ childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor,
+ VISIBILITY_PUBLIC, GROUP_ALERT_ALL, TEST_CHANNEL_ID));
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor,
+ VISIBILITY_PRIVATE, GROUP_ALERT_SUMMARY, TEST_CHANNEL_ID));
+ }
+ // Check that the generated summary alert behavior is GROUP_ALERT_CHILDREN
+ int groupAlertBehavior = mGroupHelper.getAutobundledSummaryAttributes(pkg,
+ childrenAttr).groupAlertBehavior;
+ assertThat(groupAlertBehavior).isEqualTo(GROUP_ALERT_CHILDREN);
+ }
+
+ @Test
+ public void testAutobundledSummaryAlertBehavior_allChildAlertSummary() {
+ final String pkg = "package";
+ final int iconColor = Color.BLUE;
+ // Create notifications with GROUP_ALERT_SUMMARY
+ List<NotificationAttributes> childrenAttr = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor,
+ VISIBILITY_PRIVATE, GROUP_ALERT_SUMMARY, TEST_CHANNEL_ID));
+ }
+
+ // Check that the generated summary alert behavior is GROUP_ALERT_SUMMARY
+ int groupAlertBehavior = mGroupHelper.getAutobundledSummaryAttributes(pkg,
+ childrenAttr).groupAlertBehavior;
+ assertThat(groupAlertBehavior).isEqualTo(GROUP_ALERT_SUMMARY);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAutobundledSummaryChannelId() {
+ final String pkg = "package";
+ final int iconColor = Color.BLUE;
+ final String expectedChannelId = TEST_CHANNEL_ID + "0";
+ // Create notifications with different channelIds
+ List<NotificationAttributes> childrenAttr = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor,
+ DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, TEST_CHANNEL_ID+i));
+ }
+
+ // Check that the generated summary channelId is the first child in the list
+ String summaryChannelId = mGroupHelper.getAutobundledSummaryAttributes(pkg,
+ childrenAttr).channelId;
+ assertThat(summaryChannelId).isEqualTo(expectedChannelId);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAutobundledSummaryChannelId_noChildren() {
+ final String pkg = "package";
+ // No child notifications
+ List<NotificationAttributes> childrenAttr = new ArrayList<>();
+ // Check that the generated summary channelId is null
+ String summaryChannelId = mGroupHelper.getAutobundledSummaryAttributes(pkg,
+ childrenAttr).channelId;
+ assertThat(summaryChannelId).isNull();
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
public void testMonochromeAppIcon_adaptiveIconExists() throws Exception {
final String pkg = "testPackage";
@@ -1333,4 +1707,855 @@
assertThat(mGroupHelper.getMonochromeAppIcon(pkg).getResId())
.isEqualTo(fallbackIconResId);
}
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testGetAggregateGroupKey() {
+ final String fullAggregateGroupKey = GroupHelper.getFullAggregateGroupKey("pkg",
+ "groupKey", 1234);
+ assertThat(fullAggregateGroupKey).isEqualTo("1234|pkg|g:groupKey");
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testNoGroup_postingUnderLimit_forcedGrouping() {
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, true);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ verifyZeroInteractions(mCallback);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testNoGroup_AutobundledAlready_forcedGrouping() {
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, null, true);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ verifyZeroInteractions(mCallback);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testNoGroup_isCanceled_forcedGrouping() {
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp" + i, true);
+ r.isCanceled = true;
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ verifyZeroInteractions(mCallback);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testNoGroup_isAggregated_forcedGrouping() {
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ String aggregateGroupKey = AGGREGATE_GROUP_KEY + "AlertingSection";
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, aggregateGroupKey, true);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ verifyZeroInteractions(mCallback);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testNoGroup_multiPackage_forcedGrouping() {
+ final String pkg = "package";
+ final String pkg2 = "package2";
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, true);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ NotificationRecord r = getNotificationRecord(pkg2, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, "testGrp", true);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ verifyZeroInteractions(mCallback);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testNoGroup_multiUser_forcedGrouping() {
+ final String pkg = "package";
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, true);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ NotificationRecord r = getNotificationRecord(pkg, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.of(7), "testGrp", true);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ verifyZeroInteractions(mCallback);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testNoGroup_summaryWithChildren_forcedGrouping() {
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, true);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ // Next posted summary has 1 child => no forced grouping
+ NotificationRecord summary = getNotificationRecord(pkg, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, "testGrp", true);
+ notificationList.add(summary);
+ NotificationRecord child = getNotificationRecord(pkg, AUTOGROUP_AT_COUNT + 1,
+ String.valueOf(AUTOGROUP_AT_COUNT + 1), UserHandle.SYSTEM, "testGrp", false);
+ notificationList.add(child);
+ mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+ verifyZeroInteractions(mCallback);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testNoGroup_groupWithSummary_forcedGrouping() {
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, true);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ // Next posted notification has summary => no forced grouping
+ NotificationRecord summary = getNotificationRecord(pkg, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, "testGrp", true);
+ notificationList.add(summary);
+ NotificationRecord child = getNotificationRecord(pkg, AUTOGROUP_AT_COUNT + 1,
+ String.valueOf(AUTOGROUP_AT_COUNT + 1), UserHandle.SYSTEM, "testGrp", false);
+ notificationList.add(child);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ verifyZeroInteractions(mCallback);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testAddAggregateSummary_summaryNoChildren() {
+ final String pkg = "package";
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ // Post group summaries without children => force autogroup
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, true);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testAddAggregateSummary_childrenNoSummary() {
+ final String pkg = "package";
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ // Post group notifications without summaries => force autogroup
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, false);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testAddAggregateSummary_multipleSections() {
+ final String pkg = "package";
+ final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ final String expectedGroupKey_silent = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ // Post notifications with different importance values => force group into separate sections
+ NotificationRecord r;
+ for (int i = 0; i < 2 * AUTOGROUP_AT_COUNT; i++) {
+ if (i % 2 == 0) {
+ r = getNotificationRecord(pkg, i, String.valueOf(i), UserHandle.SYSTEM,
+ "testGrp " + i, true, IMPORTANCE_DEFAULT);
+ } else {
+ r = getNotificationRecord(pkg, i, String.valueOf(i), UserHandle.SYSTEM,
+ "testGrp " + i, false, IMPORTANCE_LOW);
+ }
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_alerting), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ 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_alerting), eq(true));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_silent), eq(true));
+
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ }
+
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAddAggregateSummary_mixUngroupedAndAbusive_alwaysAutogroup() {
+ final String pkg = "package";
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ // Post ungrouped notifications => create autogroup
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ mGroupHelper.onNotificationPosted(
+ getNotificationRecord(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(expectedGroupKey),
+ anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), eq(expectedGroupKey),
+ anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+
+ reset(mCallback);
+
+ // Post group notifications without summaries => add to autogroup
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final int id = AUTOGROUP_AT_COUNT;
+ NotificationRecord r = getNotificationRecord(pkg, id, String.valueOf(id),
+ UserHandle.SYSTEM, "testGrp " + id, false);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+
+ // Check that the new notification was added
+ verify(mCallback, times(1)).addAutoGroup(eq(r.getKey()),
+ eq(expectedGroupKey), eq(true));
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey), any());
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testUpdateAggregateSummary_postUngroupedAfterForcedGrouping_alwaysAutogroup() {
+ final String pkg = "package";
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ // Post group notifications without summaries => force autogroup
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, false);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+
+ reset(mCallback);
+
+ // Post ungrouped notification => update autogroup
+ final int id = AUTOGROUP_AT_COUNT;
+ NotificationRecord r = getNotificationRecord(pkg, id, String.valueOf(id),
+ UserHandle.SYSTEM);
+ mGroupHelper.onNotificationPosted(r, true);
+
+ verify(mCallback, times(1)).addAutoGroup(eq(r.getKey()),
+ eq(expectedGroupKey), eq(true));
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+ public void testUpdateAggregateSummary_postUngroupedAfterForcedGrouping() {
+ final String pkg = "package";
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ // Post group notifications without summaries => force autogroup
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, false);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+
+ reset(mCallback);
+
+ // Post ungrouped notification => update autogroup
+ final int id = AUTOGROUP_AT_COUNT;
+ NotificationRecord r = getNotificationRecord(pkg, id, String.valueOf(id),
+ UserHandle.SYSTEM);
+ mGroupHelper.onNotificationPosted(r, true);
+
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testUpdateAggregateSummary_postAfterForcedGrouping() {
+ final String pkg = "package";
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ // Post group notifications w/o summaries and summaries w/o children => force autogrouping
+ NotificationRecord r;
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ if (i % 2 == 0) {
+ r = getNotificationRecord(pkg, i, String.valueOf(i), UserHandle.SYSTEM,
+ "testGrp " + i, true);
+ } else {
+ r = getNotificationRecord(pkg, i, String.valueOf(i), UserHandle.SYSTEM,
+ "testGrp " + i, false);
+ }
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+
+ // Post another notification after forced grouping
+ final Icon icon = mock(Icon.class);
+ when(icon.sameAs(icon)).thenReturn(true);
+ final int iconColor = Color.BLUE;
+ r = getNotificationRecord(
+ getSbn(pkg, AUTOGROUP_AT_COUNT, String.valueOf(AUTOGROUP_AT_COUNT),
+ UserHandle.SYSTEM, "testGrp " + AUTOGROUP_AT_COUNT, icon, iconColor));
+
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT + 1)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey), any());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testRemoveAggregateSummary_removeAllNotifications() {
+ final String pkg = "package";
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ // Post group notifications without summaries => force autogroup
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, false);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ Mockito.reset(mCallback);
+
+ // Remove all posted notifications
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, false);
+ r.setOverrideGroupKey(expectedGroupKey);
+ mGroupHelper.onNotificationRemoved(r, notificationList);
+ }
+ // Check that the autogroup summary is removed
+ verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey));
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testMoveAggregateGroups_updateChannel() {
+ final String pkg = "package";
+ final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ final NotificationChannel channel = new NotificationChannel(TEST_CHANNEL_ID,
+ TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ // Post group notifications without summaries => force autogroup
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, false, channel);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_alerting), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).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());
+ Mockito.reset(mCallback);
+
+ // Update the channel importance for all posted notifications
+ final String expectedGroupKey_silent = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+ channel.setImportance(IMPORTANCE_LOW);
+ for (NotificationRecord r: notificationList) {
+ r.updateNotificationChannel(channel);
+ }
+ mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel,
+ notificationList);
+
+ // Check that all notifications are moved to the silent section group
+ 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));
+
+ // Check that the alerting section group is removed
+ verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey_alerting));
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testMoveAggregateGroups_updateChannel_multipleChannels() {
+ final String pkg = "package";
+ final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ 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_DEFAULT);
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ // Post notifications with different channels that autogroup within the same section
+ NotificationRecord r;
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ if (i % 2 == 0) {
+ r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, false, channel1);
+ } else {
+ r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, false, channel2);
+ }
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ NotificationAttributes 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_alerting), anyInt(), eq(expectedSummaryAttr));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).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());
+ 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 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));
+
+ // Check that the alerting section group is not removed, only updated
+ expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
+ mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ "TEST_CHANNEL_ID2");
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey_alerting));
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey_alerting), eq(expectedSummaryAttr));
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testMoveAggregateGroups_updateChannel_groupsUngrouped() {
+ final String pkg = "package";
+ final String expectedGroupKey_silent = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+
+ // Post too few group notifications without summaries => do not autogroup
+ final NotificationChannel lowPrioChannel = new NotificationChannel("TEST_CHANNEL_LOW_ID",
+ "TEST_CHANNEL_LOW_ID", IMPORTANCE_LOW);
+ final int numUngrouped = AUTOGROUP_AT_COUNT - 1;
+ int startIdx = 42;
+ for (int i = startIdx; i < startIdx + numUngrouped; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, false, lowPrioChannel);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+
+ reset(mCallback);
+
+ final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ final NotificationChannel channel = new NotificationChannel(TEST_CHANNEL_ID,
+ TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+
+ // Post group notifications without summaries => force autogroup
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, "testGrp " + i, false, channel);
+ notificationList.add(r);
+ mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_alerting), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).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());
+ Mockito.reset(mCallback);
+
+ // Update the channel importance for all posted notifications
+ final int numSilentGroupNotifications = AUTOGROUP_AT_COUNT + numUngrouped;
+ channel.setImportance(IMPORTANCE_LOW);
+ for (NotificationRecord r: notificationList) {
+ r.updateNotificationChannel(channel);
+ }
+ mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel,
+ notificationList);
+
+ // Check that all notifications are moved to the silent section group
+ 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));
+
+ // Check that the alerting section group is removed
+ verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey_alerting));
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+ public void testNoGroup_singletonGroup_underLimit() {
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ // Post singleton groups, under forced group limit
+ for (int i = 0; i < AUTOGROUP_SINGLETONS_AT_COUNT - 1; 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);
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+ }
+ verifyZeroInteractions(mCallback);
+ }
+
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ @DisableFlags(Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS)
+ public void testAddAggregateSummary_singletonGroup_disableFlag() {
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ // Post singleton groups, above forced group limit
+ for (int i = 0; i < AUTOGROUP_SINGLETONS_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);
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+ }
+ // FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS is disabled => don't force group
+ verifyZeroInteractions(mCallback);
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+ public void testAddAggregateSummary_singletonGroups() {
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ // Post singleton groups, above forced group limit
+ for (int i = 0; i < AUTOGROUP_SINGLETONS_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);
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ summary.isCanceled = true; // simulate removing the app summary
+ mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+
+ }
+ // Check that notifications are forced grouped
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+
+ // Check that summaries are canceled
+ verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).removeAppProvidedSummary(anyString());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+ public void testCancelCachedSummary_singletonGroups() {
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ final int id = 0;
+ // Post singleton groups, above forced group limit
+ for (int i = 0; i < AUTOGROUP_SINGLETONS_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);
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ summary.isCanceled = true; // simulate removing the app summary
+ mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+ }
+ Mockito.reset(mCallback);
+
+ // App cancels the summary of an aggregated group
+ mGroupHelper.maybeCancelGroupChildrenForCanceledSummary(pkg, String.valueOf(id), id,
+ UserHandle.SYSTEM.getIdentifier(), REASON_APP_CANCEL);
+
+ verify(mCallback, times(1)).removeNotificationFromCanceledGroup(
+ eq(UserHandle.SYSTEM.getIdentifier()), eq(pkg), eq("testGrp " + id),
+ eq(REASON_APP_CANCEL));
+ CachedSummary cachedSummary = mGroupHelper.findCanceledSummary(pkg, String.valueOf(id), id,
+ UserHandle.SYSTEM.getIdentifier());
+ assertThat(cachedSummary).isNull();
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+ public void testRemoveCachedSummary_singletonGroups_removeChildren() {
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ final int id = 0;
+ NotificationRecord childToRemove = null;
+ // Post singleton groups, above forced group limit
+ for (int i = 0; i < AUTOGROUP_SINGLETONS_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);
+ if (i == id) {
+ childToRemove = child;
+ }
+ notificationList.add(child);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ summary.isCanceled = true; // simulate removing the app summary
+ mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+ }
+ // override group key for child notifications
+ List<NotificationRecord> notificationListAfterGrouping = new ArrayList<>(
+ notificationList.stream().filter(r -> {
+ if (r.getSbn().getNotification().isGroupChild()) {
+ r.setOverrideGroupKey(expectedGroupKey);
+ return true;
+ } else {
+ return false;
+ }
+ }).toList());
+ summaryByGroup.clear();
+ Mockito.reset(mCallback);
+
+ //Cancel child 0 => remove cached summary
+ childToRemove.isCanceled = true;
+ notificationListAfterGrouping.remove(childToRemove);
+ mGroupHelper.onNotificationRemoved(childToRemove, notificationListAfterGrouping);
+ CachedSummary cachedSummary = mGroupHelper.findCanceledSummary(pkg, String.valueOf(id), id,
+ UserHandle.SYSTEM.getIdentifier());
+ assertThat(cachedSummary).isNull();
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testGroupSectioners() {
+ final NotificationRecord notification_alerting = getNotificationRecord(mPkg, 0, "", mUser,
+ "", false, IMPORTANCE_DEFAULT);
+ assertThat(GroupHelper.getSection(notification_alerting).mName).isEqualTo("AlertingSection");
+
+ final NotificationRecord notification_silent = getNotificationRecord(mPkg, 0, "", mUser,
+ "", false, IMPORTANCE_LOW);
+ assertThat(GroupHelper.getSection(notification_silent).mName).isEqualTo("SilentSection");
+
+ NotificationRecord notification_conversation = mock(NotificationRecord.class);
+ when(notification_conversation.isConversation()).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification_conversation)).isNull();
+
+ NotificationRecord notification_call = spy(getNotificationRecord(mPkg, 0, "", mUser,
+ "", false, IMPORTANCE_LOW));
+ Notification n = mock(Notification.class);
+ StatusBarNotification sbn = spy(getSbn("package", 0, "0", UserHandle.SYSTEM));
+ when(notification_call.isConversation()).thenReturn(false);
+ when(notification_call.getNotification()).thenReturn(n);
+ when(notification_call.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isStyle(Notification.CallStyle.class)).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification_call)).isNull();
+
+ NotificationRecord notification_colorFg = spy(getNotificationRecord(mPkg, 0, "", mUser,
+ "", false, IMPORTANCE_LOW));
+ sbn = spy(getSbn("package", 0, "0", UserHandle.SYSTEM));
+ n = mock(Notification.class);
+ when(notification_colorFg.isConversation()).thenReturn(false);
+ when(notification_colorFg.getNotification()).thenReturn(n);
+ when(notification_colorFg.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isForegroundService()).thenReturn(true);
+ when(n.isColorized()).thenReturn(true);
+ when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
+ assertThat(GroupHelper.getSection(notification_colorFg)).isNull();
+ }
+
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index c48d745..5d306e1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -39,8 +39,10 @@
import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
import static android.app.Notification.FLAG_USER_INITIATED_JOB;
+import static android.app.Notification.GROUP_ALERT_CHILDREN;
import static android.app.Notification.VISIBILITY_PRIVATE;
import static android.app.NotificationChannel.NEWS_ID;
+import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
@@ -98,11 +100,13 @@
import static android.service.notification.Condition.SOURCE_CONTEXT;
import static android.service.notification.Condition.SOURCE_USER_ACTION;
import static android.service.notification.Condition.STATE_TRUE;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING;
import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_LOCKDOWN;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
@@ -117,6 +121,7 @@
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
import static com.android.server.notification.Flags.FLAG_REJECT_OLD_NOTIFICATIONS;
+import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY;
import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION;
import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
import static com.android.server.notification.NotificationManagerService.NOTIFICATION_TTL;
@@ -369,6 +374,7 @@
private static final int TOAST_DURATION = 2_000;
private static final int SECONDARY_DISPLAY_ID = 42;
private static final int TEST_PROFILE_USERHANDLE = 12;
+ private static final long DELAY_FORCE_REGROUP_TIME = 3000;
private static final String ACTION_NOTIFICATION_TIMEOUT =
NotificationManagerService.class.getSimpleName() + ".TIMEOUT";
@@ -2487,43 +2493,372 @@
}
@Test
+ @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testAutobundledSummary_notificationAdded() {
NotificationRecord summary =
- generateNotificationRecord(mTestNotificationChannel, 0, "pkg", true);
+ generateNotificationRecord(mTestNotificationChannel, 0, AUTOGROUP_KEY, true);
summary.getNotification().flags |= Notification.FLAG_AUTOGROUP_SUMMARY;
mService.addNotification(summary);
mService.mSummaryByGroupKey.put("pkg", summary);
mService.mAutobundledSummaries.put(0, new ArrayMap<>());
mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
- mService.updateAutobundledSummaryLocked(0, "pkg",
+ mService.updateAutobundledSummaryLocked(0, "pkg", AUTOGROUP_KEY,
new NotificationAttributes(GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT,
- mock(Icon.class), 0, VISIBILITY_PRIVATE), false);
+ mock(Icon.class), 0,
+ VISIBILITY_PRIVATE, GROUP_ALERT_CHILDREN, DEFAULT_CHANNEL_ID), false);
waitForIdle();
assertTrue(summary.getSbn().isOngoing());
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testAutobundledSummary_notificationAdded_forcedGrouping() {
+ NotificationRecord summary =
+ generateNotificationRecord(mTestNotificationChannel, 0, AUTOGROUP_KEY, true);
+ summary.getNotification().flags |= Notification.FLAG_AUTOGROUP_SUMMARY;
+ mService.addNotification(summary);
+ mService.mSummaryByGroupKey.put("pkg", summary);
+ mService.mAutobundledSummaries.put(0, new ArrayMap<>());
+ mService.mAutobundledSummaries.get(0).put(summary.getGroupKey(), summary.getKey());
+
+ mService.updateAutobundledSummaryLocked(0, "pkg", summary.getGroupKey(),
+ new NotificationAttributes(GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT,
+ mock(Icon.class), 0,
+ VISIBILITY_PRIVATE, GROUP_ALERT_CHILDREN, DEFAULT_CHANNEL_ID), false);
+ waitForIdle();
+
+ assertTrue(summary.getSbn().isOngoing());
+ }
+
+ @Test
+ @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testAutobundledSummary_notificationRemoved() {
NotificationRecord summary =
- generateNotificationRecord(mTestNotificationChannel, 0, "pkg", true);
+ generateNotificationRecord(mTestNotificationChannel, 0, AUTOGROUP_KEY, true);
summary.getNotification().flags |= Notification.FLAG_AUTOGROUP_SUMMARY;
summary.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
mService.addNotification(summary);
mService.mAutobundledSummaries.put(0, new ArrayMap<>());
mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
- mService.mSummaryByGroupKey.put("pkg", summary);
+ mService.mSummaryByGroupKey.put(summary.getGroupKey(), summary);
- mService.updateAutobundledSummaryLocked(0, "pkg",
+ mService.updateAutobundledSummaryLocked(0, "pkg", AUTOGROUP_KEY,
new NotificationAttributes(GroupHelper.BASE_FLAGS,
- mock(Icon.class), 0, VISIBILITY_PRIVATE), false);
+ mock(Icon.class), 0,
+ VISIBILITY_PRIVATE, GROUP_ALERT_CHILDREN, DEFAULT_CHANNEL_ID), false);
waitForIdle();
assertFalse(summary.getSbn().isOngoing());
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testAutobundledSummary_notificationRemoved_forceGrouping() {
+ NotificationRecord summary =
+ generateNotificationRecord(mTestNotificationChannel, 0, AUTOGROUP_KEY, true);
+ summary.getNotification().flags |= Notification.FLAG_AUTOGROUP_SUMMARY;
+ summary.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
+ mService.addNotification(summary);
+ mService.mAutobundledSummaries.put(0, new ArrayMap<>());
+ mService.mAutobundledSummaries.get(0).put(summary.getGroupKey(), summary.getKey());
+
+ mService.updateAutobundledSummaryLocked(0, "pkg", summary.getGroupKey(),
+ new NotificationAttributes(GroupHelper.BASE_FLAGS,
+ mock(Icon.class), 0,
+ VISIBILITY_PRIVATE, GROUP_ALERT_CHILDREN, DEFAULT_CHANNEL_ID), false);
+ waitForIdle();
+
+ assertFalse(summary.getSbn().isOngoing());
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testAggregatedSummary_updateSummaryAttributes() {
+ final String aggregateGroupName = "Aggregate_Test";
+ final String newChannelId = "newChannelId";
+ final NotificationChannel newChannel = new NotificationChannel(
+ newChannelId, newChannelId, IMPORTANCE_DEFAULT);
+ mService.setPreferencesHelper(mPreferencesHelper);
+ final NotificationRecord summary =
+ generateNotificationRecord(mTestNotificationChannel, 0, aggregateGroupName, true);
+ final String groupKey = summary.getGroupKey();
+ summary.getNotification().flags |= Notification.FLAG_AUTOGROUP_SUMMARY;
+ mService.addNotification(summary);
+ mService.mAutobundledSummaries.put(0, new ArrayMap<>());
+ mService.mAutobundledSummaries.get(0).put(groupKey, summary.getKey());
+ when(mPreferencesHelper.getNotificationChannel(eq("pkg"), anyInt(),
+ eq(newChannelId), anyBoolean())).thenReturn(newChannel);
+
+ mService.updateAutobundledSummaryLocked(0, "pkg", groupKey,
+ new NotificationAttributes(GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT,
+ mock(Icon.class), 0, VISIBILITY_PRIVATE, GROUP_ALERT_CHILDREN, newChannelId),
+ false);
+ waitForIdle();
+
+ assertTrue(summary.getSbn().isOngoing());
+ assertThat(summary.getNotification().getGroupAlertBehavior()).isEqualTo(
+ GROUP_ALERT_CHILDREN);
+
+ assertThat(summary.getChannel().getId()).isEqualTo(newChannelId);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testAddAggregateNotification_notifyPostedLocked() throws Exception {
+ final String originalGroupName = "originalGroup";
+ final NotificationRecord r =
+ generateNotificationRecord(mTestNotificationChannel, 0, originalGroupName, false);
+ mService.addNotification(r);
+ mService.addAutogroupKeyLocked(r.getKey(), "grpKey", true);
+
+ assertThat(r.getSbn().getOverrideGroupKey()).isEqualTo("grpKey");
+ verify(mRankingHandler, times(1)).requestSort();
+ verify(mListeners, times(1)).notifyPostedLocked(eq(r), eq(r));
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testAddAggregateSummaryNotification_convertSummary() throws Exception {
+ final String originalGroupName = "originalGroup";
+ final NotificationRecord r =
+ generateNotificationRecord(mTestNotificationChannel, 0, originalGroupName, true);
+ final String groupKey = r.getGroupKey();
+ mService.addNotification(r);
+ assertThat(mService.mSummaryByGroupKey.containsKey(groupKey)).isTrue();
+ boolean isConverted = mService.convertSummaryToNotificationLocked(r.getKey());
+
+ assertThat(isConverted).isTrue();
+ assertThat(r.getSbn().isGroup()).isTrue();
+ assertThat(r.getNotification().isGroupSummary()).isFalse();
+ assertThat(mService.mSummaryByGroupKey.containsKey(groupKey)).isFalse();
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+ public void testAggregateGroups_RemoveAppSummary() throws Exception {
+ final String originalGroupName = "originalGroup";
+ final NotificationRecord r =
+ generateNotificationRecord(mTestNotificationChannel, 0, originalGroupName, true);
+ mService.addNotification(r);
+ mService.removeAppSummaryLocked(r.getKey());
+
+ assertThat(r.isCanceled).isTrue();
+ waitForIdle();
+ verify(mWorkerHandler, times(1)).scheduleCancelNotification(any(), eq(0));
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testUngroupingAggregateSummary() 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 both children
+ mBinderService.cancelNotificationWithTag(mPkg, mPkg, nr0.getSbn().getTag(),
+ nr0.getSbn().getId(), nr0.getSbn().getUserId());
+ mBinderService.cancelNotificationWithTag(mPkg, mPkg, nr1.getSbn().getTag(),
+ nr1.getSbn().getId(), nr1.getSbn().getUserId());
+ waitForIdle();
+
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any());
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any());
+
+ // GroupHelper would send 'remove summary' event
+ mService.clearAutogroupSummaryLocked(nr1.getUserId(), nr1.getSbn().getPackageName(),
+ fullAggregateGroupKey);
+ waitForIdle();
+
+ // Make sure the summary was removed and not re-posted
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+ public void testCancelGroupChildrenForCanceledSummary_singletonGroup() throws Exception {
+ final String originalGroupName = "originalGroup";
+ final String aggregateGroupName = "Aggregate_Test";
+ final int summaryId = Integer.MAX_VALUE;
+ // Add a "singleton group"
+ NotificationRecord nr0 =
+ generateNotificationRecord(mTestNotificationChannel, 0, originalGroupName, false);
+ NotificationRecord nr1 =
+ generateNotificationRecord(mTestNotificationChannel, 1, originalGroupName, false);
+ final NotificationRecord summary =
+ generateNotificationRecord(mTestNotificationChannel, 2, originalGroupName, true);
+ final String originalGroupKey = summary.getGroupKey();
+ mService.addNotification(nr0);
+ mService.addNotification(nr1);
+ mService.addNotification(summary);
+
+ // GroupHelper is a mock, so make the calls it would make
+ // Remove the app's summary notification
+ mService.removeAppSummaryLocked(summary.getKey());
+ waitForIdle();
+
+ // 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();
+
+ assertThat(aggregateSummary.getNotification().getGroup()).isEqualTo(aggregateGroupName);
+ assertThat(aggregateSummary.getNotification().getChannelId()).isEqualTo(
+ nr0.getChannel().getId());
+ assertThat(mService.mSummaryByGroupKey.containsKey(fullAggregateGroupKey)).isTrue();
+ assertThat(mService.mSummaryByGroupKey.containsKey(originalGroupKey)).isFalse();
+
+ // Cancel the original app summary (is already removed)
+ mBinderService.cancelNotificationWithTag(summary.getSbn().getPackageName(),
+ summary.getSbn().getPackageName(), summary.getSbn().getTag(),
+ summary.getSbn().getId(), summary.getSbn().getUserId());
+ waitForIdle();
+
+ // Check if NMS.CancelNotificationRunnable calls maybeCancelGroupChildrenForCanceledSummary
+ verify(mGroupHelper, times(1)).maybeCancelGroupChildrenForCanceledSummary(
+ eq(summary.getSbn().getPackageName()), eq(summary.getSbn().getTag()),
+ eq(summary.getSbn().getId()), eq(summary.getSbn().getUserId()),
+ eq(REASON_APP_CANCEL));
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testUpdateChannel_notifyGroupHelper() throws Exception {
+ mService.setPreferencesHelper(mPreferencesHelper);
+ mTestNotificationChannel.setLightColor(Color.CYAN);
+ when(mPreferencesHelper.getNotificationChannel(eq(mPkg), anyInt(),
+ eq(mTestNotificationChannel.getId()), anyBoolean()))
+ .thenReturn(mTestNotificationChannel);
+
+ mBinderService.updateNotificationChannelForPackage(mPkg, mUid, mTestNotificationChannel);
+ mTestableLooper.moveTimeForward(DELAY_FORCE_REGROUP_TIME);
+ waitForIdle();
+
+ verify(mGroupHelper, times(1)).onChannelUpdated(eq(Process.myUserHandle().getIdentifier()),
+ eq(mPkg), eq(mTestNotificationChannel), any());
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testSnoozeRunnable_snoozeAggregateGroupChild_summaryNotSnoozed() throws Exception {
+ final String aggregateGroupName = "Aggregate_Test";
+
+ // build autogroup summary notification
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setGroup(aggregateGroupName)
+ .setGroupSummary(true)
+ .setFlag(Notification.FLAG_AUTOGROUP_SUMMARY, true);
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
+ "tag" + System.currentTimeMillis(), mUid, 0, nb.build(),
+ UserHandle.getUserHandleForUid(mUid), null, 0);
+ final NotificationRecord summary = new NotificationRecord(mContext, sbn,
+ mTestNotificationChannel);
+
+ final NotificationRecord child = generateNotificationRecord(
+ mTestNotificationChannel, 2, aggregateGroupName, false);
+ mService.addNotification(summary);
+ mService.addNotification(child);
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
+
+ // snooze child only
+ NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
+ mService.new SnoozeNotificationRunnable(
+ child.getKey(), 100, null);
+ snoozeNotificationRunnable.run();
+
+ // only child should be snoozed
+ verify(mSnoozeHelper, times(1)).snooze(any(NotificationRecord.class), anyLong());
+
+ // both group summary and child should be cancelled
+ assertNull(mService.getNotificationRecord(summary.getKey()));
+ assertNull(mService.getNotificationRecord(child.getKey()));
+
+ assertEquals(4, mNotificationRecordLogger.numCalls());
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SNOOZED,
+ mNotificationRecordLogger.event(0));
+ assertEquals(
+ NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_SNOOZED,
+ mNotificationRecordLogger.event(1));
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testOnlyForceGroupIfNeeded_newNotification_notAutogrouped() {
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
+ when(mGroupHelper.onNotificationPosted(any(), anyBoolean())).thenReturn(false);
+ mService.addEnqueuedNotification(r);
+ NotificationManagerService.PostNotificationRunnable runnable =
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
+ runnable.run();
+ waitForIdle();
+
+ mTestableLooper.moveTimeForward(DELAY_FORCE_REGROUP_TIME);
+ waitForIdle();
+
+ verify(mGroupHelper, times(1)).onNotificationPosted(any(), anyBoolean());
+ verify(mGroupHelper, times(1)).onNotificationPostedWithDelay(eq(r), any(), any());
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testOnlyForceGroupIfNeeded_newNotification_wasAutogrouped() {
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
+ when(mGroupHelper.onNotificationPosted(any(), anyBoolean())).thenReturn(true);
+ mService.addEnqueuedNotification(r);
+ NotificationManagerService.PostNotificationRunnable runnable =
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
+ runnable.run();
+ waitForIdle();
+
+ mTestableLooper.moveTimeForward(DELAY_FORCE_REGROUP_TIME);
+ waitForIdle();
+
+ verify(mGroupHelper, times(1)).onNotificationPosted(any(), anyBoolean());
+ verify(mGroupHelper, never()).onNotificationPostedWithDelay(eq(r), any(), any());
+ }
+
+ @Test
public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
when(mAmi.applyForegroundServiceNotification(
any(), anyString(), anyInt(), anyString(), anyInt())).thenReturn(SHOW_IMMEDIATELY);
@@ -3653,9 +3988,11 @@
when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
when(mPermissionHelper.isPermissionFixed(mPkg, temp.getUserId())).thenReturn(true);
+ NotificationAttributes attr = new NotificationAttributes(GroupHelper.BASE_FLAGS,
+ mock(Icon.class), 0, VISIBILITY_PRIVATE, GROUP_ALERT_CHILDREN, DEFAULT_CHANNEL_ID);
+
NotificationRecord r = mService.createAutoGroupSummary(temp.getUserId(),
- temp.getSbn().getPackageName(), temp.getKey(), 0, mock(Icon.class), 0,
- VISIBILITY_PRIVATE);
+ temp.getSbn().getPackageName(), temp.getKey(), AUTOGROUP_KEY, Integer.MAX_VALUE, attr);
assertThat(r.isImportanceFixed()).isTrue();
}
@@ -4796,6 +5133,7 @@
}
@Test
+ @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testSnoozeRunnable_snoozeAutoGroupChild_summaryNotSnoozed() throws Exception {
final NotificationRecord parent = generateNotificationRecord(
mTestNotificationChannel, 1, GroupHelper.AUTOGROUP_KEY, true);
@@ -5659,7 +5997,7 @@
public void testAddAutogroup_requestsSort() throws Exception {
final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addNotification(r);
- mService.addAutogroupKeyLocked(r.getKey(), true);
+ mService.addAutogroupKeyLocked(r.getKey(), "grpKey", true);
verify(mRankingHandler, times(1)).requestSort();
}
@@ -5679,7 +6017,7 @@
final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
r.setOverrideGroupKey("TEST");
mService.addNotification(r);
- mService.addAutogroupKeyLocked(r.getKey(), true);
+ mService.addAutogroupKeyLocked(r.getKey(), "grpName", true);
verify(mRankingHandler, never()).requestSort();
}
@@ -5689,7 +6027,7 @@
public void testAutogroupSuppressSort_noSort() throws Exception {
final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addNotification(r);
- mService.addAutogroupKeyLocked(r.getKey(), false);
+ mService.addAutogroupKeyLocked(r.getKey(), "grpName", false);
verify(mRankingHandler, never()).requestSort();
}
@@ -12688,6 +13026,7 @@
}
@Test
+ @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testUngroupingOngoingAutoSummary() throws Exception {
NotificationRecord nr0 =
generateNotificationRecord(mTestNotificationChannel, 0);
@@ -12701,10 +13040,12 @@
// grouphelper is a mock here, so make the calls it would make
// add summary
+ NotificationAttributes attr = new NotificationAttributes(
+ GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, mock(Icon.class), 0,
+ VISIBILITY_PRIVATE, GROUP_ALERT_CHILDREN, DEFAULT_CHANNEL_ID);
mService.addNotification(
mService.createAutoGroupSummary(nr1.getUserId(), nr1.getSbn().getPackageName(),
- nr1.getKey(), GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, mock(Icon.class), 0,
- VISIBILITY_PRIVATE));
+ nr1.getKey(), AUTOGROUP_KEY, Integer.MAX_VALUE, attr));
// cancel both children
mBinderService.cancelNotificationWithTag(mPkg, mPkg, nr0.getSbn().getTag(),
@@ -12714,7 +13055,8 @@
waitForIdle();
// group helper would send 'remove summary' event
- mService.clearAutogroupSummaryLocked(nr1.getUserId(), nr1.getSbn().getPackageName());
+ mService.clearAutogroupSummaryLocked(nr1.getUserId(), nr1.getSbn().getPackageName(),
+ AUTOGROUP_KEY);
waitForIdle();
// make sure the summary was removed and not re-posted
@@ -12722,6 +13064,45 @@
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testUngroupingOngoingAutoSummary_forceGrouping() throws Exception {
+ NotificationRecord nr0 =
+ generateNotificationRecord(mTestNotificationChannel, 0);
+ NotificationRecord nr1 =
+ generateNotificationRecord(mTestNotificationChannel, 0);
+ nr1.getSbn().getNotification().flags |= FLAG_ONGOING_EVENT;
+
+ mService.addNotification(nr0);
+ mService.addNotification(nr1);
+
+ // grouphelper is a mock here, so make the calls it would make
+
+ // add summary
+ NotificationAttributes attr = new NotificationAttributes(
+ GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, mock(Icon.class), 0,
+ VISIBILITY_PRIVATE, GROUP_ALERT_CHILDREN, DEFAULT_CHANNEL_ID);
+ mService.addNotification(
+ mService.createAutoGroupSummary(nr1.getUserId(), nr1.getSbn().getPackageName(),
+ nr1.getKey(), AUTOGROUP_KEY, Integer.MAX_VALUE, attr));
+
+ // cancel both children
+ mBinderService.cancelNotificationWithTag(mPkg, mPkg, nr0.getSbn().getTag(),
+ nr0.getSbn().getId(), nr0.getSbn().getUserId());
+ mBinderService.cancelNotificationWithTag(mPkg, mPkg, nr1.getSbn().getTag(),
+ nr1.getSbn().getId(), nr1.getSbn().getUserId());
+ waitForIdle();
+
+ // group helper would send 'remove summary' event
+ mService.clearAutogroupSummaryLocked(nr1.getUserId(), nr1.getSbn().getPackageName(),
+ AUTOGROUP_KEY);
+ waitForIdle();
+
+ // make sure the summary was removed and not re-posted
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
+ }
+
+ @Test
+ @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testUngroupingAutoSummary_differentUsers() throws Exception {
NotificationRecord nr0 =
generateNotificationRecord(mTestNotificationChannel, 0, USER_SYSTEM);
@@ -12729,11 +13110,14 @@
generateNotificationRecord(mTestNotificationChannel, 1, USER_SYSTEM);
// add notifications + summary for USER_SYSTEM
+ NotificationAttributes attr = new NotificationAttributes(
+ GroupHelper.BASE_FLAGS, mock(Icon.class), 0,
+ VISIBILITY_PRIVATE, GROUP_ALERT_CHILDREN, DEFAULT_CHANNEL_ID);
mService.addNotification(nr0);
mService.addNotification(nr1);
mService.addNotification(
mService.createAutoGroupSummary(nr1.getUserId(), nr1.getSbn().getPackageName(),
- nr1.getKey(), GroupHelper.BASE_FLAGS, mock(Icon.class), 0, VISIBILITY_PRIVATE));
+ nr1.getKey(), AUTOGROUP_KEY, Integer.MAX_VALUE, attr));
// add notifications + summary for USER_ALL
NotificationRecord nr0_all =
@@ -12746,7 +13130,7 @@
mService.addNotification(
mService.createAutoGroupSummary(nr0_all.getUserId(),
nr0_all.getSbn().getPackageName(),
- nr0_all.getKey(), GroupHelper.BASE_FLAGS, mock(Icon.class), 0, VISIBILITY_PRIVATE));
+ nr0_all.getKey(), AUTOGROUP_KEY, Integer.MAX_VALUE, attr));
// cancel both children for USER_ALL
mBinderService.cancelNotificationWithTag(mPkg, mPkg, nr0_all.getSbn().getTag(),
@@ -12757,7 +13141,7 @@
// group helper would send 'remove summary' event
mService.clearAutogroupSummaryLocked(UserHandle.USER_ALL,
- nr0_all.getSbn().getPackageName());
+ nr0_all.getSbn().getPackageName(), AUTOGROUP_KEY);
waitForIdle();
// make sure the right summary was removed
@@ -12770,6 +13154,58 @@
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testUngroupingAutoSummary_differentUsers_forceGrouping() throws Exception {
+ NotificationRecord nr0 =
+ generateNotificationRecord(mTestNotificationChannel, 0, USER_SYSTEM);
+ NotificationRecord nr1 =
+ generateNotificationRecord(mTestNotificationChannel, 1, USER_SYSTEM);
+
+ // add notifications + summary for USER_SYSTEM
+ NotificationAttributes attr = new NotificationAttributes(
+ GroupHelper.BASE_FLAGS, mock(Icon.class), 0,
+ VISIBILITY_PRIVATE, GROUP_ALERT_CHILDREN, DEFAULT_CHANNEL_ID);
+ mService.addNotification(nr0);
+ mService.addNotification(nr1);
+ mService.addNotification(
+ mService.createAutoGroupSummary(nr1.getUserId(), nr1.getSbn().getPackageName(),
+ nr1.getKey(), AUTOGROUP_KEY, Integer.MAX_VALUE, attr));
+
+ // add notifications + summary for USER_ALL
+ NotificationRecord nr0_all =
+ generateNotificationRecord(mTestNotificationChannel, 2, UserHandle.USER_ALL);
+ NotificationRecord nr1_all =
+ generateNotificationRecord(mTestNotificationChannel, 3, UserHandle.USER_ALL);
+
+ mService.addNotification(nr0_all);
+ mService.addNotification(nr1_all);
+ mService.addNotification(
+ mService.createAutoGroupSummary(nr0_all.getUserId(),
+ nr0_all.getSbn().getPackageName(),
+ nr0_all.getKey(), AUTOGROUP_KEY, Integer.MAX_VALUE, attr));
+
+ // cancel both children for USER_ALL
+ mBinderService.cancelNotificationWithTag(mPkg, mPkg, nr0_all.getSbn().getTag(),
+ nr0_all.getSbn().getId(), UserHandle.USER_ALL);
+ mBinderService.cancelNotificationWithTag(mPkg, mPkg, nr1_all.getSbn().getTag(),
+ nr1_all.getSbn().getId(), UserHandle.USER_ALL);
+ waitForIdle();
+
+ // group helper would send 'remove summary' event
+ mService.clearAutogroupSummaryLocked(UserHandle.USER_ALL,
+ nr0_all.getSbn().getPackageName(), AUTOGROUP_KEY);
+ waitForIdle();
+
+ // make sure the right summary was removed
+ assertThat(mService.getNotificationCount(nr0_all.getSbn().getPackageName(),
+ UserHandle.USER_ALL, 0, null)).isEqualTo(0);
+
+ // the USER_SYSTEM notifications + summary were not removed
+ assertThat(mService.getNotificationCount(nr0.getSbn().getPackageName(),
+ USER_SYSTEM, 0, null)).isEqualTo(3);
+ }
+
+ @Test
public void testStrongAuthTracker_isInLockDownMode() {
mStrongAuthTracker.setGetStrongAuthForUserReturnValue(
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);