summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java74
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NewNotifPipeline.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java426
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilder.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java625
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java9
13 files changed, 1399 insertions, 79 deletions
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 90aba2f73ef9..1eeaa7c0c939 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -87,6 +87,7 @@ import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.car.CarQSFragment;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.NavigationBarController;
import com.android.systemui.statusbar.NotificationListener;
@@ -102,7 +103,7 @@ import com.android.systemui.statusbar.car.hvac.HvacController;
import com.android.systemui.statusbar.car.hvac.TemperatureView;
import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineInitializer;
+import com.android.systemui.statusbar.notification.NewNotifPipeline;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
@@ -140,6 +141,8 @@ import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
+import dagger.Lazy;
+
/**
* A status bar (and navigation bar) tailored for the automotive use case.
*/
@@ -252,6 +255,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt
@Inject
public CarStatusBar(
Context context,
+ FeatureFlags featureFlags,
LightBarController lightBarController,
AutoHideController autoHideController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -266,7 +270,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt
DynamicPrivacyController dynamicPrivacyController,
BypassHeadsUpNotifier bypassHeadsUpNotifier,
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
- NotifPipelineInitializer notifPipelineInitializer,
+ Lazy<NewNotifPipeline> newNotifPipeline,
FalsingManager falsingManager,
BroadcastDispatcher broadcastDispatcher,
RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
@@ -309,6 +313,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt
DozeParameters dozeParameters) {
super(
context,
+ featureFlags,
lightBarController,
autoHideController,
keyguardUpdateMonitor,
@@ -323,7 +328,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt
dynamicPrivacyController,
bypassHeadsUpNotifier,
allowNotificationLongPress,
- notifPipelineInitializer,
+ newNotifPipeline,
falsingManager,
broadcastDispatcher,
remoteInputQuickSettingsDisabler,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
new file mode 100644
index 000000000000..f91341f8f4ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.provider.DeviceConfig;
+import android.util.ArrayMap;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+/**
+ * Class to manage simple DeviceConfig-based feature flags.
+ *
+ * To enable or disable a flag, run:
+ *
+ * {@code
+ * $ adb shell device_config put systemui <key> <true|false>
+* }
+ *
+ * You will probably need to @{$ adb reboot} afterwards in order for the code to pick up the change.
+ */
+public class FeatureFlags {
+ private final Map<String, Boolean> mCachedDeviceConfigFlags = new ArrayMap<>();
+
+ @Inject
+ public FeatureFlags() {
+ DeviceConfig.addOnPropertiesChangedListener(
+ "systemui",
+ new HandlerExecutor(new Handler(Looper.getMainLooper())),
+ this::onPropertiesChanged);
+ }
+
+ public boolean isNewNotifPipelineEnabled() {
+ return getDeviceConfigFlag("notification.newpipeline.enabled", false);
+ }
+
+ private void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+ synchronized (mCachedDeviceConfigFlags) {
+ for (String key : properties.getKeyset()) {
+ mCachedDeviceConfigFlags.remove(key);
+ }
+ }
+ }
+
+ private boolean getDeviceConfigFlag(String key, boolean defaultValue) {
+ synchronized (mCachedDeviceConfigFlags) {
+ Boolean flag = mCachedDeviceConfigFlags.get(key);
+ if (flag == null) {
+ flag = DeviceConfig.getBoolean("systemui", key, defaultValue);
+ mCachedDeviceConfigFlags.put(key, flag);
+ }
+ return flag;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NewNotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NewNotifPipeline.java
new file mode 100644
index 000000000000..31921a436747
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NewNotifPipeline.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.util.Log;
+
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Initialization code for the new notification pipeline.
+ */
+@Singleton
+public class NewNotifPipeline {
+ private final NotifCollection mNotifCollection;
+
+ @Inject
+ public NewNotifPipeline(
+ NotifCollection notifCollection) {
+ mNotifCollection = notifCollection;
+ }
+
+ /** Hooks the new pipeline up to NotificationManager */
+ public void initialize(
+ NotificationListener notificationService) {
+ mNotifCollection.attach(notificationService);
+
+ Log.d(TAG, "Notif pipeline initialized");
+ }
+
+ private static final String TAG = "NewNotifPipeline";
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java
deleted file mode 100644
index df70828a46be..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification;
-
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-
-import com.android.systemui.statusbar.NotificationListener;
-
-import javax.inject.Inject;
-
-/**
- * Initialization code for the new notification pipeline.
- */
-public class NotifPipelineInitializer {
-
- @Inject
- public NotifPipelineInitializer() {
- }
-
- public void initialize(
- NotificationListener notificationService) {
-
- // TODO Put real code here
- notificationService.setDownstreamListener(new NotificationListener.NotifServiceListener() {
- @Override
- public void onNotificationPosted(StatusBarNotification sbn,
- NotificationListenerService.RankingMap rankingMap) {
- Log.d(TAG, "onNotificationPosted " + sbn.getKey());
- }
-
- @Override
- public void onNotificationRemoved(StatusBarNotification sbn,
- NotificationListenerService.RankingMap rankingMap) {
- Log.d(TAG, "onNotificationRemoved " + sbn.getKey());
- }
-
- @Override
- public void onNotificationRemoved(StatusBarNotification sbn,
- NotificationListenerService.RankingMap rankingMap, int reason) {
- Log.d(TAG, "onNotificationRemoved " + sbn.getKey());
- }
-
- @Override
- public void onNotificationRankingUpdate(
- NotificationListenerService.RankingMap rankingMap) {
- Log.d(TAG, "onNotificationRankingUpdate");
- }
- });
- }
-
- private static final String TAG = "NotifInitializer";
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java
new file mode 100644
index 000000000000..ecce6ea1b211
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import android.service.notification.NotificationStats.DismissalSentiment;
+import android.service.notification.NotificationStats.DismissalSurface;
+
+import com.android.internal.statusbar.NotificationVisibility;
+
+/** Information that must be supplied when dismissing a notification on the behalf of the user. */
+public class DismissedByUserStats {
+ public final @DismissalSurface int dismissalSurface;
+ public final @DismissalSentiment int dismissalSentiment;
+ public final NotificationVisibility notificationVisibility;
+
+ public DismissedByUserStats(
+ @DismissalSurface int dismissalSurface,
+ @DismissalSentiment int dismissalSentiment,
+ NotificationVisibility notificationVisibility) {
+ this.dismissalSurface = dismissalSurface;
+ this.dismissalSentiment = dismissalSentiment;
+ this.notificationVisibility = notificationVisibility;
+ }
+}
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
new file mode 100644
index 000000000000..3203c30ce422
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
+import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static android.service.notification.NotificationListenerService.REASON_ERROR;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_OPTIMIZATION;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED;
+import static android.service.notification.NotificationListenerService.REASON_PACKAGE_CHANGED;
+import static android.service.notification.NotificationListenerService.REASON_PACKAGE_SUSPENDED;
+import static android.service.notification.NotificationListenerService.REASON_PROFILE_TURNED_OFF;
+import static android.service.notification.NotificationListenerService.REASON_SNOOZED;
+import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
+import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
+import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.IntDef;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationListener.NotifServiceListener;
+import com.android.systemui.util.Assert;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Keeps a record of all of the "active" notifications, i.e. the notifications that are currently
+ * posted to the phone. This collection is unsorted, ungrouped, and unfiltered. Just because a
+ * notification appears in this collection doesn't mean that it's currently present in the shade
+ * (notifications can be hidden for a variety of reasons). Code that cares about what notifications
+ * are *visible* right now should register listeners later in the pipeline.
+ *
+ * Each notification is represented by a {@link NotificationEntry}, which is itself made up of two
+ * parts: a {@link StatusBarNotification} and a {@link Ranking}. When notifications are updated,
+ * their underlying SBNs and Rankings are swapped out, but the enclosing NotificationEntry (and its
+ * associated key) remain the same. In general, an SBN can only be updated when the notification is
+ * reposted by the source app; Rankings are updated much more often, usually every time there is an
+ * update from any kind from NotificationManager.
+ *
+ * In general, this collection closely mirrors the list maintained by NotificationManager, but it
+ * can occasionally diverge due to lifetime extenders (see
+ * {@link #addNotificationLifetimeExtender(NotifLifetimeExtender)}).
+ *
+ * Interested parties can register listeners
+ * ({@link #addCollectionListener(NotifCollectionListener)}) to be informed when notifications are
+ * added, updated, or removed.
+ */
+@MainThread
+@Singleton
+public class NotifCollection {
+ private final IStatusBarService mStatusBarService;
+
+ private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>();
+ private final Collection<NotificationEntry> mReadOnlyNotificationSet =
+ Collections.unmodifiableCollection(mNotificationSet.values());
+
+ @Nullable private NotifListBuilder mListBuilder;
+ private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
+ private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
+
+ private boolean mAttached = false;
+ private boolean mAmDispatchingToOtherCode;
+
+ @Inject
+ public NotifCollection(IStatusBarService statusBarService) {
+ Assert.isMainThread();
+ mStatusBarService = statusBarService;
+ }
+
+ /** Initializes the NotifCollection and registers it to receive notification events. */
+ public void attach(NotificationListener listenerService) {
+ Assert.isMainThread();
+ if (mAttached) {
+ throw new RuntimeException("attach() called twice");
+ }
+ mAttached = true;
+
+ listenerService.setDownstreamListener(mNotifServiceListener);
+ }
+
+ /**
+ * Sets the class responsible for converting the collection into the list of currently-visible
+ * notifications.
+ */
+ public void setListBuilder(NotifListBuilder listBuilder) {
+ Assert.isMainThread();
+ mListBuilder = listBuilder;
+ }
+
+ /**
+ * Returns the list of "active" notifications, i.e. the notifications that are currently posted
+ * to the phone. In general, this tracks closely to the list maintained by NotificationManager,
+ * but it can diverge slightly due to lifetime extenders.
+ *
+ * The returned list is read-only, unsorted, unfiltered, and ungrouped.
+ */
+ public Collection<NotificationEntry> getNotifs() {
+ Assert.isMainThread();
+ return mReadOnlyNotificationSet;
+ }
+
+ /**
+ * Registers a listener to be informed when notifications are added, removed or updated.
+ */
+ public void addCollectionListener(NotifCollectionListener listener) {
+ Assert.isMainThread();
+ mNotifCollectionListeners.add(listener);
+ }
+
+ /**
+ * Registers a lifetime extender. Lifetime extenders can cause notifications that have been
+ * dismissed or retracted to be temporarily retained in the collection.
+ */
+ public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) {
+ Assert.isMainThread();
+ checkForReentrantCall();
+ if (mLifetimeExtenders.contains(extender)) {
+ throw new IllegalArgumentException("Extender " + extender + " already added.");
+ }
+ mLifetimeExtenders.add(extender);
+ extender.setCallback(this::onEndLifetimeExtension);
+ }
+
+ /**
+ * Dismiss a notification on behalf of the user.
+ */
+ public void dismissNotification(
+ NotificationEntry entry,
+ @CancellationReason int reason,
+ @NonNull DismissedByUserStats stats) {
+ Assert.isMainThread();
+ checkNotNull(stats);
+ checkForReentrantCall();
+
+ removeNotification(entry.key(), null, reason, stats);
+ }
+
+ private void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+ Assert.isMainThread();
+
+ NotificationEntry entry = mNotificationSet.get(sbn.getKey());
+
+ if (entry == null) {
+ // A new notification!
+ Log.d(TAG, "POSTED " + sbn.getKey());
+
+ entry = new NotificationEntry(sbn, requireRanking(rankingMap, sbn.getKey()));
+ mNotificationSet.put(sbn.getKey(), entry);
+ applyRanking(rankingMap);
+
+ dispatchOnEntryAdded(entry);
+
+ } else {
+ // Update to an existing entry
+ Log.d(TAG, "UPDATED " + sbn.getKey());
+
+ // Notification is updated so it is essentially re-added and thus alive again. Don't
+ // need to keep its lifetime extended.
+ cancelLifetimeExtension(entry);
+
+ entry.setNotification(sbn);
+ applyRanking(rankingMap);
+
+ dispatchOnEntryUpdated(entry);
+ }
+
+ rebuildList();
+ }
+
+ private void onNotificationRemoved(
+ StatusBarNotification sbn,
+ @Nullable RankingMap rankingMap,
+ int reason) {
+ Assert.isMainThread();
+ Log.d(TAG, "REMOVED " + sbn.getKey() + " reason=" + reason);
+ removeNotification(sbn.getKey(), rankingMap, reason, null);
+ }
+
+ private void onNotificationRankingUpdate(RankingMap rankingMap) {
+ Assert.isMainThread();
+ applyRanking(rankingMap);
+ rebuildList();
+ }
+
+ private void removeNotification(
+ String key,
+ @Nullable RankingMap rankingMap,
+ @CancellationReason int reason,
+ DismissedByUserStats dismissedByUserStats) {
+
+ NotificationEntry entry = mNotificationSet.get(key);
+ if (entry == null) {
+ throw new IllegalStateException("No notification to remove with key " + key);
+ }
+
+ entry.mLifetimeExtenders.clear();
+ mAmDispatchingToOtherCode = true;
+ for (NotifLifetimeExtender extender : mLifetimeExtenders) {
+ if (extender.shouldExtendLifetime(entry, reason)) {
+ entry.mLifetimeExtenders.add(extender);
+ }
+ }
+ mAmDispatchingToOtherCode = false;
+
+ if (!isLifetimeExtended(entry)) {
+ mNotificationSet.remove(entry.key());
+
+ if (dismissedByUserStats != null) {
+ try {
+ mStatusBarService.onNotificationClear(
+ entry.sbn().getPackageName(),
+ entry.sbn().getTag(),
+ entry.sbn().getId(),
+ entry.sbn().getUser().getIdentifier(),
+ entry.sbn().getKey(),
+ dismissedByUserStats.dismissalSurface,
+ dismissedByUserStats.dismissalSentiment,
+ dismissedByUserStats.notificationVisibility);
+ } catch (RemoteException e) {
+ // system process is dead if we're here.
+ }
+ }
+
+ if (rankingMap != null) {
+ applyRanking(rankingMap);
+ }
+
+ dispatchOnEntryRemoved(entry, reason, dismissedByUserStats != null /* removedByUser */);
+ }
+
+ rebuildList();
+ }
+
+ private void applyRanking(RankingMap rankingMap) {
+ for (NotificationEntry entry : mNotificationSet.values()) {
+ if (!isLifetimeExtended(entry)) {
+ Ranking ranking = requireRanking(rankingMap, entry.key());
+ entry.setRanking(ranking);
+ }
+ }
+ }
+
+ private void rebuildList() {
+ if (mListBuilder != null) {
+ mListBuilder.onBuildList(mReadOnlyNotificationSet);
+ }
+ }
+
+ private void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry) {
+ Assert.isMainThread();
+ if (!mAttached) {
+ return;
+ }
+ checkForReentrantCall();
+
+ if (!entry.mLifetimeExtenders.remove(extender)) {
+ throw new IllegalStateException(
+ String.format(
+ "Cannot end lifetime extension for extender \"%s\" (%s)",
+ extender.getName(),
+ extender));
+ }
+
+ if (!isLifetimeExtended(entry)) {
+ // TODO: This doesn't need to be undefined -- we can set either EXTENDER_EXPIRED or
+ // save the original reason
+ removeNotification(entry.key(), null, REASON_UNKNOWN, null);
+ }
+ }
+
+ private void cancelLifetimeExtension(NotificationEntry entry) {
+ mAmDispatchingToOtherCode = true;
+ for (NotifLifetimeExtender extender : entry.mLifetimeExtenders) {
+ extender.cancelLifetimeExtension(entry);
+ }
+ mAmDispatchingToOtherCode = false;
+ entry.mLifetimeExtenders.clear();
+ }
+
+ private boolean isLifetimeExtended(NotificationEntry entry) {
+ return entry.mLifetimeExtenders.size() > 0;
+ }
+
+ private void checkForReentrantCall() {
+ if (mAmDispatchingToOtherCode) {
+ throw new IllegalStateException("Reentrant call detected");
+ }
+ }
+
+ private static Ranking requireRanking(RankingMap rankingMap, String key) {
+ // TODO: Modify RankingMap so that we don't have to make a copy here
+ Ranking ranking = new Ranking();
+ if (!rankingMap.getRanking(key, ranking)) {
+ throw new IllegalArgumentException("Ranking map doesn't contain key: " + key);
+ }
+ return ranking;
+ }
+
+ private void dispatchOnEntryAdded(NotificationEntry entry) {
+ mAmDispatchingToOtherCode = true;
+ if (mListBuilder != null) {
+ mListBuilder.onBeginDispatchToListeners();
+ }
+ for (NotifCollectionListener listener : mNotifCollectionListeners) {
+ listener.onEntryAdded(entry);
+ }
+ mAmDispatchingToOtherCode = false;
+ }
+
+ private void dispatchOnEntryUpdated(NotificationEntry entry) {
+ mAmDispatchingToOtherCode = true;
+ if (mListBuilder != null) {
+ mListBuilder.onBeginDispatchToListeners();
+ }
+ for (NotifCollectionListener listener : mNotifCollectionListeners) {
+ listener.onEntryUpdated(entry);
+ }
+ mAmDispatchingToOtherCode = false;
+ }
+
+ private void dispatchOnEntryRemoved(
+ NotificationEntry entry,
+ @CancellationReason int reason,
+ boolean removedByUser) {
+ mAmDispatchingToOtherCode = true;
+ if (mListBuilder != null) {
+ mListBuilder.onBeginDispatchToListeners();
+ }
+ for (NotifCollectionListener listener : mNotifCollectionListeners) {
+ listener.onEntryRemoved(entry, reason, removedByUser);
+ }
+ mAmDispatchingToOtherCode = false;
+ }
+
+ private final NotifServiceListener mNotifServiceListener = new NotifServiceListener() {
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+ NotifCollection.this.onNotificationPosted(sbn, rankingMap);
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+ NotifCollection.this.onNotificationRemoved(sbn, rankingMap, REASON_UNKNOWN);
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+ int reason) {
+ NotifCollection.this.onNotificationRemoved(sbn, rankingMap, reason);
+ }
+
+ @Override
+ public void onNotificationRankingUpdate(RankingMap rankingMap) {
+ NotifCollection.this.onNotificationRankingUpdate(rankingMap);
+ }
+ };
+
+ private static final String TAG = "NotifCollection";
+
+ @IntDef(prefix = { "REASON_" }, value = {
+ REASON_UNKNOWN,
+ REASON_CLICK,
+ REASON_CANCEL_ALL,
+ REASON_ERROR,
+ REASON_PACKAGE_CHANGED,
+ REASON_USER_STOPPED,
+ REASON_PACKAGE_BANNED,
+ REASON_APP_CANCEL,
+ REASON_APP_CANCEL_ALL,
+ REASON_LISTENER_CANCEL,
+ REASON_LISTENER_CANCEL_ALL,
+ REASON_GROUP_SUMMARY_CANCELED,
+ REASON_GROUP_OPTIMIZATION,
+ REASON_PACKAGE_SUSPENDED,
+ REASON_PROFILE_TURNED_OFF,
+ REASON_UNAUTOBUNDLED,
+ REASON_CHANNEL_BANNED,
+ REASON_SNOOZED,
+ REASON_TIMEOUT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface CancellationReason {}
+
+ public static final int REASON_UNKNOWN = 0;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java
new file mode 100644
index 000000000000..032620e14336
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
+
+/**
+ * Listener interface for {@link NotifCollection}.
+ */
+public interface NotifCollectionListener {
+ /**
+ * Called whenever a notification with a new key is posted.
+ */
+ default void onEntryAdded(NotificationEntry entry) {
+ }
+
+ /**
+ * Called whenever a notification with the same key as an existing notification is posted. By
+ * the time this listener is called, the entry's SBN and Ranking will already have been updated.
+ */
+ default void onEntryUpdated(NotificationEntry entry) {
+ }
+
+ /**
+ * Called immediately after a notification has been removed from the collection.
+ */
+ default void onEntryRemoved(
+ NotificationEntry entry,
+ @CancellationReason int reason,
+ boolean removedByUser) {
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java
new file mode 100644
index 000000000000..2c7b13866c10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
+
+/**
+ * A way for other code to temporarily extend the lifetime of a notification after it has been
+ * retracted. See {@link NotifCollection#addNotificationLifetimeExtender(NotifLifetimeExtender)}.
+ */
+public interface NotifLifetimeExtender {
+ /** Name to associate with this extender (for the purposes of debugging) */
+ String getName();
+
+ /**
+ * Called on the extender immediately after it has been registered. The extender should hang on
+ * to this callback and execute it whenever it no longer needs to extend the lifetime of a
+ * notification.
+ */
+ void setCallback(OnEndLifetimeExtensionCallback callback);
+
+ /**
+ * Called by the NotifCollection whenever a notification has been retracted (by the app) or
+ * dismissed (by the user). If the extender returns true, it is considered to be extending the
+ * lifetime of that notification. Lifetime-extended notifications are kept around until all
+ * active extenders expire their extension by calling onEndLifetimeExtension(). This method is
+ * called on all lifetime extenders even if earlier ones return true (in other words, multiple
+ * lifetime extenders can be extending a notification at the same time).
+ */
+ boolean shouldExtendLifetime(NotificationEntry entry, @CancellationReason int reason);
+
+ /**
+ * Called by the NotifCollection to inform a lifetime extender that its extension of a notif
+ * is no longer valid (usually because the notif has been reposted and so no longer needs
+ * lifetime extension). The extender should clean up any references it has to the notif in
+ * question.
+ */
+ void cancelLifetimeExtension(NotificationEntry entry);
+
+ /** Callback for notifying the NotifCollection that a lifetime extension has expired. */
+ interface OnEndLifetimeExtensionCallback {
+ void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilder.java
new file mode 100644
index 000000000000..17fef6850f97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilder.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import java.util.Collection;
+
+/**
+ * Interface for the class responsible for converting a NotifCollection into the final sorted,
+ * filtered, and grouped list of currently visible notifications.
+ */
+public interface NotifListBuilder {
+ /**
+ * Called after the NotifCollection has received an update from NotificationManager but before
+ * it dispatches any change events to its listeners. This is to inform the list builder that
+ * the first stage of the pipeline has been triggered. After events have been dispatched,
+ * onBuildList() will be called.
+ *
+ * While onBuildList() is always called after this method is called, the converse is not always
+ * true: sometimes the NotifCollection applies an update that does not need to dispatch events,
+ * in which case this method will be skipped and onBuildList will be called directly.
+ */
+ void onBeginDispatchToListeners();
+
+ /**
+ * Called by the NotifCollection to indicate that something in the collection has changed and
+ * that the list builder should regenerate the list.
+ */
+ void onBuildList(Collection<NotificationEntry> entries);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index c3211e307845..b12461a36a6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -94,6 +94,20 @@ public final class NotificationEntry {
public StatusBarNotification notification;
private Ranking mRanking;
+
+ /*
+ * Bookkeeping members
+ */
+
+ /** List of lifetime extenders that are extending the lifetime of this notification. */
+ final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
+
+
+ /*
+ * Old members
+ * TODO: Remove every member beneath this line if possible
+ */
+
public boolean noisy;
public StatusBarIconView icon;
public StatusBarIconView expandedIcon;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index c092f9b0a4c3..8e70d082dbb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -179,6 +179,7 @@ import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyboardShortcuts;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -198,7 +199,7 @@ import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineInitializer;
+import com.android.systemui.statusbar.notification.NewNotifPipeline;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
import com.android.systemui.statusbar.notification.NotificationClicker;
@@ -246,6 +247,7 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
+import dagger.Lazy;
import dagger.Subcomponent;
@Singleton
@@ -370,6 +372,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private final Object mQueueLock = new Object();
+ private final FeatureFlags mFeatureFlags;
private final StatusBarIconController mIconController;
private final DozeLog mDozeLog;
private final InjectionInflationController mInjectionInflater;
@@ -381,7 +384,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private final DynamicPrivacyController mDynamicPrivacyController;
private final BypassHeadsUpNotifier mBypassHeadsUpNotifier;
private final boolean mAllowNotificationLongPress;
- private final NotifPipelineInitializer mNotifPipelineInitializer;
+ private final Lazy<NewNotifPipeline> mNewNotifPipeline;
private final FalsingManager mFalsingManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private final ConfigurationController mConfigurationController;
@@ -621,6 +624,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Inject
public StatusBar(
Context context,
+ FeatureFlags featureFlags,
LightBarController lightBarController,
AutoHideController autoHideController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -635,7 +639,7 @@ public class StatusBar extends SystemUI implements DemoMode,
DynamicPrivacyController dynamicPrivacyController,
BypassHeadsUpNotifier bypassHeadsUpNotifier,
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
- NotifPipelineInitializer notifPipelineInitializer,
+ Lazy<NewNotifPipeline> newNotifPipeline,
FalsingManager falsingManager,
BroadcastDispatcher broadcastDispatcher,
RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
@@ -677,6 +681,7 @@ public class StatusBar extends SystemUI implements DemoMode,
NotifLog notifLog,
DozeParameters dozeParameters) {
super(context);
+ mFeatureFlags = featureFlags;
mLightBarController = lightBarController;
mAutoHideController = autoHideController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -691,7 +696,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mDynamicPrivacyController = dynamicPrivacyController;
mBypassHeadsUpNotifier = bypassHeadsUpNotifier;
mAllowNotificationLongPress = allowNotificationLongPress;
- mNotifPipelineInitializer = notifPipelineInitializer;
+ mNewNotifPipeline = newNotifPipeline;
mFalsingManager = falsingManager;
mBroadcastDispatcher = broadcastDispatcher;
mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler;
@@ -1211,7 +1216,9 @@ public class StatusBar extends SystemUI implements DemoMode,
mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
mNotificationListController.bind();
- mNotifPipelineInitializer.initialize(mNotificationListener);
+ if (mFeatureFlags.isNewNotifPipelineEnabled()) {
+ mNewNotifPipeline.get().initialize(mNotificationListener);
+ }
}
/**
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
new file mode 100644
index 000000000000..e4a67dbbfa20
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -0,0 +1,625 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_CLICK;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN;
+
+import static org.junit.Assert.assertEquals;
+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.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.Nullable;
+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 androidx.test.filters.SmallTest;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationListener.NotifServiceListener;
+import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
+import com.android.systemui.util.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.Arrays;
+import java.util.Map;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotifCollectionTest extends SysuiTestCase {
+
+ @Mock private IStatusBarService mStatusBarService;
+ @Mock private NotificationListener mListenerService;
+ @Spy private RecordingCollectionListener mCollectionListener;
+
+ @Spy private RecordingLifetimeExtender mExtender1 = new RecordingLifetimeExtender("Extender1");
+ @Spy private RecordingLifetimeExtender mExtender2 = new RecordingLifetimeExtender("Extender2");
+ @Spy private RecordingLifetimeExtender mExtender3 = new RecordingLifetimeExtender("Extender3");
+
+ @Captor private ArgumentCaptor<NotifServiceListener> mListenerCaptor;
+ @Captor private ArgumentCaptor<NotificationEntry> mEntryCaptor;
+
+ private NotifCollection mCollection;
+ private NotifServiceListener mServiceListener;
+
+ private NoManSimulator mNoMan;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ Assert.sMainLooper = TestableLooper.get(this).getLooper();
+
+ mCollection = new NotifCollection(mStatusBarService);
+ mCollection.attach(mListenerService);
+ mCollection.addCollectionListener(mCollectionListener);
+
+ // Capture the listener object that the collection registers with the listener service so
+ // we can simulate listener service events in tests below
+ verify(mListenerService).setDownstreamListener(mListenerCaptor.capture());
+ mServiceListener = checkNotNull(mListenerCaptor.getValue());
+
+ mNoMan = new NoManSimulator(mServiceListener);
+ }
+
+ @Test
+ public void testEventDispatchedWhenNotifPosted() {
+ // WHEN a notification is posted
+ PostedNotif notif1 = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 3)
+ .setRank(4747));
+
+ // THEN the listener is notified
+ verify(mCollectionListener).onEntryAdded(mEntryCaptor.capture());
+
+ NotificationEntry entry = mEntryCaptor.getValue();
+ assertEquals(notif1.key, entry.key());
+ assertEquals(notif1.sbn, entry.sbn());
+ assertEquals(notif1.ranking, entry.ranking());
+ }
+
+ @Test
+ public void testEventDispatchedWhenNotifUpdated() {
+ // GIVEN a collection with one notif
+ mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)
+ .setRank(4747));
+
+ // WHEN the notif is reposted
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)
+ .setRank(89));
+
+ // THEN the listener is notified
+ verify(mCollectionListener).onEntryUpdated(mEntryCaptor.capture());
+
+ NotificationEntry entry = mEntryCaptor.getValue();
+ assertEquals(notif2.key, entry.key());
+ assertEquals(notif2.sbn, entry.sbn());
+ assertEquals(notif2.ranking, entry.ranking());
+ }
+
+ @Test
+ public void testEventDispatchedWhenNotifRemoved() {
+ // GIVEN a collection with one notif
+ mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3));
+ clearInvocations(mCollectionListener);
+
+ PostedNotif notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ NotificationEntry entry = mCollectionListener.getEntry(notif.key);
+ clearInvocations(mCollectionListener);
+
+ // WHEN a notif is retracted
+ mNoMan.retractNotif(notif.sbn, REASON_APP_CANCEL);
+
+ // THEN the listener is notified
+ verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL, false);
+ assertEquals(notif.sbn, entry.sbn());
+ assertEquals(notif.ranking, entry.ranking());
+ }
+
+ @Test
+ public void testRankingsAreUpdatedForOtherNotifs() {
+ // GIVEN a collection with one notif
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)
+ .setRank(47));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+
+ // WHEN a new notif is posted, triggering a rerank
+ mNoMan.setRanking(notif1.sbn.getKey(), new RankingBuilder(notif1.ranking)
+ .setRank(56)
+ .build());
+ mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 77));
+
+ // THEN the ranking is updated on the first entry
+ assertEquals(56, entry1.ranking().getRank());
+ }
+
+ @Test
+ public void testRankingUpdateIsProperlyIssuedToEveryone() {
+ // GIVEN a collection with a couple notifs
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)
+ .setRank(3));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 8)
+ .setRank(2));
+ PostedNotif notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 77)
+ .setRank(1));
+
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+ NotificationEntry entry3 = mCollectionListener.getEntry(notif3.key);
+
+ // WHEN a ranking update is delivered
+ Ranking newRanking1 = new RankingBuilder(notif1.ranking)
+ .setRank(4)
+ .setExplanation("Foo bar")
+ .build();
+ Ranking newRanking2 = new RankingBuilder(notif2.ranking)
+ .setRank(5)
+ .setExplanation("baz buzz")
+ .build();
+ Ranking newRanking3 = new RankingBuilder(notif3.ranking)
+ .setRank(6)
+ .setExplanation("Penguin pizza")
+ .build();
+
+ mNoMan.setRanking(notif1.sbn.getKey(), newRanking1);
+ mNoMan.setRanking(notif2.sbn.getKey(), newRanking2);
+ mNoMan.setRanking(notif3.sbn.getKey(), newRanking3);
+ mNoMan.issueRankingUpdate();
+
+ // THEN all of the NotifEntries have their rankings properly updated
+ assertEquals(newRanking1, entry1.ranking());
+ assertEquals(newRanking2, entry2.ranking());
+ assertEquals(newRanking3, entry3.ranking());
+ }
+
+ @Test
+ public void testNotifEntriesAreNotPersistedAcrossRemovalAndReposting() {
+ // GIVEN a notification that has been posted
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+
+ // WHEN the notification is retracted and then reposted
+ mNoMan.retractNotif(notif1.sbn, REASON_APP_CANCEL);
+ mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3));
+
+ // THEN the new NotificationEntry is a new object
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif1.key);
+ assertNotEquals(entry2, entry1);
+ }
+
+ @Test
+ public void testDismissNotification() throws RemoteException {
+ // GIVEN a collection with a couple notifications and a lifetime extender
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN a notification is manually dismissed
+ DismissedByUserStats stats = new DismissedByUserStats(
+ NotificationStats.DISMISSAL_SHADE,
+ NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
+ NotificationVisibility.obtain(entry2.key(), 7, 2, true));
+
+ mCollection.dismissNotification(entry2, REASON_CLICK, stats);
+
+ // THEN we check for lifetime extension
+ verify(mExtender1).shouldExtendLifetime(entry2, REASON_CLICK);
+
+ // THEN we send the dismissal to system server
+ verify(mStatusBarService).onNotificationClear(
+ notif2.sbn.getPackageName(),
+ notif2.sbn.getTag(),
+ 88,
+ notif2.sbn.getUser().getIdentifier(),
+ notif2.sbn.getKey(),
+ stats.dismissalSurface,
+ stats.dismissalSentiment,
+ stats.notificationVisibility);
+
+ // THEN we fire a remove event
+ verify(mCollectionListener).onEntryRemoved(entry2, REASON_CLICK, true);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDismissingNonExistentNotificationThrows() {
+ // GIVEN a collection that originally had three notifs, but where one was dismissed
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+ PostedNotif notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 99));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+ mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
+
+ // WHEN we try to dismiss a notification that isn't present
+ mCollection.dismissNotification(
+ entry2,
+ REASON_CLICK,
+ new DismissedByUserStats(0, 0, NotificationVisibility.obtain("foo", 47, 3, true)));
+
+ // THEN an exception is thrown
+ }
+
+ @Test
+ public void testLifetimeExtendersAreQueriedWhenNotifRemoved() {
+ // GIVEN a couple notifications and a few lifetime extenders
+ mExtender1.shouldExtendLifetime = true;
+ mExtender2.shouldExtendLifetime = true;
+
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ mCollection.addNotificationLifetimeExtender(mExtender2);
+ mCollection.addNotificationLifetimeExtender(mExtender3);
+
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN a notification is removed
+ mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
+
+ // THEN each extender is asked whether to extend, even if earlier ones return true
+ verify(mExtender1).shouldExtendLifetime(entry2, REASON_UNKNOWN);
+ verify(mExtender2).shouldExtendLifetime(entry2, REASON_UNKNOWN);
+ verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN);
+
+ // THEN the entry is not removed
+ assertTrue(mCollection.getNotifs().contains(entry2));
+
+ // THEN the entry properly records all extenders that returned true
+ assertEquals(Arrays.asList(mExtender1, mExtender2), entry2.mLifetimeExtenders);
+ }
+
+ @Test
+ public void testWhenLastLifetimeExtenderExpiresAllAreReQueried() {
+ // GIVEN a couple notifications and a few lifetime extenders
+ mExtender2.shouldExtendLifetime = true;
+
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ mCollection.addNotificationLifetimeExtender(mExtender2);
+ mCollection.addNotificationLifetimeExtender(mExtender3);
+
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // GIVEN a notification gets lifetime-extended by one of them
+ mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
+ assertTrue(mCollection.getNotifs().contains(entry2));
+ clearInvocations(mExtender1, mExtender2, mExtender3);
+
+ // WHEN the last active extender expires (but new ones become active)
+ mExtender1.shouldExtendLifetime = true;
+ mExtender2.shouldExtendLifetime = false;
+ mExtender3.shouldExtendLifetime = true;
+ mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
+
+ // THEN each extender is re-queried
+ verify(mExtender1).shouldExtendLifetime(entry2, REASON_UNKNOWN);
+ verify(mExtender2).shouldExtendLifetime(entry2, REASON_UNKNOWN);
+ verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN);
+
+ // THEN the entry is not removed
+ assertTrue(mCollection.getNotifs().contains(entry2));
+
+ // THEN the entry properly records all extenders that returned true
+ assertEquals(Arrays.asList(mExtender1, mExtender3), entry2.mLifetimeExtenders);
+ }
+
+ @Test
+ public void testExtendersAreNotReQueriedUntilFinalActiveExtenderExpires() {
+ // GIVEN a couple notifications and a few lifetime extenders
+ mExtender1.shouldExtendLifetime = true;
+ mExtender2.shouldExtendLifetime = true;
+
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ mCollection.addNotificationLifetimeExtender(mExtender2);
+ mCollection.addNotificationLifetimeExtender(mExtender3);
+
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // GIVEN a notification gets lifetime-extended by a couple of them
+ mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
+ assertTrue(mCollection.getNotifs().contains(entry2));
+ clearInvocations(mExtender1, mExtender2, mExtender3);
+
+ // WHEN one (but not all) of the extenders expires
+ mExtender2.shouldExtendLifetime = false;
+ mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
+
+ // THEN the entry is not removed
+ assertTrue(mCollection.getNotifs().contains(entry2));
+
+ // THEN we don't re-query the extenders
+ verify(mExtender1, never()).shouldExtendLifetime(eq(entry2), anyInt());
+ verify(mExtender2, never()).shouldExtendLifetime(eq(entry2), anyInt());
+ verify(mExtender3, never()).shouldExtendLifetime(eq(entry2), anyInt());
+
+ // THEN the entry properly records all extenders that returned true
+ assertEquals(Arrays.asList(mExtender1), entry2.mLifetimeExtenders);
+ }
+
+ @Test
+ public void testNotificationIsRemovedWhenAllLifetimeExtendersExpire() {
+ // GIVEN a couple notifications and a few lifetime extenders
+ mExtender1.shouldExtendLifetime = true;
+ mExtender2.shouldExtendLifetime = true;
+
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ mCollection.addNotificationLifetimeExtender(mExtender2);
+ mCollection.addNotificationLifetimeExtender(mExtender3);
+
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // GIVEN a notification gets lifetime-extended by a couple of them
+ mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
+ assertTrue(mCollection.getNotifs().contains(entry2));
+ clearInvocations(mExtender1, mExtender2, mExtender3);
+
+ // WHEN all of the active extenders expire
+ mExtender2.shouldExtendLifetime = false;
+ mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
+ mExtender1.shouldExtendLifetime = false;
+ mExtender1.callback.onEndLifetimeExtension(mExtender1, entry2);
+
+ // THEN the entry removed
+ assertFalse(mCollection.getNotifs().contains(entry2));
+ verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN, false);
+ }
+
+ @Test
+ public void testLifetimeExtensionIsCanceledWhenNotifIsUpdated() {
+ // GIVEN a few lifetime extenders and a couple notifications
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ mCollection.addNotificationLifetimeExtender(mExtender2);
+ mCollection.addNotificationLifetimeExtender(mExtender3);
+
+ mExtender1.shouldExtendLifetime = true;
+ mExtender2.shouldExtendLifetime = true;
+
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // GIVEN a notification gets lifetime-extended by a couple of them
+ mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
+ assertTrue(mCollection.getNotifs().contains(entry2));
+ clearInvocations(mExtender1, mExtender2, mExtender3);
+
+ // WHEN the notification is reposted
+ mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+
+ // THEN all of the active lifetime extenders are canceled
+ verify(mExtender1).cancelLifetimeExtension(entry2);
+ verify(mExtender2).cancelLifetimeExtension(entry2);
+
+ // THEN the notification is still present
+ assertTrue(mCollection.getNotifs().contains(entry2));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testReentrantCallsToLifetimeExtendersThrow() {
+ // GIVEN a few lifetime extenders and a couple notifications
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ mCollection.addNotificationLifetimeExtender(mExtender2);
+ mCollection.addNotificationLifetimeExtender(mExtender3);
+
+ mExtender1.shouldExtendLifetime = true;
+ mExtender2.shouldExtendLifetime = true;
+
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // GIVEN a notification gets lifetime-extended by a couple of them
+ mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
+ assertTrue(mCollection.getNotifs().contains(entry2));
+ clearInvocations(mExtender1, mExtender2, mExtender3);
+
+ // WHEN a lifetime extender makes a reentrant call during cancelLifetimeExtension()
+ mExtender2.onCancelLifetimeExtension = () -> {
+ mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
+ };
+ // This triggers the call to cancelLifetimeExtension()
+ mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+
+ // THEN an exception is thrown
+ }
+
+ @Test
+ public void testRankingIsUpdatedWhenALifetimeExtendedNotifIsReposted() {
+ // GIVEN a few lifetime extenders and a couple notifications
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ mCollection.addNotificationLifetimeExtender(mExtender2);
+ mCollection.addNotificationLifetimeExtender(mExtender3);
+
+ mExtender1.shouldExtendLifetime = true;
+ mExtender2.shouldExtendLifetime = true;
+
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // GIVEN a notification gets lifetime-extended by a couple of them
+ mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
+ assertTrue(mCollection.getNotifs().contains(entry2));
+ clearInvocations(mExtender1, mExtender2, mExtender3);
+
+ // WHEN the notification is reposted
+ PostedNotif notif2a = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)
+ .setRank(4747)
+ .setExplanation("Some new explanation"));
+
+ // THEN the notification's ranking is properly updated
+ assertEquals(notif2a.ranking, entry2.ranking());
+ }
+
+ private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) {
+ return new NotificationEntryBuilder()
+ .setPkg(pkg)
+ .setId(id)
+ .setTag(tag);
+ }
+
+ private static NotificationEntryBuilder buildNotif(String pkg, int id) {
+ return new NotificationEntryBuilder()
+ .setPkg(pkg)
+ .setId(id);
+ }
+
+ private static class NoManSimulator {
+ private final NotifServiceListener mListener;
+ private final Map<String, Ranking> mRankings = new ArrayMap<>();
+
+ private NoManSimulator(
+ NotifServiceListener listener) {
+ mListener = listener;
+ }
+
+ PostedNotif postNotif(NotificationEntryBuilder builder) {
+ NotificationEntry entry = builder.build();
+ mRankings.put(entry.key(), entry.ranking());
+ mListener.onNotificationPosted(entry.sbn(), buildRankingMap());
+ return new PostedNotif(entry.sbn(), entry.ranking());
+ }
+
+ void retractNotif(StatusBarNotification sbn, int reason) {
+ assertNotNull(mRankings.remove(sbn.getKey()));
+ mListener.onNotificationRemoved(sbn, buildRankingMap(), reason);
+ }
+
+ void issueRankingUpdate() {
+ mListener.onNotificationRankingUpdate(buildRankingMap());
+ }
+
+ void setRanking(String key, Ranking ranking) {
+ mRankings.put(key, ranking);
+ }
+
+ private RankingMap buildRankingMap() {
+ return new RankingMap(mRankings.values().toArray(new Ranking[0]));
+ }
+ }
+
+ private static class PostedNotif {
+ public final String key;
+ public final StatusBarNotification sbn;
+ public final Ranking ranking;
+
+ private PostedNotif(StatusBarNotification sbn,
+ Ranking ranking) {
+ this.key = sbn.getKey();
+ this.sbn = sbn;
+ this.ranking = ranking;
+ }
+ }
+
+ private static class RecordingCollectionListener implements NotifCollectionListener {
+ private final Map<String, NotificationEntry> mLastSeenEntries = new ArrayMap<>();
+
+ @Override
+ public void onEntryAdded(NotificationEntry entry) {
+ mLastSeenEntries.put(entry.key(), entry);
+ }
+
+ @Override
+ public void onEntryUpdated(NotificationEntry entry) {
+ }
+
+ @Override
+ public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) {
+ }
+
+ public NotificationEntry getEntry(String key) {
+ if (!mLastSeenEntries.containsKey(key)) {
+ throw new RuntimeException("Key not found: " + key);
+ }
+ return mLastSeenEntries.get(key);
+ }
+ }
+
+ private static class RecordingLifetimeExtender implements NotifLifetimeExtender {
+ private final String mName;
+
+ public @Nullable OnEndLifetimeExtensionCallback callback;
+ public boolean shouldExtendLifetime = false;
+ public @Nullable Runnable onCancelLifetimeExtension;
+
+ private RecordingLifetimeExtender(String name) {
+ mName = name;
+ }
+
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public void setCallback(OnEndLifetimeExtensionCallback callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public boolean shouldExtendLifetime(
+ NotificationEntry entry,
+ @CancellationReason int reason) {
+ return shouldExtendLifetime;
+ }
+
+ @Override
+ public void cancelLifetimeExtension(NotificationEntry entry) {
+ if (onCancelLifetimeExtension != null) {
+ onCancelLifetimeExtension.run();
+ }
+ }
+ }
+
+ private static final String TEST_PACKAGE = "com.android.test.collection";
+ private static final String TEST_PACKAGE2 = "com.android.test.collection2";
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 8f1b6017aff9..52b3720125c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -91,6 +91,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NavigationBarController;
import com.android.systemui.statusbar.NotificationEntryBuilder;
@@ -107,7 +108,7 @@ import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineInitializer;
+import com.android.systemui.statusbar.notification.NewNotifPipeline;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -156,6 +157,7 @@ public class StatusBarTest extends SysuiTestCase {
private TestableNotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
private CommandQueue mCommandQueue;
+ @Mock private FeatureFlags mFeatureFlags;
@Mock private LightBarController mLightBarController;
@Mock private StatusBarIconController mStatusBarIconController;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -205,7 +207,7 @@ public class StatusBarTest extends SysuiTestCase {
@Mock private KeyguardBypassController mKeyguardBypassController;
@Mock private InjectionInflationController mInjectionInflationController;
@Mock private DynamicPrivacyController mDynamicPrivacyController;
- @Mock private NotifPipelineInitializer mNotifPipelineInitializer;
+ @Mock private NewNotifPipeline mNewNotifPipeline;
@Mock private ZenModeController mZenModeController;
@Mock private AutoHideController mAutoHideController;
@Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager;
@@ -288,6 +290,7 @@ public class StatusBarTest extends SysuiTestCase {
mStatusBar = new StatusBar(
mContext,
+ mFeatureFlags,
mLightBarController,
mAutoHideController,
mKeyguardUpdateMonitor,
@@ -302,7 +305,7 @@ public class StatusBarTest extends SysuiTestCase {
mDynamicPrivacyController,
mBypassHeadsUpNotifier,
true,
- mNotifPipelineInitializer,
+ () -> mNewNotifPipeline,
new FalsingManagerFake(),
mBroadcastDispatcher,
new RemoteInputQuickSettingsDisabler(