summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java181
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java161
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java124
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java283
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java15
10 files changed, 661 insertions, 159 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index ec3285f2b241..6d4b13ccf494 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -44,12 +44,16 @@ import static java.util.Objects.requireNonNull;
import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.Notification;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
+import android.util.Pair;
import androidx.annotation.NonNull;
@@ -191,59 +195,121 @@ public class NotifCollection implements Dumpable {
}
/**
- * Dismiss a notification on behalf of the user.
+ * Dismisses multiple notifications on behalf of the user.
*/
- public void dismissNotification(NotificationEntry entry, @NonNull DismissedByUserStats stats) {
+ public void dismissNotifications(
+ List<Pair<NotificationEntry, DismissedByUserStats>> entriesToDismiss) {
Assert.isMainThread();
- requireNonNull(stats);
checkForReentrantCall();
- if (entry != mNotificationSet.get(entry.getKey())) {
- throw new IllegalStateException("Invalid entry: " + entry.getKey());
- }
+ final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>();
+ for (int i = 0; i < entriesToDismiss.size(); i++) {
+ NotificationEntry entry = entriesToDismiss.get(i).first;
+ DismissedByUserStats stats = entriesToDismiss.get(i).second;
- if (entry.getDismissState() == DISMISSED) {
- return;
- }
+ requireNonNull(stats);
+ if (entry != mNotificationSet.get(entry.getKey())) {
+ throw new IllegalStateException("Invalid entry: " + entry.getKey());
+ }
- updateDismissInterceptors(entry);
- if (isDismissIntercepted(entry)) {
- mLogger.logNotifDismissedIntercepted(entry.getKey());
- return;
+ if (entry.getDismissState() == DISMISSED) {
+ continue;
+ }
+
+ updateDismissInterceptors(entry);
+ if (isDismissIntercepted(entry)) {
+ mLogger.logNotifDismissedIntercepted(entry.getKey());
+ continue;
+ }
+
+ entriesToLocallyDismiss.add(entry);
+ if (!isCanceled(entry)) {
+ // send message to system server if this notification hasn't already been cancelled
+ try {
+ mStatusBarService.onNotificationClear(
+ entry.getSbn().getPackageName(),
+ entry.getSbn().getTag(),
+ entry.getSbn().getId(),
+ entry.getSbn().getUser().getIdentifier(),
+ entry.getSbn().getKey(),
+ stats.dismissalSurface,
+ stats.dismissalSentiment,
+ stats.notificationVisibility);
+ } catch (RemoteException e) {
+ // system process is dead if we're here.
+ }
+ }
}
- // Optimistically mark the notification as dismissed -- we'll wait for the signal from
- // system server before removing it from our notification set.
- entry.setDismissState(DISMISSED);
- mLogger.logNotifDismissed(entry.getKey());
+ locallyDismissNotifications(entriesToLocallyDismiss);
+ rebuildList();
+ }
- List<NotificationEntry> canceledEntries = new ArrayList<>();
+ /**
+ * Dismisses a single notification on behalf of the user.
+ */
+ public void dismissNotification(
+ NotificationEntry entry,
+ @NonNull DismissedByUserStats stats) {
+ dismissNotifications(List.of(
+ new Pair<NotificationEntry, DismissedByUserStats>(entry, stats)));
+ }
- if (isCanceled(entry)) {
- canceledEntries.add(entry);
- } else {
- // Ask system server to remove it for us
- try {
- mStatusBarService.onNotificationClear(
- entry.getSbn().getPackageName(),
- entry.getSbn().getTag(),
- entry.getSbn().getId(),
- entry.getSbn().getUser().getIdentifier(),
- entry.getSbn().getKey(),
- stats.dismissalSurface,
- stats.dismissalSentiment,
- stats.notificationVisibility);
- } catch (RemoteException e) {
- // system process is dead if we're here.
+ /**
+ * Dismisses all clearable notifications for a given userid on behalf of the user.
+ */
+ public void dismissAllNotifications(@UserIdInt int userId) {
+ Assert.isMainThread();
+ checkForReentrantCall();
+
+ try {
+ mStatusBarService.onClearAllNotifications(userId);
+ } catch (RemoteException e) {
+ // system process is dead if we're here.
+ }
+
+ final List<NotificationEntry> entries = new ArrayList(getActiveNotifs());
+ for (int i = entries.size() - 1; i >= 0; i--) {
+ NotificationEntry entry = entries.get(i);
+ if (!shouldDismissOnClearAll(entry, userId)) {
+ // system server won't be removing these notifications, but we still give dismiss
+ // interceptors the chance to filter the notification
+ updateDismissInterceptors(entry);
+ if (isDismissIntercepted(entry)) {
+ mLogger.logNotifClearAllDismissalIntercepted(entry.getKey());
+ }
+ entries.remove(i);
}
+ }
- // Also mark any children as dismissed as system server will auto-dismiss them as well
- if (entry.getSbn().getNotification().isGroupSummary()) {
- for (NotificationEntry otherEntry : mNotificationSet.values()) {
- if (shouldAutoDismiss(otherEntry, entry.getSbn().getGroupKey())) {
- otherEntry.setDismissState(PARENT_DISMISSED);
- if (isCanceled(otherEntry)) {
- canceledEntries.add(otherEntry);
+ locallyDismissNotifications(entries);
+ rebuildList();
+ }
+
+ /**
+ * Optimistically marks the given notifications as dismissed -- we'll wait for the signal
+ * from system server before removing it from our notification set.
+ */
+ private void locallyDismissNotifications(List<NotificationEntry> entries) {
+ final List<NotificationEntry> canceledEntries = new ArrayList<>();
+
+ for (int i = 0; i < entries.size(); i++) {
+ NotificationEntry entry = entries.get(i);
+
+ entry.setDismissState(DISMISSED);
+ mLogger.logNotifDismissed(entry.getKey());
+
+ if (isCanceled(entry)) {
+ canceledEntries.add(entry);
+ } else {
+ // Mark any children as dismissed as system server will auto-dismiss them as well
+ if (entry.getSbn().getNotification().isGroupSummary()) {
+ for (NotificationEntry otherEntry : mNotificationSet.values()) {
+ if (shouldAutoDismissChildren(otherEntry, entry.getSbn().getGroupKey())) {
+ otherEntry.setDismissState(PARENT_DISMISSED);
+ if (isCanceled(otherEntry)) {
+ canceledEntries.add(otherEntry);
+ }
}
}
}
@@ -255,7 +321,6 @@ public class NotifCollection implements Dumpable {
for (NotificationEntry canceledEntry : canceledEntries) {
tryRemoveNotification(canceledEntry);
}
- rebuildList();
}
private void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
@@ -552,7 +617,7 @@ public class NotifCollection implements Dumpable {
*
* See NotificationManager.cancelGroupChildrenByListLocked() for corresponding code.
*/
- private static boolean shouldAutoDismiss(
+ private static boolean shouldAutoDismissChildren(
NotificationEntry entry,
String dismissedGroupKey) {
return entry.getSbn().getGroupKey().equals(dismissedGroupKey)
@@ -562,10 +627,39 @@ public class NotifCollection implements Dumpable {
&& entry.getDismissState() != DISMISSED;
}
+ /**
+ * When the user 'clears all notifications' through SystemUI, NotificationManager will not
+ * dismiss unclearable notifications.
+ * @return true if we think NotificationManager will dismiss the entry when asked to
+ * cancel this notification with {@link NotificationListenerService#REASON_CANCEL_ALL}
+ *
+ * See NotificationManager.cancelAllLocked for corresponding code.
+ */
+ private static boolean shouldDismissOnClearAll(
+ NotificationEntry entry,
+ @UserIdInt int userId) {
+ // TODO: (b/149396544) add FLAG_BUBBLE check here + in NoManService
+ return userIdMatches(entry, userId)
+ && entry.isClearable()
+ && entry.getDismissState() != DISMISSED;
+ }
+
private static boolean hasFlag(NotificationEntry entry, int flag) {
return (entry.getSbn().getNotification().flags & flag) != 0;
}
+ /**
+ * Determine whether the userId applies to the notification in question, either because
+ * they match exactly, or one of them is USER_ALL (which is treated as a wildcard).
+ *
+ * See NotificationManager#notificationMatchesUserId
+ */
+ private static boolean userIdMatches(NotificationEntry entry, int userId) {
+ return userId == UserHandle.USER_ALL
+ || entry.getSbn().getUser().getIdentifier() == UserHandle.USER_ALL
+ || entry.getSbn().getUser().getIdentifier() == userId;
+ }
+
private void dispatchOnEntryInit(NotificationEntry entry) {
mAmDispatchingToOtherCode = true;
for (NotifCollectionListener listener : mNotifCollectionListeners) {
@@ -613,6 +707,7 @@ public class NotifCollection implements Dumpable {
}
mAmDispatchingToOtherCode = false;
}
+
@Override
public void dump(@NonNull FileDescriptor fd, PrintWriter pw, @NonNull String[] args) {
final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index 83f56cc1e83d..1f6413b525cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -44,6 +44,7 @@ public class NotifInflaterImpl implements NotifInflater {
private final IStatusBarService mStatusBarService;
private final NotifCollection mNotifCollection;
private final NotifInflationErrorManager mNotifErrorManager;
+ private final NotifPipeline mNotifPipeline;
private NotificationRowBinderImpl mNotificationRowBinder;
private InflationCallback mExternalInflationCallback;
@@ -52,10 +53,12 @@ public class NotifInflaterImpl implements NotifInflater {
public NotifInflaterImpl(
IStatusBarService statusBarService,
NotifCollection notifCollection,
- NotifInflationErrorManager errorManager) {
+ NotifInflationErrorManager errorManager,
+ NotifPipeline notifPipeline) {
mStatusBarService = statusBarService;
mNotifCollection = notifCollection;
mNotifErrorManager = errorManager;
+ mNotifPipeline = notifPipeline;
}
/**
@@ -110,7 +113,7 @@ public class NotifInflaterImpl implements NotifInflater {
DISMISS_SENTIMENT_NEUTRAL,
NotificationVisibility.obtain(entry.getKey(),
entry.getRanking().getRank(),
- mNotifCollection.getActiveNotifs().size(),
+ mNotifPipeline.getShadeListCount(),
true,
NotificationLogger.getNotificationLocation(entry))
));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
index d4d2369ba822..44cec966ba55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -195,4 +195,27 @@ public class NotifPipeline implements CommonNotifCollection {
public List<ListEntry> getShadeList() {
return mShadeListBuilder.getShadeList();
}
+
+ /**
+ * Returns the number of notifications currently shown in the shade. This includes all
+ * children and summary notifications. If this method is called during pipeline execution it
+ * will return the number of notifications in its current state, which will likely be only
+ * partially-generated.
+ */
+ public int getShadeListCount() {
+ final List<ListEntry> entries = getShadeList();
+ int numNotifs = 0;
+ for (int i = 0; i < entries.size(); i++) {
+ final ListEntry entry = entries.get(i);
+ if (entry instanceof GroupEntry) {
+ final GroupEntry parentEntry = (GroupEntry) entry;
+ numNotifs++; // include the summary in the count
+ numNotifs += parentEntry.getChildren().size();
+ } else {
+ numNotifs++;
+ }
+ }
+
+ return numNotifs;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 116c70c4f1cf..8b2a07d00378 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
import static android.service.notification.NotificationStats.DISMISSAL_OTHER;
-import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_UNKNOWN;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.bubbles.BubbleController;
@@ -153,10 +153,10 @@ public class BubbleCoordinator implements Coordinator {
private DismissedByUserStats createDismissedByUserStats(NotificationEntry entry) {
return new DismissedByUserStats(
DISMISSAL_OTHER,
- DISMISS_SENTIMENT_UNKNOWN,
+ DISMISS_SENTIMENT_NEUTRAL,
NotificationVisibility.obtain(entry.getKey(),
entry.getRanking().getRank(),
- mNotifPipeline.getActiveNotifs().size(),
+ mNotifPipeline.getShadeListCount(),
true, // was visible as a bubble
NotificationLogger.getNotificationLocation(entry))
);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index dc7a50d621a1..8675cca3cffe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -77,6 +77,14 @@ class NotifCollectionLogger @Inject constructor(
})
}
+ fun logNotifClearAllDismissalIntercepted(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ }, {
+ "CLEAR ALL DISMISSAL INTERCEPTED $str1"
+ })
+ }
+
fun logRankingMissing(key: String, rankingMap: RankingMap) {
buffer.log(TAG, WARNING, { str1 = key }, { "Ranking update is missing ranking for $str1" })
buffer.log(TAG, DEBUG, {}, { "Ranking map contents:" })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 0cc337187f02..b2b46d58713f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.notification.stack;
+import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
@@ -79,6 +82,7 @@ import com.android.internal.graphics.ColorUtils;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
import com.android.keyguard.KeyguardSliceView;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
@@ -98,6 +102,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController.StateList
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DragDownHelper.DragDownCallback;
import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -114,7 +119,11 @@ import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.ShadeViewRefactor;
import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -484,8 +493,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
private NotificationIconAreaController mIconAreaController;
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final Rect mTmpRect = new Rect();
- private final NotificationEntryManager mEntryManager =
- Dependency.get(NotificationEntryManager.class);
+ private final FeatureFlags mFeatureFlags;
+ private final NotifPipeline mNotifPipeline;
+ private final NotifCollection mNotifCollection;
+ private final NotificationEntryManager mEntryManager;
private final IStatusBarService mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@VisibleForTesting
@@ -529,7 +540,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
ZenModeController zenController,
NotificationSectionsManager notificationSectionsManager,
ForegroundServiceSectionController fgsSectionController,
- ForegroundServiceDismissalFeatureController fgsFeatureController
+ ForegroundServiceDismissalFeatureController fgsFeatureController,
+ FeatureFlags featureFlags,
+ NotifPipeline notifPipeline,
+ NotificationEntryManager entryManager,
+ NotifCollection notifCollection
) {
super(context, attrs, 0, 0);
Resources res = getResources();
@@ -607,16 +622,26 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
}
}, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL);
- mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
- @Override
- public void onPreEntryUpdated(NotificationEntry entry) {
- if (entry.rowExists() && !entry.getSbn().isClearable()) {
- // If the row already exists, the user may have performed a dismiss action on
- // the notification. Since it's not clearable we should snap it back.
- snapViewIfNeeded(entry);
+ mFeatureFlags = featureFlags;
+ mNotifPipeline = notifPipeline;
+ mEntryManager = entryManager;
+ mNotifCollection = notifCollection;
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
+ @Override
+ public void onEntryUpdated(NotificationEntry entry) {
+ NotificationStackScrollLayout.this.onEntryUpdated(entry);
}
- }
- });
+ });
+ } else {
+ mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+ @Override
+ public void onPreEntryUpdated(NotificationEntry entry) {
+ NotificationStackScrollLayout.this.onEntryUpdated(entry);
+ }
+ });
+ }
+
dynamicPrivacyController.addListener(this);
mDynamicPrivacyController = dynamicPrivacyController;
mStatusbarStateController = statusBarStateController;
@@ -708,7 +733,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void updateFooter() {
boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications(ROWS_ALL);
- boolean showFooterView = (showDismissView || mEntryManager.hasActiveNotifications())
+ boolean showFooterView = (showDismissView || hasActiveNotifications())
&& mStatusBarState != StatusBarState.KEYGUARD
&& !mRemoteInputManager.getController().isRemoteInputActive();
@@ -5537,32 +5562,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
return;
}
- performDismissAllAnimations(viewsToHide, closeShade, () -> {
- for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
- if (canChildBeDismissed(rowToRemove)) {
- if (selection == ROWS_ALL) {
- // TODO: This is a listener method; we shouldn't be calling it. Can we just
- // call performRemoveNotification as below?
- mEntryManager.removeNotification(
- rowToRemove.getEntry().getKey(),
- null /* ranking */,
- NotificationListenerService.REASON_CANCEL_ALL);
- } else {
- mEntryManager.performRemoveNotification(
- rowToRemove.getEntry().getSbn(),
- NotificationListenerService.REASON_CANCEL_ALL);
- }
- } else {
- rowToRemove.resetTranslation();
- }
- }
- if (selection == ROWS_ALL) {
- try {
- mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
- } catch (Exception ex) {
- }
- }
- });
+ performDismissAllAnimations(
+ viewsToHide,
+ closeShade,
+ () -> onDismissAllAnimationsEnd(viewsToRemove, selection));
}
private boolean includeChildInDismissAll(
@@ -6407,6 +6410,83 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
return false;
}
+ // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------
+
+ private void onEntryUpdated(NotificationEntry entry) {
+ // If the row already exists, the user may have performed a dismiss action on the
+ // notification. Since it's not clearable we should snap it back.
+ if (entry.rowExists() && !entry.getSbn().isClearable()) {
+ snapViewIfNeeded(entry);
+ }
+ }
+
+ private boolean hasActiveNotifications() {
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ return mNotifPipeline.getShadeList().isEmpty();
+ } else {
+ return mEntryManager.hasActiveNotifications();
+ }
+ }
+
+ /**
+ * Called after the animations for a "clear all notifications" action has ended.
+ */
+ private void onDismissAllAnimationsEnd(
+ List<ExpandableNotificationRow> viewsToRemove,
+ @SelectedRows int selectedRows) {
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ if (selectedRows == ROWS_ALL) {
+ mNotifCollection.dismissAllNotifications(mLockscreenUserManager.getCurrentUserId());
+ } else {
+ final List<Pair<NotificationEntry, DismissedByUserStats>>
+ entriesWithRowsDismissedFromShade = new ArrayList<>();
+ final List<DismissedByUserStats> dismissalUserStats = new ArrayList<>();
+ final int numVisibleEntries = mNotifPipeline.getShadeListCount();
+ for (int i = 0; i < viewsToRemove.size(); i++) {
+ final NotificationEntry entry = viewsToRemove.get(i).getEntry();
+ final DismissedByUserStats stats =
+ new DismissedByUserStats(
+ DISMISSAL_SHADE,
+ DISMISS_SENTIMENT_NEUTRAL,
+ NotificationVisibility.obtain(
+ entry.getKey(),
+ entry.getRanking().getRank(),
+ numVisibleEntries,
+ true,
+ NotificationLogger.getNotificationLocation(entry)));
+ entriesWithRowsDismissedFromShade.add(
+ new Pair<NotificationEntry, DismissedByUserStats>(entry, stats));
+ }
+ mNotifCollection.dismissNotifications(entriesWithRowsDismissedFromShade);
+ }
+ } else {
+ for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
+ if (canChildBeDismissed(rowToRemove)) {
+ if (selectedRows == ROWS_ALL) {
+ // TODO: This is a listener method; we shouldn't be calling it. Can we just
+ // call performRemoveNotification as below?
+ mEntryManager.removeNotification(
+ rowToRemove.getEntry().getKey(),
+ null /* ranking */,
+ NotificationListenerService.REASON_CANCEL_ALL);
+ } else {
+ mEntryManager.performRemoveNotification(
+ rowToRemove.getEntry().getSbn(),
+ NotificationListenerService.REASON_CANCEL_ALL);
+ }
+ } else {
+ rowToRemove.resetTranslation();
+ }
+ }
+ if (selectedRows == ROWS_ALL) {
+ try {
+ mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
+ } catch (Exception ex) {
+ }
+ }
+ }
+ }
+
// ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
@ShadeViewRefactor(RefactorComponent.INPUT)
@@ -6415,8 +6495,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
/* Only ever called as a consequence of a lockscreen expansion gesture. */
@Override
public boolean onDraggedDown(View startingChild, int dragLengthY) {
- if (mStatusBarState == StatusBarState.KEYGUARD
- && mEntryManager.hasActiveNotifications()) {
+ if (mStatusBarState == StatusBarState.KEYGUARD && hasActiveNotifications()) {
mLockscreenGestureLogger.write(
MetricsEvent.ACTION_LS_SHADE,
(int) (dragLengthY / mDisplayMetrics.density),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 0f3b5db2d281..70b43bfc0367 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
import static com.android.systemui.statusbar.phone.StatusBar.getActivityOptions;
@@ -35,6 +36,7 @@ import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.dreams.IDreamManager;
+import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.EventLog;
@@ -56,6 +58,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -66,7 +69,11 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
@@ -97,6 +104,9 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final KeyguardStateController mKeyguardStateController;
private final ActivityStarter mActivityStarter;
private final NotificationEntryManager mEntryManager;
+ private final NotifPipeline mNotifPipeline;
+ private final NotifCollection mNotifCollection;
+ private final FeatureFlags mFeatureFlags;
private final StatusBarStateController mStatusBarStateController;
private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
private final MetricsLogger mMetricsLogger;
@@ -135,7 +145,9 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
NotificationInterruptionStateProvider notificationInterruptionStateProvider,
MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils,
Handler mainThreadHandler, Handler backgroundHandler, Executor uiBgExecutor,
- ActivityIntentHelper activityIntentHelper, BubbleController bubbleController) {
+ ActivityIntentHelper activityIntentHelper, BubbleController bubbleController,
+ FeatureFlags featureFlags, NotifPipeline notifPipeline,
+ NotifCollection notifCollection) {
mContext = context;
mNotificationPanel = panel;
mPresenter = presenter;
@@ -162,12 +174,25 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mLockPatternUtils = lockPatternUtils;
mBackgroundHandler = backgroundHandler;
mUiBgExecutor = uiBgExecutor;
- mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
- @Override
- public void onPendingEntryAdded(NotificationEntry entry) {
- handleFullScreenIntent(entry);
- }
- });
+ mFeatureFlags = featureFlags;
+ mNotifPipeline = notifPipeline;
+ mNotifCollection = notifCollection;
+ if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+ @Override
+ public void onPendingEntryAdded(NotificationEntry entry) {
+ handleFullScreenIntent(entry);
+ }
+ });
+ } else {
+ mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
+ @Override
+ public void onEntryAdded(NotificationEntry entry) {
+ handleFullScreenIntent(entry);
+ }
+ });
+ }
+
mStatusBarRemoteInputCallback = remoteInputCallback;
mMainThreadHandler = mainThreadHandler;
mActivityIntentHelper = activityIntentHelper;
@@ -246,15 +271,14 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mHeadsUpManager.removeNotification(sbn.getKey(),
true /* releaseImmediately */);
}
- StatusBarNotification parentToCancel = null;
+ NotificationEntry parentToCancel = null;
if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
- StatusBarNotification summarySbn =
- mGroupManager.getLogicalGroupSummary(sbn).getSbn();
- if (shouldAutoCancel(summarySbn)) {
+ NotificationEntry summarySbn = mGroupManager.getLogicalGroupSummary(sbn);
+ if (shouldAutoCancel(summarySbn.getSbn())) {
parentToCancel = summarySbn;
}
}
- final StatusBarNotification parentToCancelFinal = parentToCancel;
+ final NotificationEntry parentToCancelFinal = parentToCancel;
final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed(
sbn, row, controller, intent,
isActivityIntent, wasOccluded, parentToCancelFinal);
@@ -279,7 +303,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
PendingIntent intent,
boolean isActivityIntent,
boolean wasOccluded,
- StatusBarNotification parentToCancelFinal) {
+ NotificationEntry parentToCancelFinal) {
String notificationKey = sbn.getKey();
try {
// The intent we are sending is for the application, which
@@ -330,7 +354,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
collapseOnMainThread();
}
- final int count = mEntryManager.getActiveNotificationsCount();
+ final int count = getVisibleNotificationsCount();
final int rank = entry.getRanking().getRank();
NotificationVisibility.NotificationLocation location =
NotificationLogger.getNotificationLocation(entry);
@@ -349,7 +373,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
|| mRemoteInputManager.isNotificationKeptForRemoteInputHistory(
notificationKey)) {
// Automatically remove all notifications that we may have kept around longer
- removeNotification(sbn);
+ removeNotification(row.getEntry());
}
}
mIsCollapsingToShowActivityOverLockscreen = false;
@@ -482,11 +506,10 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
return entry.shouldSuppressFullScreenIntent();
}
- private void removeNotification(StatusBarNotification notification) {
+ private void removeNotification(NotificationEntry entry) {
// We have to post it to the UI thread for synchronization
mMainThreadHandler.post(() -> {
- Runnable removeRunnable =
- () -> mEntryManager.performRemoveNotification(notification, REASON_CLICK);
+ Runnable removeRunnable = createRemoveRunnable(entry);
if (mPresenter.isCollapsing()) {
// To avoid lags we're only performing the remove
// after the shade was collapsed
@@ -497,6 +520,53 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
});
}
+ // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------
+
+ private int getVisibleNotificationsCount() {
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ return mNotifPipeline.getShadeListCount();
+ } else {
+ return mEntryManager.getActiveNotificationsCount();
+ }
+ }
+
+ private Runnable createRemoveRunnable(NotificationEntry entry) {
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ return new Runnable() {
+ @Override
+ public void run() {
+ // see NotificationLogger#logNotificationClear
+ int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+ if (mHeadsUpManager.isAlerting(entry.getKey())) {
+ dismissalSurface = NotificationStats.DISMISSAL_PEEK;
+ } else if (mNotificationPanel.hasPulsingNotifications()) {
+ dismissalSurface = NotificationStats.DISMISSAL_AOD;
+ }
+
+ mNotifCollection.dismissNotification(
+ entry,
+ new DismissedByUserStats(
+ dismissalSurface,
+ DISMISS_SENTIMENT_NEUTRAL,
+ NotificationVisibility.obtain(
+ entry.getKey(),
+ entry.getRanking().getRank(),
+ mNotifPipeline.getShadeListCount(),
+ true,
+ NotificationLogger.getNotificationLocation(entry))
+ ));
+ }
+ };
+ } else {
+ return new Runnable() {
+ @Override
+ public void run() {
+ mEntryManager.performRemoveNotification(entry.getSbn(), REASON_CLICK);
+ }
+ };
+ }
+ }
+
/**
* Public builder for {@link StatusBarNotificationActivityStarter}.
*/
@@ -506,6 +576,9 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final CommandQueue mCommandQueue;
private final Lazy<AssistManager> mAssistManagerLazy;
private final NotificationEntryManager mEntryManager;
+ private final FeatureFlags mFeatureFlags;
+ private final NotifPipeline mNotifPipeline;
+ private final NotifCollection mNotifCollection;
private final HeadsUpManagerPhone mHeadsUpManager;
private final ActivityStarter mActivityStarter;
private final IStatusBarService mStatusBarService;
@@ -557,7 +630,10 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@UiBackground Executor uiBgExecutor,
ActivityIntentHelper activityIntentHelper,
BubbleController bubbleController,
- ShadeController shadeController) {
+ ShadeController shadeController,
+ FeatureFlags featureFlags,
+ NotifPipeline notifPipeline,
+ NotifCollection notifCollection) {
mContext = context;
mCommandQueue = commandQueue;
mAssistManagerLazy = assistManagerLazy;
@@ -583,6 +659,9 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mActivityIntentHelper = activityIntentHelper;
mBubbleController = bubbleController;
mShadeController = shadeController;
+ mFeatureFlags = featureFlags;
+ mNotifPipeline = notifPipeline;
+ mNotifCollection = notifCollection;
}
/** Sets the status bar to use as {@link StatusBar}. */
@@ -608,8 +687,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
return this;
}
-
-
public StatusBarNotificationActivityStarter build() {
return new StatusBarNotificationActivityStarter(mContext,
mCommandQueue, mAssistManagerLazy,
@@ -638,7 +715,10 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mBackgroundHandler,
mUiBgExecutor,
mActivityIntentHelper,
- mBubbleController);
+ mBubbleController,
+ mFeatureFlags,
+ mNotifPipeline,
+ mNotifCollection);
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 96db16adb7dc..12e9d31fdd0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection;
+import static android.app.Notification.FLAG_NO_CLEAR;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
@@ -33,8 +34,8 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -50,12 +51,12 @@ import android.app.Notification;
import android.os.RemoteException;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import androidx.test.filters.SmallTest;
@@ -104,7 +105,6 @@ public class NotifCollectionTest extends SysuiTestCase {
@Spy private RecordingCollectionListener mCollectionListener;
@Mock private CollectionReadyForBuildListener mBuildListener;
@Mock private FeatureFlags mFeatureFlags;
- @Mock private DismissedByUserStats mDismissedByUserStats;
@Spy private RecordingLifetimeExtender mExtender1 = new RecordingLifetimeExtender("Extender1");
@Spy private RecordingLifetimeExtender mExtender2 = new RecordingLifetimeExtender("Extender2");
@@ -424,13 +424,16 @@ public class NotifCollectionTest extends SysuiTestCase {
public void testDismissingLifetimeExtendedSummaryDoesNotDismissChildren() {
// GIVEN A notif group with one summary and two children
mCollection.addNotificationLifetimeExtender(mExtender1);
- NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")
- .setGroup(mContext, GROUP_1)
- .setGroupSummary(mContext, true));
- NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")
- .setGroup(mContext, GROUP_1));
- NotifEvent notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3, "myTag")
- .setGroup(mContext, GROUP_1));
+ CollectionEvent notif1 = postNotif(
+ buildNotif(TEST_PACKAGE, 1, "myTag")
+ .setGroup(mContext, GROUP_1)
+ .setGroupSummary(mContext, true));
+ CollectionEvent notif2 = postNotif(
+ buildNotif(TEST_PACKAGE, 2, "myTag")
+ .setGroup(mContext, GROUP_1));
+ CollectionEvent notif3 = postNotif(
+ buildNotif(TEST_PACKAGE, 3, "myTag")
+ .setGroup(mContext, GROUP_1));
NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
@@ -456,7 +459,7 @@ public class NotifCollectionTest extends SysuiTestCase {
}
@Test
- public void testDismissInterceptorsAreCalled() throws RemoteException {
+ public void testDismissNotificationCallsDismissInterceptors() throws RemoteException {
// GIVEN a collection with notifications with multiple dismiss interceptors
mInterceptor1.shouldInterceptDismissal = true;
mInterceptor2.shouldInterceptDismissal = true;
@@ -469,10 +472,7 @@ public class NotifCollectionTest extends SysuiTestCase {
NotificationEntry entry = mCollectionListener.getEntry(notif.key);
// WHEN a notification is manually dismissed
- DismissedByUserStats stats = new DismissedByUserStats(
- NotificationStats.DISMISSAL_SHADE,
- NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
- NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+ DismissedByUserStats stats = defaultStats(entry);
mCollection.dismissNotification(entry, stats);
// THEN all interceptors get checked
@@ -506,10 +506,7 @@ public class NotifCollectionTest extends SysuiTestCase {
NotificationEntry entry = mCollectionListener.getEntry(notif.key);
// WHEN a notification is manually dismissed and intercepted
- DismissedByUserStats stats = new DismissedByUserStats(
- NotificationStats.DISMISSAL_SHADE,
- NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
- NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+ DismissedByUserStats stats = defaultStats(entry);
mCollection.dismissNotification(entry, stats);
assertEquals(List.of(mInterceptor1, mInterceptor2), entry.mDismissInterceptors);
clearInvocations(mInterceptor1, mInterceptor2);
@@ -531,7 +528,7 @@ public class NotifCollectionTest extends SysuiTestCase {
eq(notif.sbn.getKey()),
anyInt(),
anyInt(),
- anyObject());
+ eq(stats.notificationVisibility));
}
@Test
@@ -544,19 +541,16 @@ public class NotifCollectionTest extends SysuiTestCase {
NotificationEntry entry = mCollectionListener.getEntry(notif.key);
// GIVEN a notification is manually dismissed
- DismissedByUserStats stats = new DismissedByUserStats(
- NotificationStats.DISMISSAL_SHADE,
- NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
- NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+ DismissedByUserStats stats = defaultStats(entry);
mCollection.dismissNotification(entry, stats);
// WHEN all interceptors end their interception dismissal
mInterceptor1.shouldInterceptDismissal = false;
mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
- mDismissedByUserStats);
+ stats);
// THEN we send the dismissal to system server
- verify(mStatusBarService, times(1)).onNotificationClear(
+ verify(mStatusBarService).onNotificationClear(
eq(notif.sbn.getPackageName()),
eq(notif.sbn.getTag()),
eq(47),
@@ -564,7 +558,7 @@ public class NotifCollectionTest extends SysuiTestCase {
eq(notif.sbn.getKey()),
anyInt(),
anyInt(),
- anyObject());
+ eq(stats.notificationVisibility));
}
@Test
@@ -581,16 +575,12 @@ public class NotifCollectionTest extends SysuiTestCase {
NotificationEntry entry = mCollectionListener.getEntry(notif.key);
// GIVEN a notification is manually dismissed
- DismissedByUserStats stats = new DismissedByUserStats(
- NotificationStats.DISMISSAL_SHADE,
- NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
- NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
- mCollection.dismissNotification(entry, stats);
+ mCollection.dismissNotification(entry, defaultStats(entry));
// WHEN an interceptor ends its interception
mInterceptor1.shouldInterceptDismissal = false;
mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
- mDismissedByUserStats);
+ defaultStats(entry));
// THEN all interceptors get checked
verify(mInterceptor1).shouldInterceptDismissal(entry);
@@ -613,7 +603,7 @@ public class NotifCollectionTest extends SysuiTestCase {
// WHEN we try to end the dismissal of an interceptor that didn't intercept the notif
mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
- mDismissedByUserStats);
+ defaultStats(entry));
// THEN an exception is thrown
}
@@ -636,11 +626,11 @@ public class NotifCollectionTest extends SysuiTestCase {
@Test
public void testGroupChildrenAreDismissedLocallyWhenSummaryIsDismissed() {
// GIVEN a collection with two grouped notifs in it
- NotifEvent notif0 = mNoMan.postNotif(
+ CollectionEvent notif0 = postNotif(
buildNotif(TEST_PACKAGE, 0)
.setGroup(mContext, GROUP_1)
.setGroupSummary(mContext, true));
- NotifEvent notif1 = mNoMan.postNotif(
+ CollectionEvent notif1 = postNotif(
buildNotif(TEST_PACKAGE, 1)
.setGroup(mContext, GROUP_1));
NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -657,11 +647,11 @@ public class NotifCollectionTest extends SysuiTestCase {
@Test
public void testUpdatingDismissedSummaryBringsChildrenBack() {
// GIVEN a collection with two grouped notifs in it
- NotifEvent notif0 = mNoMan.postNotif(
+ CollectionEvent notif0 = postNotif(
buildNotif(TEST_PACKAGE, 0)
.setGroup(mContext, GROUP_1)
.setGroupSummary(mContext, true));
- NotifEvent notif1 = mNoMan.postNotif(
+ CollectionEvent notif1 = postNotif(
buildNotif(TEST_PACKAGE, 1)
.setGroup(mContext, GROUP_1));
NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -680,14 +670,14 @@ public class NotifCollectionTest extends SysuiTestCase {
@Test
public void testDismissedChildrenAreNotResetByParentUpdate() {
// GIVEN a collection with three grouped notifs in it
- NotifEvent notif0 = mNoMan.postNotif(
+ CollectionEvent notif0 = postNotif(
buildNotif(TEST_PACKAGE, 0)
.setGroup(mContext, GROUP_1)
.setGroupSummary(mContext, true));
- NotifEvent notif1 = mNoMan.postNotif(
+ CollectionEvent notif1 = postNotif(
buildNotif(TEST_PACKAGE, 1)
.setGroup(mContext, GROUP_1));
- NotifEvent notif2 = mNoMan.postNotif(
+ CollectionEvent notif2 = postNotif(
buildNotif(TEST_PACKAGE, 2)
.setGroup(mContext, GROUP_1));
NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -709,11 +699,11 @@ public class NotifCollectionTest extends SysuiTestCase {
@Test
public void testUpdatingGroupKeyOfDismissedSummaryBringsChildrenBack() {
// GIVEN a collection with two grouped notifs in it
- NotifEvent notif0 = mNoMan.postNotif(
+ CollectionEvent notif0 = postNotif(
buildNotif(TEST_PACKAGE, 0)
.setOverrideGroupKey(GROUP_1)
.setGroupSummary(mContext, true));
- NotifEvent notif1 = mNoMan.postNotif(
+ CollectionEvent notif1 = postNotif(
buildNotif(TEST_PACKAGE, 1)
.setOverrideGroupKey(GROUP_1));
NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -1055,6 +1045,213 @@ public class NotifCollectionTest extends SysuiTestCase {
assertEquals(REASON_NOT_CANCELED, entry0.mCancellationReason);
}
+ @Test
+ public void testDismissNotificationsRebuildsOnce() {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+ clearInvocations(mBuildListener);
+
+ // WHEN both notifications are manually dismissed together
+ mCollection.dismissNotifications(
+ List.of(new Pair(entry1, defaultStats(entry1)),
+ new Pair(entry2, defaultStats(entry2))));
+
+ // THEN build list is only called one time
+ verify(mBuildListener).onBuildList(any(Collection.class));
+ }
+
+ @Test
+ public void testDismissNotificationsSentToSystemServer() throws RemoteException {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN both notifications are manually dismissed together
+ DismissedByUserStats stats1 = defaultStats(entry1);
+ DismissedByUserStats stats2 = defaultStats(entry2);
+ mCollection.dismissNotifications(
+ List.of(new Pair(entry1, defaultStats(entry1)),
+ new Pair(entry2, defaultStats(entry2))));
+
+ // THEN we send the dismissals to system server
+ verify(mStatusBarService).onNotificationClear(
+ notif1.sbn.getPackageName(),
+ notif1.sbn.getTag(),
+ 47,
+ notif1.sbn.getUser().getIdentifier(),
+ notif1.sbn.getKey(),
+ stats1.dismissalSurface,
+ stats1.dismissalSentiment,
+ stats1.notificationVisibility);
+
+ verify(mStatusBarService).onNotificationClear(
+ notif2.sbn.getPackageName(),
+ notif2.sbn.getTag(),
+ 88,
+ notif2.sbn.getUser().getIdentifier(),
+ notif2.sbn.getKey(),
+ stats2.dismissalSurface,
+ stats2.dismissalSentiment,
+ stats2.notificationVisibility);
+ }
+
+ @Test
+ public void testDismissNotificationsMarkedAsDismissed() {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN both notifications are manually dismissed together
+ mCollection.dismissNotifications(
+ List.of(new Pair(entry1, defaultStats(entry1)),
+ new Pair(entry2, defaultStats(entry2))));
+
+ // THEN the entries are marked as dismissed
+ assertEquals(DISMISSED, entry1.getDismissState());
+ assertEquals(DISMISSED, entry2.getDismissState());
+ }
+
+ @Test
+ public void testDismissNotificationssCallsDismissInterceptors() {
+ // GIVEN a collection with notifications with multiple dismiss interceptors
+ mInterceptor1.shouldInterceptDismissal = true;
+ mInterceptor2.shouldInterceptDismissal = true;
+ mInterceptor3.shouldInterceptDismissal = false;
+ mCollection.addNotificationDismissInterceptor(mInterceptor1);
+ mCollection.addNotificationDismissInterceptor(mInterceptor2);
+ mCollection.addNotificationDismissInterceptor(mInterceptor3);
+
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN both notifications are manually dismissed together
+ mCollection.dismissNotifications(
+ List.of(new Pair(entry1, defaultStats(entry1)),
+ new Pair(entry2, defaultStats(entry2))));
+
+ // THEN all interceptors get checked
+ verify(mInterceptor1).shouldInterceptDismissal(entry1);
+ verify(mInterceptor2).shouldInterceptDismissal(entry1);
+ verify(mInterceptor3).shouldInterceptDismissal(entry1);
+ verify(mInterceptor1).shouldInterceptDismissal(entry2);
+ verify(mInterceptor2).shouldInterceptDismissal(entry2);
+ verify(mInterceptor3).shouldInterceptDismissal(entry2);
+
+ assertEquals(List.of(mInterceptor1, mInterceptor2), entry1.mDismissInterceptors);
+ assertEquals(List.of(mInterceptor1, mInterceptor2), entry2.mDismissInterceptors);
+ }
+
+ @Test
+ public void testDismissAllNotificationsCallsRebuildOnce() {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+ clearInvocations(mBuildListener);
+
+ // WHEN all notifications are dismissed for the user who posted both notifs
+ mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
+
+ // THEN build list is only called one time
+ verify(mBuildListener).onBuildList(any(Collection.class));
+ }
+
+ @Test
+ public void testDismissAllNotificationsSentToSystemServer() throws RemoteException {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN all notifications are dismissed for the user who posted both notifs
+ mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
+
+ // THEN we send the dismissal to system server
+ verify(mStatusBarService).onClearAllNotifications(
+ entry1.getSbn().getUser().getIdentifier());
+ }
+
+ @Test
+ public void testDismissAllNotificationsMarkedAsDismissed() {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN all notifications are dismissed for the user who posted both notifs
+ mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
+
+ // THEN the entries are marked as dismissed
+ assertEquals(DISMISSED, entry1.getDismissState());
+ assertEquals(DISMISSED, entry2.getDismissState());
+ }
+
+ @Test
+ public void testDismissAllNotificationsDoesNotMarkDismissedUnclearableNotifs() {
+ // GIVEN a collection with one unclearable notification and one clearable notification
+ NotificationEntryBuilder notifEntryBuilder = buildNotif(TEST_PACKAGE, 47, "myTag");
+ notifEntryBuilder.modifyNotification(mContext)
+ .setFlag(FLAG_NO_CLEAR, true);
+ NotifEvent unclearabeNotif = mNoMan.postNotif(notifEntryBuilder);
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry unclearableEntry = mCollectionListener.getEntry(unclearabeNotif.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN all notifications are dismissed for the user who posted both notifs
+ mCollection.dismissAllNotifications(unclearableEntry.getSbn().getUser().getIdentifier());
+
+ // THEN only the clearable entry is marked as dismissed
+ assertEquals(NOT_DISMISSED, unclearableEntry.getDismissState());
+ assertEquals(DISMISSED, entry2.getDismissState());
+ }
+
+ @Test
+ public void testDismissAllNotificationsCallsDismissInterceptorsOnlyOnUnclearableNotifs() {
+ // GIVEN a collection with multiple dismiss interceptors
+ mInterceptor1.shouldInterceptDismissal = true;
+ mInterceptor2.shouldInterceptDismissal = true;
+ mInterceptor3.shouldInterceptDismissal = false;
+ mCollection.addNotificationDismissInterceptor(mInterceptor1);
+ mCollection.addNotificationDismissInterceptor(mInterceptor2);
+ mCollection.addNotificationDismissInterceptor(mInterceptor3);
+
+ // GIVEN a collection with one unclearable and one clearable notification
+ NotifEvent unclearableNotif = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 47, "myTag")
+ .setFlag(mContext, FLAG_NO_CLEAR, true));
+ NotificationEntry unclearable = mCollectionListener.getEntry(unclearableNotif.key);
+ NotifEvent clearableNotif = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 88, "myTag")
+ .setFlag(mContext, FLAG_NO_CLEAR, false));
+ NotificationEntry clearable = mCollectionListener.getEntry(clearableNotif.key);
+
+ // WHEN all notifications are dismissed for the user who posted the notif
+ mCollection.dismissAllNotifications(clearable.getSbn().getUser().getIdentifier());
+
+ // THEN all interceptors get checked for the unclearable notification
+ verify(mInterceptor1).shouldInterceptDismissal(unclearable);
+ verify(mInterceptor2).shouldInterceptDismissal(unclearable);
+ verify(mInterceptor3).shouldInterceptDismissal(unclearable);
+ assertEquals(List.of(mInterceptor1, mInterceptor2), unclearable.mDismissInterceptors);
+
+ // THEN no interceptors get checked for the clearable notification
+ verify(mInterceptor1, never()).shouldInterceptDismissal(clearable);
+ verify(mInterceptor2, never()).shouldInterceptDismissal(clearable);
+ verify(mInterceptor3, never()).shouldInterceptDismissal(clearable);
+ }
+
private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) {
return new NotificationEntryBuilder()
.setPkg(pkg)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index b16e52ce7bd4..9ccee75a3d09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -70,6 +70,8 @@ import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.TestableNotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
@@ -133,6 +135,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Mock private NotificationSectionsManager mNotificationSectionsManager;
@Mock private NotificationSection mNotificationSection;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+ @Mock private FeatureFlags mFeatureFlags;
private UserChangedListener mUserChangedListener;
private TestableNotificationEntryManager mEntryManager;
private int mOriginalInterruptionModelSetting;
@@ -182,9 +185,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mock(LeakDetector.class),
mock(ForegroundServiceDismissalFeatureController.class)
);
- mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager);
-
+ when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
NotificationShelf notificationShelf = mock(NotificationShelf.class);
when(mNotificationSectionsManager.createSectionsForBuckets()).thenReturn(
@@ -208,7 +210,11 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mZenModeController,
mNotificationSectionsManager,
mock(ForegroundServiceSectionController.class),
- mock(ForegroundServiceDismissalFeatureController.class)
+ mock(ForegroundServiceDismissalFeatureController.class),
+ mFeatureFlags,
+ mock(NotifPipeline.class),
+ mEntryManager,
+ mock(NotifCollection.class)
);
verify(mLockscreenUserManager).addUserChangedListener(userChangedCaptor.capture());
mUserChangedListener = userChangedCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 50276106f8d4..1e4df272b02b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -58,6 +58,7 @@ import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -67,6 +68,8 @@ import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -114,6 +117,12 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
private BubbleController mBubbleController;
@Mock
private ShadeControllerImpl mShadeController;
+ @Mock
+ private FeatureFlags mFeatureFlags;
+ @Mock
+ private NotifPipeline mNotifPipeline;
+ @Mock
+ private NotifCollection mNotifCollection;
@Mock
private ActivityIntentHelper mActivityIntentHelper;
@@ -162,6 +171,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
mActiveNotifications.add(mBubbleNotificationRow.getEntry());
when(mEntryManager.getVisibleNotifications()).thenReturn(mActiveNotifications);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
+ when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
mNotificationActivityStarter = (new StatusBarNotificationActivityStarter.Builder(
getContext(), mock(CommandQueue.class), () -> mAssistManager,
@@ -175,11 +185,12 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
mKeyguardStateController,
mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class),
mock(LockPatternUtils.class), mHandler, mHandler, mUiBgExecutor,
- mActivityIntentHelper, mBubbleController, mShadeController))
+ mActivityIntentHelper, mBubbleController, mShadeController, mFeatureFlags,
+ mNotifPipeline, mNotifCollection)
.setStatusBar(mStatusBar)
.setNotificationPanelViewController(mock(NotificationPanelViewController.class))
.setNotificationPresenter(mock(NotificationPresenter.class))
- .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class))
+ .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class)))
.build();
// set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg