Remove dead code from NotifEntryManager

All removed code is unused; since all changes are relegated to
NotificationEntryManager.java, if this CL still builds, it is proof that
the code in question was not referenced anywhere (assuming no
reflection).

Bug: 200269355
Test: atest SystemUITests
Change-Id: If24a9a92c6af5c2edd4fe1b628bb720a6a12a311
This commit is contained in:
Steve Elliott
2022-07-21 12:56:26 -04:00
parent 5c5931c764
commit c785c4fad5
48 changed files with 60 additions and 7010 deletions

View File

@@ -87,7 +87,6 @@ import com.android.systemui.statusbar.events.PrivacyDotViewController;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
@@ -102,7 +101,6 @@ import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -313,7 +311,6 @@ public class Dependency {
@Inject Lazy<AccessibilityFloatingMenuController> mAccessibilityFloatingMenuController;
@Inject Lazy<StatusBarStateController> mStatusBarStateController;
@Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager;
@Inject Lazy<NotificationGroupAlertTransferHelper> mNotificationGroupAlertTransferHelper;
@Inject Lazy<NotificationGroupManagerLegacy> mNotificationGroupManager;
@Inject Lazy<VisualStabilityManager> mVisualStabilityManager;
@Inject Lazy<NotificationGutsManager> mNotificationGutsManager;
@@ -322,7 +319,6 @@ public class Dependency {
@Inject Lazy<SmartReplyConstants> mSmartReplyConstants;
@Inject Lazy<NotificationListener> mNotificationListener;
@Inject Lazy<NotificationLogger> mNotificationLogger;
@Inject Lazy<NotificationFilter> mNotificationFilter;
@Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil;
@Inject Lazy<SmartReplyController> mSmartReplyController;
@Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler;
@@ -529,8 +525,6 @@ public class Dependency {
mNotificationLockscreenUserManager::get);
mProviders.put(VisualStabilityManager.class, mVisualStabilityManager::get);
mProviders.put(NotificationGroupManagerLegacy.class, mNotificationGroupManager::get);
mProviders.put(NotificationGroupAlertTransferHelper.class,
mNotificationGroupAlertTransferHelper::get);
mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get);
mProviders.put(NotificationGutsManager.class, mNotificationGutsManager::get);
mProviders.put(NotificationRemoteInputManager.class,
@@ -538,7 +532,6 @@ public class Dependency {
mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get);
mProviders.put(NotificationListener.class, mNotificationListener::get);
mProviders.put(NotificationLogger.class, mNotificationLogger::get);
mProviders.put(NotificationFilter.class, mNotificationFilter::get);
mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get);
mProviders.put(SmartReplyController.class, mSmartReplyController::get);
mProviders.put(RemoteInputQuickSettingsDisabler.class,

View File

@@ -36,7 +36,7 @@ import java.util.stream.Stream;
* remove notifications that appear on screen for a period of time and dismiss themselves at the
* appropriate time. These include heads up notifications and ambient pulses.
*/
public abstract class AlertingNotificationManager implements NotificationLifetimeExtender {
public abstract class AlertingNotificationManager {
private static final String TAG = "AlertNotifManager";
protected final Clock mClock = new Clock();
protected final ArrayMap<String, AlertEntry> mAlertEntries = new ArrayMap<>();
@@ -47,14 +47,6 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
mHandler = handler;
}
/**
* This is the list of entries that have already been removed from the
* NotificationManagerService side, but we keep it to prevent the UI from looking weird and
* will remove when possible. See {@link NotificationLifetimeExtender}
*/
protected final ArraySet<NotificationEntry> mExtendedLifetimeAlertEntries = new ArraySet<>();
protected NotificationSafeToRemoveCallback mNotificationLifetimeFinishedCallback;
protected int mMinimumDisplayTime;
protected int mAutoDismissNotificationDecay;
private final Handler mHandler;
@@ -209,12 +201,6 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
onAlertEntryRemoved(alertEntry);
entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
alertEntry.reset();
if (mExtendedLifetimeAlertEntries.contains(entry)) {
if (mNotificationLifetimeFinishedCallback != null) {
mNotificationLifetimeFinishedCallback.onSafeToRemove(key);
}
mExtendedLifetimeAlertEntries.remove(entry);
}
}
/**
@@ -243,19 +229,6 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
|| alertEntry.mEntry.isRowDismissed();
}
///////////////////////////////////////////////////////////////////////////////////////////////
// NotificationLifetimeExtender Methods
@Override
public void setCallback(NotificationSafeToRemoveCallback callback) {
mNotificationLifetimeFinishedCallback = callback;
}
@Override
public boolean shouldExtendLifetime(NotificationEntry entry) {
return !canRemoveImmediately(entry.getKey());
}
/**
* @param key
* @return true if the entry is pinned
@@ -280,20 +253,6 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
return 0;
}
@Override
public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) {
if (shouldExtend) {
mExtendedLifetimeAlertEntries.add(entry);
// We need to make sure that entries are stopping to alert eventually, let's remove
// this as soon as possible.
AlertEntry alertEntry = mAlertEntries.get(entry.getKey());
alertEntry.removeAsSoonAsPossible();
} else {
mExtendedLifetimeAlertEntries.remove(entry);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////
protected class AlertEntry implements Comparable<AlertEntry> {
@Nullable public NotificationEntry mEntry;
public long mPostTime;

View File

@@ -1,65 +0,0 @@
package com.android.systemui.statusbar;
import androidx.annotation.NonNull;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* Interface for anything that may need to keep notifications managed even after
* {@link NotificationListener} removes it. The lifetime extender is in charge of performing the
* callback when the notification is then safe to remove.
*/
public interface NotificationLifetimeExtender {
/**
* Set the handler to callback to when the notification is safe to remove.
*
* @param callback the handler to callback
*/
void setCallback(@NonNull NotificationSafeToRemoveCallback callback);
/**
* Determines whether or not the extender needs the notification kept after removal.
*
* @param entry the entry containing the notification to check
* @return true if the notification lifetime should be extended
*/
boolean shouldExtendLifetime(@NonNull NotificationEntry entry);
/**
* It's possible that a notification was canceled before it ever became visible. This callback
* gives lifetime extenders a chance to make sure it shows up. For example if a foreground
* service is canceled too quickly but we still want to make sure a FGS notification shows.
* @param pendingEntry the canceled (but pending) entry
* @return true if the notification lifetime should be extended
*/
default boolean shouldExtendLifetimeForPendingNotification(
@NonNull NotificationEntry pendingEntry) {
return false;
}
/**
* Sets whether or not the lifetime should be managed by the extender. In practice, if
* shouldManage is true, this is where the extender starts managing the entry internally and is
* now responsible for calling {@link NotificationSafeToRemoveCallback#onSafeToRemove(String)}
* when the entry is safe to remove. If shouldManage is false, the extender no longer needs to
* worry about it (either because we will be removing it anyway or the entry is no longer
* removed due to an update).
*
* @param entry the entry that needs an extended lifetime
* @param shouldManage true if the extender should manage the entry now, false otherwise
*/
void setShouldManageLifetime(@NonNull NotificationEntry entry, boolean shouldManage);
/**
* The callback for when the notification is now safe to remove (i.e. its lifetime has ended).
*/
interface NotificationSafeToRemoveCallback {
/**
* Called when the lifetime extender determines it's safe to remove.
*
* @param key key of the entry that is now safe to remove
*/
void onSafeToRemove(String key);
}
}

View File

@@ -24,7 +24,6 @@ import android.app.RemoteInput;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -32,7 +31,6 @@ import android.os.SystemProperties;
import android.os.UserManager;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Pair;
@@ -47,12 +45,10 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
@@ -75,7 +71,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import dagger.Lazy;
@@ -100,8 +95,6 @@ public class NotificationRemoteInputManager implements Dumpable {
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final SmartReplyController mSmartReplyController;
private final NotificationVisibilityProvider mVisibilityProvider;
private final NotificationEntryManager mEntryManager;
private final Handler mMainHandler;
private final ActionClickLogger mLogger;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
@@ -110,7 +103,6 @@ public class NotificationRemoteInputManager implements Dumpable {
protected final NotifPipelineFlags mNotifPipelineFlags;
private final UserManager mUserManager;
private final KeyguardManager mKeyguardManager;
private final RemoteInputNotificationRebuilder mRebuilder;
private final StatusBarStateController mStatusBarStateController;
private final RemoteInputUriController mRemoteInputUriController;
private final NotificationClickNotifier mClickNotifier;
@@ -265,10 +257,8 @@ public class NotificationRemoteInputManager implements Dumpable {
SmartReplyController smartReplyController,
NotificationVisibilityProvider visibilityProvider,
NotificationEntryManager notificationEntryManager,
RemoteInputNotificationRebuilder rebuilder,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
StatusBarStateController statusBarStateController,
@Main Handler mainHandler,
RemoteInputUriController remoteInputUriController,
NotificationClickNotifier clickNotifier,
ActionClickLogger logger,
@@ -278,14 +268,11 @@ public class NotificationRemoteInputManager implements Dumpable {
mLockscreenUserManager = lockscreenUserManager;
mSmartReplyController = smartReplyController;
mVisibilityProvider = visibilityProvider;
mEntryManager = notificationEntryManager;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mMainHandler = mainHandler;
mLogger = logger;
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mRebuilder = rebuilder;
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mStatusBarStateController = statusBarStateController;
mRemoteInputUriController = remoteInputUriController;
@@ -788,238 +775,4 @@ public class NotificationRemoteInputManager implements Dumpable {
/** Called when the RemoteInputController is attached to the manager */
void setRemoteInputController(@NonNull RemoteInputController remoteInputController);
}
@VisibleForTesting
protected class LegacyRemoteInputLifetimeExtender implements RemoteInputListener, Dumpable {
/**
* How long to wait before auto-dismissing a notification that was kept for remote input,
* and has now sent a remote input. We auto-dismiss, because the app may not see a reason to
* cancel these given that they technically don't exist anymore. We wait a bit in case the
* app issues an update.
*/
private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200;
/**
* Notifications that are already removed but are kept around because we want to show the
* remote input history. See {@link RemoteInputHistoryExtender} and
* {@link SmartReplyHistoryExtender}.
*/
protected final ArraySet<String> mKeysKeptForRemoteInputHistory = new ArraySet<>();
/**
* Notifications that are already removed but are kept around because the remote input is
* actively being used (i.e. user is typing in it). See {@link RemoteInputActiveExtender}.
*/
protected final ArraySet<NotificationEntry> mEntriesKeptForRemoteInputActive =
new ArraySet<>();
protected NotificationLifetimeExtender.NotificationSafeToRemoveCallback
mNotificationLifetimeFinishedCallback;
protected final ArrayList<NotificationLifetimeExtender> mLifetimeExtenders =
new ArrayList<>();
private RemoteInputController mRemoteInputController;
LegacyRemoteInputLifetimeExtender() {
addLifetimeExtenders();
}
/**
* Adds all the notification lifetime extenders. Each extender represents a reason for the
* NotificationRemoteInputManager to keep a notification lifetime extended.
*/
protected void addLifetimeExtenders() {
mLifetimeExtenders.add(new RemoteInputHistoryExtender());
mLifetimeExtenders.add(new SmartReplyHistoryExtender());
mLifetimeExtenders.add(new RemoteInputActiveExtender());
}
@Override
public void setRemoteInputController(@NonNull RemoteInputController remoteInputController) {
mRemoteInputController= remoteInputController;
}
@Override
public void onRemoteInputSent(@NonNull NotificationEntry entry) {
if (FORCE_REMOTE_INPUT_HISTORY
&& isNotificationKeptForRemoteInputHistory(entry.getKey())) {
mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey());
} else if (mEntriesKeptForRemoteInputActive.contains(entry)) {
// We're currently holding onto this notification, but from the apps point of
// view it is already canceled, so we'll need to cancel it on the apps behalf
// after sending - unless the app posts an update in the mean time, so wait a
// bit.
mMainHandler.postDelayed(() -> {
if (mEntriesKeptForRemoteInputActive.remove(entry)) {
mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey());
}
}, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
}
}
@Override
public void onPanelCollapsed() {
for (int i = 0; i < mEntriesKeptForRemoteInputActive.size(); i++) {
NotificationEntry entry = mEntriesKeptForRemoteInputActive.valueAt(i);
if (mRemoteInputController != null) {
mRemoteInputController.removeRemoteInput(entry, null);
}
if (mNotificationLifetimeFinishedCallback != null) {
mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey());
}
}
mEntriesKeptForRemoteInputActive.clear();
}
@Override
public boolean isNotificationKeptForRemoteInputHistory(@NonNull String key) {
return mKeysKeptForRemoteInputHistory.contains(key);
}
@Override
public void releaseNotificationIfKeptForRemoteInputHistory(
@NonNull NotificationEntry entry) {
final String key = entry.getKey();
if (isNotificationKeptForRemoteInputHistory(key)) {
mMainHandler.postDelayed(() -> {
if (isNotificationKeptForRemoteInputHistory(key)) {
mNotificationLifetimeFinishedCallback.onSafeToRemove(key);
}
}, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
}
}
@VisibleForTesting
public Set<NotificationEntry> getEntriesKeptForRemoteInputActive() {
return mEntriesKeptForRemoteInputActive;
}
@Override
public void dump(@NonNull PrintWriter pw,
@NonNull String[] args) {
pw.println("LegacyRemoteInputLifetimeExtender:");
pw.print(" mKeysKeptForRemoteInputHistory: ");
pw.println(mKeysKeptForRemoteInputHistory);
pw.print(" mEntriesKeptForRemoteInputActive: ");
pw.println(mEntriesKeptForRemoteInputActive);
}
/**
* NotificationRemoteInputManager has multiple reasons to keep notification lifetime
* extended so we implement multiple NotificationLifetimeExtenders
*/
protected abstract class RemoteInputExtender implements NotificationLifetimeExtender {
@Override
public void setCallback(NotificationSafeToRemoveCallback callback) {
if (mNotificationLifetimeFinishedCallback == null) {
mNotificationLifetimeFinishedCallback = callback;
}
}
}
/**
* Notification is kept alive as it was cancelled in response to a remote input interaction.
* This allows us to show what you replied and allows you to continue typing into it.
*/
protected class RemoteInputHistoryExtender extends RemoteInputExtender {
@Override
public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
return shouldKeepForRemoteInputHistory(entry);
}
@Override
public void setShouldManageLifetime(NotificationEntry entry,
boolean shouldExtend) {
if (shouldExtend) {
StatusBarNotification newSbn = mRebuilder.rebuildForRemoteInputReply(entry);
entry.onRemoteInputInserted();
if (newSbn == null) {
return;
}
mEntryManager.updateNotification(newSbn, null);
// Ensure the entry hasn't already been removed. This can happen if there is an
// inflation exception while updating the remote history
if (entry.isRemoved()) {
return;
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Keeping notification around after sending remote input "
+ entry.getKey());
}
mKeysKeptForRemoteInputHistory.add(entry.getKey());
} else {
mKeysKeptForRemoteInputHistory.remove(entry.getKey());
}
}
}
/**
* Notification is kept alive for smart reply history. Similar to REMOTE_INPUT_HISTORY but
* with {@link SmartReplyController} specific logic
*/
protected class SmartReplyHistoryExtender extends RemoteInputExtender {
@Override
public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
return shouldKeepForSmartReplyHistory(entry);
}
@Override
public void setShouldManageLifetime(NotificationEntry entry,
boolean shouldExtend) {
if (shouldExtend) {
StatusBarNotification newSbn = mRebuilder.rebuildForCanceledSmartReplies(entry);
if (newSbn == null) {
return;
}
mEntryManager.updateNotification(newSbn, null);
if (entry.isRemoved()) {
return;
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Keeping notification around after sending smart reply "
+ entry.getKey());
}
mKeysKeptForRemoteInputHistory.add(entry.getKey());
} else {
mKeysKeptForRemoteInputHistory.remove(entry.getKey());
mSmartReplyController.stopSending(entry);
}
}
}
/**
* Notification is kept alive because the user is still using the remote input
*/
protected class RemoteInputActiveExtender extends RemoteInputExtender {
@Override
public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
return isRemoteInputActive(entry);
}
@Override
public void setShouldManageLifetime(NotificationEntry entry,
boolean shouldExtend) {
if (shouldExtend) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Keeping notification around while remote input active "
+ entry.getKey());
}
mEntriesKeptForRemoteInputActive.add(entry);
} else {
mEntriesKeptForRemoteInputActive.remove(entry);
}
}
}
}
}

View File

@@ -115,10 +115,8 @@ public interface CentralSurfacesDependenciesModule {
smartReplyController,
visibilityProvider,
notificationEntryManager,
rebuilder,
centralSurfacesOptionalLazy,
statusBarStateController,
mainHandler,
remoteInputUriController,
clickNotifier,
actionClickLogger,

View File

@@ -15,66 +15,22 @@
*/
package com.android.systemui.statusbar.notification;
import static android.service.notification.NotificationListenerService.REASON_ERROR;
import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
import android.app.Notification;
import android.app.NotificationChannel;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
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.ArraySet;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
import com.android.systemui.statusbar.NotificationUiAdjustment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationRanker;
import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationRankerStub;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.util.Assert;
import com.android.systemui.util.Compile;
import com.android.systemui.util.leak.LeakDetector;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import dagger.Lazy;
/**
* NotificationEntryManager is responsible for the adding, removing, and updating of
@@ -90,33 +46,10 @@ import dagger.Lazy;
* inflated, an entry moves into the active state, where it _could_ potentially be shown to the
* user. After an entry makes its way into the active state, we sort and filter the entire set to
* repopulate the visible set.
*
* There are a few different things that other classes may be interested in, and most of them
* involve the current set of notifications. Here's a brief overview of things you may want to know:
* @see #getVisibleNotifications() for the visible set
* @see #getActiveNotificationUnfiltered(String) to check if a key exists
* @see #getPendingNotificationsIterator() for an iterator over the pending notifications
* @see #getPendingOrActiveNotif(String) to find a notification exists for that key in any list
* @see #getActiveNotificationsForCurrentUser() to see every notification that the current user owns
*/
public class NotificationEntryManager implements
CommonNotifCollection,
Dumpable,
VisualStabilityManager.Callback {
public class NotificationEntryManager implements VisualStabilityManager.Callback {
private final NotificationEntryManagerLogger mLogger;
private final NotificationGroupManagerLegacy mGroupManager;
private final NotifPipelineFlags mNotifPipelineFlags;
private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy;
private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
private final LeakDetector mLeakDetector;
private final IStatusBarService mStatusBarService;
private final DumpManager mDumpManager;
private final Executor mBgExecutor;
private final Set<NotificationEntry> mAllNotifications = new ArraySet<>();
private final Set<NotificationEntry> mReadOnlyAllNotifications =
Collections.unmodifiableSet(mAllNotifications);
/** Pending notifications are ones awaiting inflation */
@VisibleForTesting
@@ -129,96 +62,14 @@ public class NotificationEntryManager implements
/** This is the list of "active notifications for this user in this context" */
@VisibleForTesting
protected final ArrayList<NotificationEntry> mSortedAndFiltered = new ArrayList<>();
private final List<NotificationEntry> mReadOnlyNotifications =
Collections.unmodifiableList(mSortedAndFiltered);
private final Map<NotificationEntry, NotificationLifetimeExtender> mRetainedNotifications =
new ArrayMap<>();
private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
private LegacyNotificationRanker mRanker = new LegacyNotificationRankerStub();
private RankingMap mLatestRankingMap;
@VisibleForTesting
final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
= new ArrayList<>();
private final List<NotificationEntryListener> mNotificationEntryListeners = new ArrayList<>();
private final List<NotificationRemoveInterceptor> mRemoveInterceptors = new ArrayList<>();
/**
* Injected constructor. See {@link NotificationsModule}.
*/
public NotificationEntryManager(
NotificationEntryManagerLogger logger,
NotificationGroupManagerLegacy groupManager,
NotifPipelineFlags notifPipelineFlags,
Lazy<NotificationRowBinder> notificationRowBinderLazy,
Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
LeakDetector leakDetector,
IStatusBarService statusBarService,
DumpManager dumpManager,
@Background Executor bgExecutor
) {
public NotificationEntryManager(NotificationEntryManagerLogger logger) {
mLogger = logger;
mGroupManager = groupManager;
mNotifPipelineFlags = notifPipelineFlags;
mNotificationRowBinderLazy = notificationRowBinderLazy;
mRemoteInputManagerLazy = notificationRemoteInputManagerLazy;
mLeakDetector = leakDetector;
mStatusBarService = statusBarService;
mDumpManager = dumpManager;
mBgExecutor = bgExecutor;
}
/** Once called, the NEM will start processing notification events from system server. */
public void initialize(
NotificationListener notificationListener,
LegacyNotificationRanker ranker) {
mRanker = ranker;
notificationListener.addNotificationHandler(mNotifListener);
mDumpManager.registerDumpable(this);
}
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("NotificationEntryManager state:");
pw.println(" mAllNotifications=");
if (mAllNotifications.size() == 0) {
pw.println("null");
} else {
int i = 0;
for (NotificationEntry entry : mAllNotifications) {
dumpEntry(pw, " ", i, entry);
i++;
}
}
pw.print(" mPendingNotifications=");
if (mPendingNotifications.size() == 0) {
pw.println("null");
} else {
for (NotificationEntry entry : mPendingNotifications.values()) {
pw.println(entry.getSbn());
}
}
pw.println(" Remove interceptors registered:");
for (NotificationRemoveInterceptor interceptor : mRemoveInterceptors) {
pw.println(" " + interceptor.getClass().getSimpleName());
}
pw.println(" Lifetime extenders registered:");
for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
pw.println(" " + extender.getClass().getSimpleName());
}
pw.println(" Lifetime-extended notifications:");
if (mRetainedNotifications.isEmpty()) {
pw.println(" None");
} else {
for (Map.Entry<NotificationEntry, NotificationLifetimeExtender> entry
: mRetainedNotifications.entrySet()) {
pw.println(" " + entry.getKey().getSbn() + " retained by "
+ entry.getValue().getClass().getName());
}
}
}
/** Adds a {@link NotificationEntryListener}. */
@@ -234,500 +85,11 @@ public class NotificationEntryManager implements
mNotificationEntryListeners.remove(listener);
}
/** Add a {@link NotificationRemoveInterceptor}. */
public void addNotificationRemoveInterceptor(NotificationRemoveInterceptor interceptor) {
mRemoveInterceptors.add(interceptor);
}
/** Remove a {@link NotificationRemoveInterceptor} */
public void removeNotificationRemoveInterceptor(NotificationRemoveInterceptor interceptor) {
mRemoveInterceptors.remove(interceptor);
}
/** Adds multiple {@link NotificationLifetimeExtender}s. */
public void addNotificationLifetimeExtenders(List<NotificationLifetimeExtender> extenders) {
for (NotificationLifetimeExtender extender : extenders) {
addNotificationLifetimeExtender(extender);
}
}
/** Adds a {@link NotificationLifetimeExtender}. */
public void addNotificationLifetimeExtender(NotificationLifetimeExtender extender) {
mNotificationLifetimeExtenders.add(extender);
extender.setCallback(key -> removeNotification(key, mLatestRankingMap,
UNDEFINED_DISMISS_REASON));
}
@Override
public void onChangeAllowed() {
updateNotifications("reordering is now allowed");
}
/**
* User requests a notification to be removed.
*
* @param n the notification to remove.
* @param reason why it is being removed e.g. {@link NotificationListenerService#REASON_CANCEL},
* or 0 if unknown.
*/
public void performRemoveNotification(
StatusBarNotification n,
@NonNull DismissedByUserStats stats,
int reason
) {
removeNotificationInternal(
n.getKey(),
null,
stats.notificationVisibility,
false /* forceRemove */,
stats,
reason);
}
private NotificationVisibility obtainVisibility(String key) {
NotificationEntry e = mActiveNotifications.get(key);
final int rank;
if (e != null) {
rank = e.getRanking().getRank();
} else {
rank = 0;
}
final int count = mActiveNotifications.size();
NotificationVisibility.NotificationLocation location =
NotificationLogger.getNotificationLocation(getActiveNotificationUnfiltered(key));
return NotificationVisibility.obtain(key, rank, count, true, location);
}
private void abortExistingInflation(String key, String reason) {
if (mPendingNotifications.containsKey(key)) {
NotificationEntry entry = mPendingNotifications.get(key);
entry.abortTask();
mPendingNotifications.remove(key);
mLogger.logInflationAborted(key, "pending", reason);
}
NotificationEntry addedEntry = getActiveNotificationUnfiltered(key);
if (addedEntry != null) {
addedEntry.abortTask();
mLogger.logInflationAborted(key, "active", reason);
}
}
/**
* Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
* about the failure.
*
* WARNING: this will call back into us. Don't hold any locks.
*/
private void handleInflationException(StatusBarNotification n, Exception e) {
removeNotificationInternal(
n.getKey(),
null,
null,
true /* forceRemove */,
null /* dismissedByUserStats */,
REASON_ERROR);
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onInflationError(n, e);
}
}
private final InflationCallback mInflationCallback = new InflationCallback() {
@Override
public void handleInflationException(NotificationEntry entry, Exception e) {
Trace.beginSection("NotificationEntryManager.handleInflationException");
NotificationEntryManager.this.handleInflationException(entry.getSbn(), e);
Trace.endSection();
}
@Override
public void onAsyncInflationFinished(NotificationEntry entry) {
Trace.beginSection("NotificationEntryManager.onAsyncInflationFinished");
mPendingNotifications.remove(entry.getKey());
// If there was an async task started after the removal, we don't want to add it back to
// the list, otherwise we might get leaks.
if (!entry.isRowRemoved()) {
boolean isNew = getActiveNotificationUnfiltered(entry.getKey()) == null;
mLogger.logNotifInflated(entry.getKey(), isNew);
if (isNew) {
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onEntryInflated(entry);
}
addActiveNotification(entry);
updateNotifications("onAsyncInflationFinished");
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onNotificationAdded(entry);
}
} else {
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onEntryReinflated(entry);
}
}
}
Trace.endSection();
}
};
private final NotificationHandler mNotifListener = new NotificationHandler() {
@Override
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
final boolean isUpdateToInflatedNotif = mActiveNotifications.containsKey(sbn.getKey());
if (isUpdateToInflatedNotif) {
updateNotification(sbn, rankingMap);
} else {
addNotification(sbn, rankingMap);
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
removeNotification(sbn.getKey(), rankingMap, UNDEFINED_DISMISS_REASON);
}
@Override
public void onNotificationRemoved(
StatusBarNotification sbn,
RankingMap rankingMap,
int reason) {
removeNotification(sbn.getKey(), rankingMap, reason);
}
@Override
public void onNotificationRankingUpdate(RankingMap rankingMap) {
updateNotificationRanking(rankingMap);
}
@Override
public void onNotificationsInitialized() {
}
@Override
public void onNotificationChannelModified(
String pkgName,
UserHandle user,
NotificationChannel channel,
int modificationType) {
notifyChannelModified(pkgName, user, channel, modificationType);
}
};
/**
* Equivalent to the old NotificationData#add
* @param entry - an entry which is prepared for display
*/
private void addActiveNotification(NotificationEntry entry) {
Assert.isMainThread();
mActiveNotifications.put(entry.getKey(), entry);
mGroupManager.onEntryAdded(entry);
updateRankingAndSort(mRanker.getRankingMap(), "addEntryInternalInternal");
}
/**
* Available so that tests can directly manipulate the list of active notifications easily
*
* @param entry the entry to add directly to the visible notification map
*/
@VisibleForTesting
public void addActiveNotificationForTest(NotificationEntry entry) {
mActiveNotifications.put(entry.getKey(), entry);
mGroupManager.onEntryAdded(entry);
reapplyFilterAndSort("addVisibleNotification");
}
@VisibleForTesting
protected void removeNotification(String key, RankingMap ranking, int reason) {
removeNotificationInternal(
key,
ranking,
obtainVisibility(key),
false /* forceRemove */,
null /* dismissedByUserStats */,
reason);
}
/**
* Internally remove a notification because system server has reported the notification
* should be removed OR the user has manually dismissed the notification
* @param dismissedByUserStats non-null if the user manually dismissed the notification
*/
private void removeNotificationInternal(
String key,
@Nullable RankingMap ranking,
@Nullable NotificationVisibility visibility,
boolean forceRemove,
DismissedByUserStats dismissedByUserStats,
int reason) {
Trace.beginSection("NotificationEntryManager.removeNotificationInternal");
final NotificationEntry entry = getActiveNotificationUnfiltered(key);
for (NotificationRemoveInterceptor interceptor : mRemoveInterceptors) {
if (interceptor.onNotificationRemoveRequested(key, entry, reason)) {
// Remove intercepted; log and skip
mLogger.logRemovalIntercepted(key);
Trace.endSection();
return;
}
}
boolean lifetimeExtended = false;
// Notification was canceled before it got inflated
if (entry == null) {
NotificationEntry pendingEntry = mPendingNotifications.get(key);
if (pendingEntry != null) {
for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
if (extender.shouldExtendLifetimeForPendingNotification(pendingEntry)) {
extendLifetime(pendingEntry, extender);
lifetimeExtended = true;
mLogger.logLifetimeExtended(key, extender.getClass().getName(), "pending");
}
}
if (!lifetimeExtended) {
// At this point, we are guaranteed the notification will be removed
abortExistingInflation(key, "removeNotification");
// Fix for b/201097913: NotifCollectionListener#onEntryRemoved specifies that
// #onEntryRemoved should be called when a notification is cancelled,
// regardless of whether the notification was pending or active.
// Note that mNotificationEntryListeners are NOT notified of #onEntryRemoved
// because for that interface, #onEntryRemoved should only be called for
// active entries, NOT pending ones.
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryRemoved(pendingEntry, REASON_UNKNOWN);
}
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryCleanUp(pendingEntry);
}
mAllNotifications.remove(pendingEntry);
mLeakDetector.trackGarbage(pendingEntry);
}
}
} else {
// If a manager needs to keep the notification around for whatever reason, we
// keep the notification
boolean entryDismissed = entry.isRowDismissed();
if (!forceRemove && !entryDismissed) {
for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
if (extender.shouldExtendLifetime(entry)) {
mLatestRankingMap = ranking;
extendLifetime(entry, extender);
lifetimeExtended = true;
mLogger.logLifetimeExtended(key, extender.getClass().getName(), "active");
break;
}
}
}
if (!lifetimeExtended) {
// At this point, we are guaranteed the notification will be removed
abortExistingInflation(key, "removeNotification");
mAllNotifications.remove(entry);
// Ensure any managers keeping the lifetime extended stop managing the entry
cancelLifetimeExtension(entry);
if (entry.rowExists()) {
entry.removeRow();
}
// Let's remove the children if this was a summary
handleGroupSummaryRemoved(key);
removeVisibleNotification(key);
updateNotifications("removeNotificationInternal");
final boolean removedByUser = dismissedByUserStats != null;
mLogger.logNotifRemoved(entry.getKey(), removedByUser);
if (removedByUser && visibility != null) {
sendNotificationRemovalToServer(entry.getSbn(), dismissedByUserStats);
}
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onEntryRemoved(entry, visibility, removedByUser, reason);
}
for (NotifCollectionListener listener : mNotifCollectionListeners) {
// NEM doesn't have a good knowledge of reasons so defaulting to unknown.
listener.onEntryRemoved(entry, REASON_UNKNOWN);
}
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryCleanUp(entry);
}
mLeakDetector.trackGarbage(entry);
}
}
Trace.endSection();
}
private void sendNotificationRemovalToServer(
StatusBarNotification notification,
DismissedByUserStats dismissedByUserStats) {
mBgExecutor.execute(() -> {
try {
mStatusBarService.onNotificationClear(
notification.getPackageName(),
notification.getUser().getIdentifier(),
notification.getKey(),
dismissedByUserStats.dismissalSurface,
dismissedByUserStats.dismissalSentiment,
dismissedByUserStats.notificationVisibility);
} catch (RemoteException ex) {
// system process is dead if we're here.
}
});
}
/**
* Ensures that the group children are cancelled immediately when the group summary is cancelled
* instead of waiting for the notification manager to send all cancels. Otherwise this could
* lead to flickers.
*
* This also ensures that the animation looks nice and only consists of a single disappear
* animation instead of multiple.
* @param key the key of the notification was removed
*
*/
private void handleGroupSummaryRemoved(String key) {
NotificationEntry entry = getActiveNotificationUnfiltered(key);
if (entry != null && entry.rowExists() && entry.isSummaryWithChildren()) {
if (entry.getSbn().getOverrideGroupKey() != null && !entry.isRowDismissed()) {
// We don't want to remove children for autobundled notifications as they are not
// always cancelled. We only remove them if they were dismissed by the user.
return;
}
List<NotificationEntry> childEntries = entry.getAttachedNotifChildren();
if (childEntries == null) {
return;
}
for (int i = 0; i < childEntries.size(); i++) {
NotificationEntry childEntry = childEntries.get(i);
boolean isForeground = (entry.getSbn().getNotification().flags
& Notification.FLAG_FOREGROUND_SERVICE) != 0;
boolean keepForReply =
mRemoteInputManagerLazy.get().shouldKeepForRemoteInputHistory(childEntry)
|| mRemoteInputManagerLazy.get().shouldKeepForSmartReplyHistory(childEntry);
if (isForeground || keepForReply) {
// the child is a foreground service notification which we can't remove or it's
// a child we're keeping around for reply!
continue;
}
childEntry.setKeepInParent(true);
// we need to set this state earlier as otherwise we might generate some weird
// animations
childEntry.removeRow();
}
}
}
private void addNotificationInternal(
StatusBarNotification notification,
RankingMap rankingMap) throws InflationException {
Trace.beginSection("NotificationEntryManager.addNotificationInternal");
String key = notification.getKey();
if (DEBUG) {
Log.d(TAG, "addNotification key=" + key);
}
updateRankingAndSort(rankingMap, "addNotificationInternal");
Ranking ranking = new Ranking();
rankingMap.getRanking(key, ranking);
NotificationEntry entry = mPendingNotifications.get(key);
if (entry != null) {
entry.setSbn(notification);
entry.setRanking(ranking);
} else {
entry = new NotificationEntry(
notification,
ranking,
SystemClock.uptimeMillis());
mAllNotifications.add(entry);
mLeakDetector.trackInstance(entry);
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryInit(entry);
}
}
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryBind(entry, notification);
}
mPendingNotifications.put(key, entry);
mLogger.logNotifAdded(entry.getKey());
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onPendingEntryAdded(entry);
}
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryAdded(entry);
}
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onRankingApplied();
}
Trace.endSection();
}
public void addNotification(StatusBarNotification notification, RankingMap ranking) {
try {
addNotificationInternal(notification, ranking);
} catch (InflationException e) {
handleInflationException(notification, e);
}
}
private void updateNotificationInternal(StatusBarNotification notification,
RankingMap ranking) throws InflationException {
Trace.beginSection("NotificationEntryManager.updateNotificationInternal");
if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
final String key = notification.getKey();
abortExistingInflation(key, "updateNotification");
final NotificationEntry entry = getActiveNotificationUnfiltered(key);
if (entry == null) {
Trace.endSection();
return;
}
// Notification is updated so it is essentially re-added and thus alive again. Don't need
// to keep its lifetime extended.
cancelLifetimeExtension(entry);
updateRankingAndSort(ranking, "updateNotificationInternal");
StatusBarNotification oldSbn = entry.getSbn();
entry.setSbn(notification);
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryBind(entry, notification);
}
mGroupManager.onEntryUpdated(entry, oldSbn);
mLogger.logNotifUpdated(entry.getKey());
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onPreEntryUpdated(entry);
}
final boolean fromSystem = ranking != null;
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryUpdated(entry, fromSystem);
}
updateNotifications("updateNotificationInternal");
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onPostEntryUpdated(entry);
}
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onRankingApplied();
}
Trace.endSection();
}
public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
try {
updateNotificationInternal(notification, ranking);
} catch (InflationException e) {
handleInflationException(notification, e);
}
}
/**
* Update the notifications
* @param reason why the notifications are updating
@@ -736,128 +98,6 @@ public class NotificationEntryManager implements
mLogger.logUseWhileNewPipelineActive("updateNotifications", reason);
}
public void updateNotificationRanking(RankingMap rankingMap) {
Trace.beginSection("NotificationEntryManager.updateNotificationRanking");
List<NotificationEntry> entries = new ArrayList<>();
entries.addAll(getVisibleNotifications());
entries.addAll(mPendingNotifications.values());
// Has a copy of the current UI adjustments.
ArrayMap<String, NotificationUiAdjustment> oldAdjustments = new ArrayMap<>();
ArrayMap<String, Integer> oldImportances = new ArrayMap<>();
for (NotificationEntry entry : entries) {
NotificationUiAdjustment adjustment =
NotificationUiAdjustment.extractFromNotificationEntry(entry);
oldAdjustments.put(entry.getKey(), adjustment);
oldImportances.put(entry.getKey(), entry.getImportance());
}
// Populate notification entries from the new rankings.
updateRankingAndSort(rankingMap, "updateNotificationRanking");
updateRankingOfPendingNotifications(rankingMap);
// By comparing the old and new UI adjustments, reinflate the view accordingly.
for (NotificationEntry entry : entries) {
mNotificationRowBinderLazy.get()
.onNotificationRankingUpdated(
entry,
oldImportances.get(entry.getKey()),
oldAdjustments.get(entry.getKey()),
NotificationUiAdjustment.extractFromNotificationEntry(entry),
mInflationCallback);
}
updateNotifications("updateNotificationRanking");
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onNotificationRankingUpdated(rankingMap);
}
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onRankingUpdate(rankingMap);
}
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onRankingApplied();
}
Trace.endSection();
}
void notifyChannelModified(
String pkgName,
UserHandle user,
NotificationChannel channel,
int modificationType) {
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onNotificationChannelModified(pkgName, user, channel, modificationType);
}
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onNotificationChannelModified(pkgName, user, channel, modificationType);
}
}
private void updateRankingOfPendingNotifications(@Nullable RankingMap rankingMap) {
if (rankingMap == null) {
return;
}
for (NotificationEntry pendingNotification : mPendingNotifications.values()) {
Ranking ranking = new Ranking();
if (rankingMap.getRanking(pendingNotification.getKey(), ranking)) {
pendingNotification.setRanking(ranking);
}
}
}
/**
* @return An iterator for all "pending" notifications. Pending notifications are newly-posted
* notifications whose views have not yet been inflated. In general, the system pretends like
* these don't exist, although there are a couple exceptions.
*/
public Iterable<NotificationEntry> getPendingNotificationsIterator() {
mNotifPipelineFlags.checkLegacyPipelineEnabled();
return mPendingNotifications.values();
}
/**
* Use this method to retrieve a notification entry that has been prepared for presentation.
* Note that the notification may be filtered out and never shown to the user.
*
* @see #getVisibleNotifications() for the currently sorted and filtered list
*
* @return a {@link NotificationEntry} if it has been prepared, else null
*/
public NotificationEntry getActiveNotificationUnfiltered(String key) {
mNotifPipelineFlags.checkLegacyPipelineEnabled();
return mActiveNotifications.get(key);
}
/**
* Gets the pending or visible notification entry with the given key. Returns null if
* notification doesn't exist.
*/
public NotificationEntry getPendingOrActiveNotif(String key) {
mNotifPipelineFlags.checkLegacyPipelineEnabled();
NotificationEntry entry = mPendingNotifications.get(key);
if (entry != null) {
return entry;
}
return mActiveNotifications.get(key);
}
private void extendLifetime(NotificationEntry entry, NotificationLifetimeExtender extender) {
NotificationLifetimeExtender activeExtender = mRetainedNotifications.get(entry);
if (activeExtender != null && activeExtender != extender) {
activeExtender.setShouldManageLifetime(entry, false);
}
mRetainedNotifications.put(entry, extender);
extender.setShouldManageLifetime(entry, true);
}
private void cancelLifetimeExtension(NotificationEntry entry) {
NotificationLifetimeExtender activeExtender = mRetainedNotifications.remove(entry);
if (activeExtender != null) {
activeExtender.setShouldManageLifetime(entry, false);
}
}
/*
* -----
* Annexed from NotificationData below:
@@ -865,59 +105,11 @@ public class NotificationEntryManager implements
* we'll try to keep the behavior the same and can simplify these interfaces in another pass
*/
/** Internalization of NotificationData#remove */
private void removeVisibleNotification(String key) {
// no need to synchronize if we're on the main thread dawg
Assert.isMainThread();
NotificationEntry removed = mActiveNotifications.remove(key);
if (removed == null) return;
mGroupManager.onEntryRemoved(removed);
}
/** @return list of active notifications filtered for the current user */
public List<NotificationEntry> getActiveNotificationsForCurrentUser() {
Trace.beginSection("NotificationEntryManager.getActiveNotificationsForCurrentUser");
Assert.isMainThread();
ArrayList<NotificationEntry> filtered = new ArrayList<>();
final int len = mActiveNotifications.size();
for (int i = 0; i < len; i++) {
NotificationEntry entry = mActiveNotifications.valueAt(i);
if (!mRanker.isNotificationForCurrentProfiles(entry)) {
continue;
}
filtered.add(entry);
}
Trace.endSection();
return filtered;
}
//TODO: Get rid of this in favor of NotificationUpdateHandler#updateNotificationRanking
/**
* @param rankingMap the {@link RankingMap} to apply to the current notification list
* @param reason the reason for calling this method, which will be logged
*/
public void updateRanking(RankingMap rankingMap, String reason) {
Trace.beginSection("NotificationEntryManager.updateRanking");
updateRankingAndSort(rankingMap, reason);
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onRankingApplied();
}
Trace.endSection();
}
/** Resorts / filters the current notification set with the current RankingMap */
public void reapplyFilterAndSort(String reason) {
mLogger.logUseWhileNewPipelineActive("reapplyFilterAndSort", reason);
}
/** Calls to NotificationRankingManager and updates mSortedAndFiltered */
private void updateRankingAndSort(RankingMap rankingMap, String reason) {
mLogger.logUseWhileNewPipelineActive("updateRankingAndSort", reason);
}
/** dump the current active notification list. Called from CentralSurfaces */
public void dump(PrintWriter pw, String indent) {
pw.println("NotificationEntryManager (Legacy)");
@@ -955,55 +147,10 @@ public class NotificationEntryManager implements
pw.println(" notification=" + n.getNotification());
}
/**
* This is the answer to the question "what notifications should the user be seeing right now?"
* These are sorted and filtered, and directly inform the notification shade what to show
*
* @return A read-only list of the currently active notifications
*/
public List<NotificationEntry> getVisibleNotifications() {
mNotifPipelineFlags.checkLegacyPipelineEnabled();
return mReadOnlyNotifications;
}
/**
* Returns a collections containing ALL notifications we know about, including ones that are
* hidden or for other users. See {@link CommonNotifCollection#getAllNotifs()}.
*/
@NonNull
@Override
public Collection<NotificationEntry> getAllNotifs() {
mNotifPipelineFlags.checkLegacyPipelineEnabled();
return mReadOnlyAllNotifications;
}
@Nullable
@Override
public NotificationEntry getEntry(@NonNull String key) {
mNotifPipelineFlags.checkLegacyPipelineEnabled();
return getPendingOrActiveNotif(key);
}
/** @return A count of the active notifications */
public int getActiveNotificationsCount() {
mNotifPipelineFlags.checkLegacyPipelineEnabled();
return mReadOnlyNotifications.size();
}
/**
* @return {@code true} if there is at least one notification that should be visible right now
*/
public boolean hasActiveNotifications() {
mNotifPipelineFlags.checkLegacyPipelineEnabled();
return mReadOnlyNotifications.size() != 0;
}
@Override
public void addCollectionListener(@NonNull NotifCollectionListener listener) {
mNotifCollectionListeners.add(listener);
}
@Override
public void removeCollectionListener(@NonNull NotifCollectionListener listener) {
mNotifCollectionListeners.remove(listener);
}
@@ -1024,9 +171,6 @@ public class NotificationEntryManager implements
boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
}
private static final String TAG = "NotificationEntryMgr";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
/**
* Used when a notification is removed and it doesn't have a reason that maps to one of the
* reasons defined in NotificationListenerService

View File

@@ -1,144 +0,0 @@
/*
* Copyright (C) 2018 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 static com.android.systemui.media.MediaDataManagerKt.isMediaNotification;
import android.Manifest;
import android.app.AppGlobals;
import android.app.Notification;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.service.notification.StatusBarNotification;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.DebugModeFilterProvider;
import javax.inject.Inject;
/** Component which manages the various reasons a notification might be filtered out.*/
// TODO: delete NotificationFilter.java after migrating to new NotifPipeline b/145659174.
// Notification filtering is taken care of across the different Coordinators (mostly
// KeyguardCoordinator.java)
@SysUISingleton
public class NotificationFilter {
private final DebugModeFilterProvider mDebugNotificationFilter;
private final StatusBarStateController mStatusBarStateController;
private final KeyguardEnvironment mKeyguardEnvironment;
private final ForegroundServiceController mForegroundServiceController;
private final NotificationLockscreenUserManager mUserManager;
private final Boolean mIsMediaFlagEnabled;
@Inject
public NotificationFilter(
DebugModeFilterProvider debugNotificationFilter,
StatusBarStateController statusBarStateController,
KeyguardEnvironment keyguardEnvironment,
ForegroundServiceController foregroundServiceController,
NotificationLockscreenUserManager userManager,
MediaFeatureFlag mediaFeatureFlag) {
mDebugNotificationFilter = debugNotificationFilter;
mStatusBarStateController = statusBarStateController;
mKeyguardEnvironment = keyguardEnvironment;
mForegroundServiceController = foregroundServiceController;
mUserManager = userManager;
mIsMediaFlagEnabled = mediaFeatureFlag.getEnabled();
}
/**
* @return true if the provided notification should NOT be shown right now.
*/
public boolean shouldFilterOut(NotificationEntry entry) {
final StatusBarNotification sbn = entry.getSbn();
if (mDebugNotificationFilter.shouldFilterOut(entry)) {
return true;
}
if (!(mKeyguardEnvironment.isDeviceProvisioned()
|| showNotificationEvenIfUnprovisioned(sbn))) {
return true;
}
if (!mKeyguardEnvironment.isNotificationForCurrentProfiles(sbn)) {
return true;
}
if (mUserManager.isLockscreenPublicMode(sbn.getUserId())
&& (sbn.getNotification().visibility == Notification.VISIBILITY_SECRET
|| mUserManager.shouldHideNotifications(sbn.getUserId())
|| mUserManager.shouldHideNotifications(sbn.getKey()))) {
return true;
}
if (mStatusBarStateController.isDozing() && entry.shouldSuppressAmbient()) {
return true;
}
if (!mStatusBarStateController.isDozing() && entry.shouldSuppressNotificationList()) {
return true;
}
if (entry.getRanking().isSuspended()) {
return true;
}
if (mForegroundServiceController.isDisclosureNotification(sbn)
&& !mForegroundServiceController.isDisclosureNeededForUser(sbn.getUserId())) {
// this is a foreground-service disclosure for a user that does not need to show one
return true;
}
if (mIsMediaFlagEnabled && isMediaNotification(sbn)) {
return true;
}
return false;
}
// Q: What kinds of notifications should show during setup?
// A: Almost none! Only things coming from packages with permission
// android.permission.NOTIFICATION_DURING_SETUP that also have special "kind" tags marking them
// as relevant for setup (see below).
private static boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
return showNotificationEvenIfUnprovisioned(AppGlobals.getPackageManager(), sbn);
}
@VisibleForTesting
static boolean showNotificationEvenIfUnprovisioned(IPackageManager packageManager,
StatusBarNotification sbn) {
return checkUidPermission(packageManager, Manifest.permission.NOTIFICATION_DURING_SETUP,
sbn.getUid()) == PackageManager.PERMISSION_GRANTED
&& sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
}
private static int checkUidPermission(IPackageManager packageManager, String permission,
int uid) {
try {
return packageManager.checkUidPermission(permission, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}

View File

@@ -61,11 +61,11 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.icon.IconPack;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
@@ -421,7 +421,7 @@ public final class NotificationEntry extends ListEntry {
* Get the children that are actually attached to this notification's row.
*
* TODO: Seems like most callers here should probably be using
* {@link NotificationGroupManagerLegacy#getChildren}
* {@link GroupMembershipManager#getChildren(ListEntry)}
*/
public @Nullable List<NotificationEntry> getAttachedNotifChildren() {
if (row == null) {

View File

@@ -1,227 +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.collection
import android.app.Notification
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.NotificationManager.IMPORTANCE_MIN
import android.service.notification.NotificationListenerService.Ranking
import android.service.notification.NotificationListenerService.RankingMap
import android.service.notification.StatusBarNotification
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment
import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger
import com.android.systemui.statusbar.notification.NotificationFilter
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationRanker
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.notification.stack.PriorityBucket
import com.android.systemui.statusbar.policy.HeadsUpManager
import dagger.Lazy
import java.util.Objects
import javax.inject.Inject
private const val TAG = "NotifRankingManager"
/**
* NotificationRankingManager is responsible for holding on to the most recent [RankingMap], and
* updating SystemUI's set of [NotificationEntry]s with their own ranking. It also sorts and filters
* a set of entries (but retains none of them). We also set buckets on the entries here since
* bucketing is tied closely to sorting.
*
* For the curious: this class is one iteration closer to null of what used to be called
* NotificationData.java.
*/
open class NotificationRankingManager @Inject constructor(
private val mediaManagerLazy: Lazy<NotificationMediaManager>,
private val groupManager: NotificationGroupManagerLegacy,
private val headsUpManager: HeadsUpManager,
private val notifFilter: NotificationFilter,
private val logger: NotificationEntryManagerLogger,
private val sectionsFeatureManager: NotificationSectionsFeatureManager,
private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
private val highPriorityProvider: HighPriorityProvider,
private val keyguardEnvironment: KeyguardEnvironment
) : LegacyNotificationRanker {
override var rankingMap: RankingMap? = null
protected set
private val mediaManager by lazy {
mediaManagerLazy.get()
}
private val usePeopleFiltering: Boolean
get() = sectionsFeatureManager.isFilteringEnabled()
private val rankingComparator: Comparator<NotificationEntry> = Comparator { a, b ->
val na = a.sbn
val nb = b.sbn
val aRank = a.ranking.rank
val bRank = b.ranking.rank
val aIsFsn = a.isColorizedForegroundService()
val bIsFsn = b.isColorizedForegroundService()
val aCall = a.isImportantCall()
val bCall = b.isImportantCall()
val aPersonType = a.getPeopleNotificationType()
val bPersonType = b.getPeopleNotificationType()
val aMedia = a.isImportantMedia()
val bMedia = b.isImportantMedia()
val aSystemMax = a.isSystemMax()
val bSystemMax = b.isSystemMax()
val aHeadsUp = a.isRowHeadsUp
val bHeadsUp = b.isRowHeadsUp
val aIsHighPriority = a.isHighPriority()
val bIsHighPriority = b.isHighPriority()
when {
aHeadsUp != bHeadsUp -> if (aHeadsUp) -1 else 1
// Provide consistent ranking with headsUpManager
aHeadsUp -> headsUpManager.compare(a, b)
aIsFsn != bIsFsn -> if (aIsFsn) -1 else 1
aCall != bCall -> if (aCall) -1 else 1
usePeopleFiltering && aPersonType != bPersonType ->
peopleNotificationIdentifier.compareTo(aPersonType, bPersonType)
// Upsort current media notification.
aMedia != bMedia -> if (aMedia) -1 else 1
// Upsort PRIORITY_MAX system notifications
aSystemMax != bSystemMax -> if (aSystemMax) -1 else 1
aIsHighPriority != bIsHighPriority ->
-1 * aIsHighPriority.compareTo(bIsHighPriority)
aRank != bRank -> aRank - bRank
else -> nb.notification.`when`.compareTo(na.notification.`when`)
}
}
override fun updateRanking(
newRankingMap: RankingMap?,
entries: Collection<NotificationEntry>,
reason: String
): List<NotificationEntry> {
// TODO: may not be ideal to guard on null here, but this code is implementing exactly what
// NotificationData used to do
if (newRankingMap != null) {
rankingMap = newRankingMap
updateRankingForEntries(entries)
}
return synchronized(this) {
filterAndSortLocked(entries, reason)
}
}
override fun isNotificationForCurrentProfiles(
entry: NotificationEntry
): Boolean {
return keyguardEnvironment.isNotificationForCurrentProfiles(entry.sbn)
}
/** Uses the [rankingComparator] to sort notifications which aren't filtered */
private fun filterAndSortLocked(
entries: Collection<NotificationEntry>,
reason: String
): List<NotificationEntry> {
logger.logFilterAndSort(reason)
val filtered = entries.asSequence()
.filterNot(this::filter)
.sortedWith(rankingComparator)
.toList()
entries.forEach { it.bucket = getBucketForEntry(it) }
return filtered
}
private fun filter(entry: NotificationEntry): Boolean {
val filtered = notifFilter.shouldFilterOut(entry)
if (filtered) {
// notification is removed from the list, so we reset its initialization time
entry.resetInitializationTime()
}
return filtered
}
@PriorityBucket
private fun getBucketForEntry(entry: NotificationEntry): Int {
val isImportantCall = entry.isImportantCall()
val isHeadsUp = entry.isRowHeadsUp
val isMedia = entry.isImportantMedia()
val isSystemMax = entry.isSystemMax()
return when {
entry.isColorizedForegroundService() || isImportantCall -> BUCKET_FOREGROUND_SERVICE
usePeopleFiltering && entry.isConversation() -> BUCKET_PEOPLE
isHeadsUp || isMedia || isSystemMax || entry.isHighPriority() -> BUCKET_ALERTING
else -> BUCKET_SILENT
}
}
private fun updateRankingForEntries(entries: Iterable<NotificationEntry>) {
rankingMap?.let { rankingMap ->
synchronized(entries) {
for (entry in entries) {
val newRanking = Ranking()
if (!rankingMap.getRanking(entry.key, newRanking)) {
continue
}
entry.ranking = newRanking
val newOverrideGroupKey = newRanking.overrideGroupKey
if (!Objects.equals(entry.sbn.overrideGroupKey, newOverrideGroupKey)) {
val oldGroupKey = entry.sbn.groupKey
val oldIsGroup = entry.sbn.isGroup
val oldIsGroupSummary = entry.sbn.notification.isGroupSummary
entry.sbn.overrideGroupKey = newOverrideGroupKey
groupManager.onEntryUpdated(entry, oldGroupKey, oldIsGroup,
oldIsGroupSummary)
}
}
}
}
}
private fun NotificationEntry.isImportantMedia() =
key == mediaManager.mediaNotificationKey && importance > IMPORTANCE_MIN
private fun NotificationEntry.isConversation() = getPeopleNotificationType() != TYPE_NON_PERSON
private fun NotificationEntry.getPeopleNotificationType() =
peopleNotificationIdentifier.getPeopleNotificationType(this)
private fun NotificationEntry.isHighPriority() =
highPriorityProvider.isHighPriority(this)
}
// Convenience functions
private fun NotificationEntry.isSystemMax() =
importance >= IMPORTANCE_HIGH && sbn.isSystemNotification()
private fun StatusBarNotification.isSystemNotification() =
"android" == packageName || "com.android.systemui" == packageName
private fun NotificationEntry.isImportantCall() =
sbn.notification.isStyle(Notification.CallStyle::class.java) && importance > IMPORTANCE_MIN
private fun NotificationEntry.isColorizedForegroundService() = sbn.notification.run {
isForegroundService && isColorized && importance > IMPORTANCE_MIN
}

View File

@@ -17,8 +17,6 @@
package com.android.systemui.statusbar.notification.collection.inflation
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.NotificationEntryListener
import com.android.systemui.statusbar.notification.NotificationEntryManager
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager.Listener
import javax.inject.Inject
@@ -31,12 +29,4 @@ class BindEventManagerImpl @Inject constructor() : BindEventManager() {
/** Emit the [Listener.onViewBound] event to all registered listeners. */
fun notifyViewBound(entry: NotificationEntry) =
listeners.forEach { listener -> listener.onViewBound(entry) }
/** Initialize this for the legacy pipeline. */
fun attachToLegacyPipeline(notificationEntryManager: NotificationEntryManager) {
notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener {
override fun onEntryInflated(entry: NotificationEntry) = notifyViewBound(entry)
override fun onEntryReinflated(entry: NotificationEntry) = notifyViewBound(entry)
})
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright (C) 2021 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.legacy
import android.service.notification.NotificationListenerService
import com.android.systemui.statusbar.notification.collection.NotificationEntry
interface LegacyNotificationRanker {
val rankingMap: NotificationListenerService.RankingMap?
fun updateRanking(
newRankingMap: NotificationListenerService.RankingMap?,
entries: Collection<NotificationEntry>,
reason: String
): List<NotificationEntry>
fun isNotificationForCurrentProfiles(
entry: NotificationEntry
): Boolean
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright (C) 2021 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.legacy;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
/**
* Stub implementation that we use until we get passed the "real" one in the form of
* {@link com.android.systemui.statusbar.notification.collection.NotificationRankingManager}
*/
public class LegacyNotificationRankerStub implements LegacyNotificationRanker {
private RankingMap mRankingMap = new RankingMap(new Ranking[] {});
@NonNull
@Override
public List<NotificationEntry> updateRanking(
@Nullable RankingMap newRankingMap,
@NonNull Collection<NotificationEntry> entries,
@NonNull String reason) {
if (newRankingMap != null) {
mRankingMap = newRankingMap;
}
List<NotificationEntry> ranked = new ArrayList<>(entries);
ranked.sort(mEntryComparator);
return ranked;
}
@Nullable
@Override
public RankingMap getRankingMap() {
return mRankingMap;
}
private final Comparator<NotificationEntry> mEntryComparator = Comparator.comparingLong(
o -> o.getSbn().getNotification().when);
@Override
public boolean isNotificationForCurrentProfiles(@NonNull NotificationEntry entry) {
return true;
}
}

View File

@@ -19,9 +19,6 @@ package com.android.systemui.statusbar.notification.collection.legacy;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.RowContentBindParams;
import com.android.systemui.statusbar.notification.row.RowContentBindStage;
import javax.inject.Inject;
@@ -32,43 +29,16 @@ import javax.inject.Inject;
@SysUISingleton
public class LowPriorityInflationHelper {
private final NotificationGroupManagerLegacy mGroupManager;
private final RowContentBindStage mRowContentBindStage;
private final NotifPipelineFlags mNotifPipelineFlags;
@Inject
LowPriorityInflationHelper(
NotificationGroupManagerLegacy groupManager,
RowContentBindStage rowContentBindStage,
NotifPipelineFlags notifPipelineFlags) {
mGroupManager = groupManager;
mRowContentBindStage = rowContentBindStage;
mNotifPipelineFlags = notifPipelineFlags;
}
/**
* Check if we inflated the wrong version of the view and if we need to reinflate the
* content views to be their low priority version or not.
*
* Whether we inflate the low priority view or not depends on the notification being visually
* part of a group. Since group membership is determined AFTER inflation, we're forced to check
* again at a later point in the pipeline to see if we inflated the wrong view and reinflate
* the correct one here.
*
* TODO: The group manager should run before inflation so that we don't deal with this
*/
public void recheckLowPriorityViewAndInflate(
NotificationEntry entry,
ExpandableNotificationRow row) {
mNotifPipelineFlags.checkLegacyPipelineEnabled();
RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
final boolean shouldBeLowPriority = shouldUseLowPriorityView(entry);
if (!row.isRemoved() && row.isLowPriority() != shouldBeLowPriority) {
params.setUseLowPriority(shouldBeLowPriority);
mRowContentBindStage.requestRebind(entry,
en -> row.setIsLowPriority(shouldBeLowPriority));
}
}
/**
* Whether the notification should inflate a low priority version of its content views.
*/

View File

@@ -33,13 +33,9 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager.OnGroupExpansionChangeListener;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.util.Compile;
import com.android.wm.shell.bubbles.Bubbles;
@@ -47,7 +43,6 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -65,12 +60,7 @@ import dagger.Lazy;
* 2. Tracking group expansion states
*/
@SysUISingleton
public class NotificationGroupManagerLegacy implements
OnHeadsUpChangedListener,
StateListener,
GroupMembershipManager,
GroupExpansionManager,
Dumpable {
public class NotificationGroupManagerLegacy implements StateListener, Dumpable {
private static final String TAG = "LegacyNotifGroupManager";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
@@ -81,14 +71,10 @@ public class NotificationGroupManagerLegacy implements
*/
private static final long POST_BATCH_MAX_AGE = 5000;
private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
private final ArraySet<OnGroupExpansionChangeListener> mExpansionChangeListeners =
new ArraySet<>();
private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier;
private final Optional<Bubbles> mBubblesOptional;
private final GroupEventDispatcher mEventDispatcher = new GroupEventDispatcher(mGroupMap::get);
private int mBarState = -1;
private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
private HeadsUpManager mHeadsUpManager;
private final HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
private boolean mIsUpdatingUnchangedGroup;
@Inject
@@ -111,47 +97,8 @@ public class NotificationGroupManagerLegacy implements
mEventDispatcher.registerGroupChangeListener(listener);
}
@Override
public void registerGroupExpansionChangeListener(OnGroupExpansionChangeListener listener) {
mExpansionChangeListeners.add(listener);
}
@Override
public boolean isGroupExpanded(NotificationEntry entry) {
NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn()));
if (group == null) {
return false;
}
return group.expanded;
}
/**
* @return if the group that this notification is associated with logically is expanded
*/
public boolean isLogicalGroupExpanded(StatusBarNotification sbn) {
NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
if (group == null) {
return false;
}
return group.expanded;
}
@Override
public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn()));
if (group == null) {
return;
}
setGroupExpanded(group, expanded);
}
private void setGroupExpanded(NotificationGroup group, boolean expanded) {
group.expanded = expanded;
if (group.summary != null) {
for (OnGroupExpansionChangeListener listener : mExpansionChangeListeners) {
listener.onGroupExpansionChange(group.summary.getRow(), expanded);
}
}
}
/**
@@ -212,19 +159,6 @@ public class NotificationGroupManagerLegacy implements
}
}
/**
* Notify the group manager that a new entry was added
*/
public void onEntryAdded(final NotificationEntry added) {
if (SPEW) {
Log.d(TAG, "onEntryAdded: entry=" + logKey(added));
}
mEventDispatcher.openBufferScope();
updateIsolation(added);
onEntryAddedInternal(added);
mEventDispatcher.closeBufferScope();
}
private void onEntryAddedInternal(final NotificationEntry added) {
if (added.isRowRemoved()) {
added.setDebugThrowable(new Throwable());
@@ -499,106 +433,13 @@ public class NotificationGroupManagerLegacy implements
return result;
}
/**
* Update an entry's group information
* @param entry notification entry to update
* @param oldNotification previous notification info before this update
*/
public void onEntryUpdated(NotificationEntry entry, StatusBarNotification oldNotification) {
if (SPEW) {
Log.d(TAG, "onEntryUpdated: entry=" + logKey(entry));
}
onEntryUpdated(entry, oldNotification.getGroupKey(), oldNotification.isGroup(),
oldNotification.getNotification().isGroupSummary());
}
/**
* Updates an entry's group information
* @param entry notification entry to update
* @param oldGroupKey the notification's previous group key before this update
* @param oldIsGroup whether this notification was a group before this update
* @param oldIsGroupSummary whether this notification was a group summary before this update
*/
public void onEntryUpdated(NotificationEntry entry, String oldGroupKey, boolean oldIsGroup,
boolean oldIsGroupSummary) {
String newGroupKey = entry.getSbn().getGroupKey();
boolean groupKeysChanged = !oldGroupKey.equals(newGroupKey);
boolean wasGroupChild = isGroupChild(entry.getKey(), oldIsGroup, oldIsGroupSummary);
boolean isGroupChild = isGroupChild(entry.getSbn());
mEventDispatcher.openBufferScope();
mIsUpdatingUnchangedGroup = !groupKeysChanged && wasGroupChild == isGroupChild;
if (mGroupMap.get(getGroupKey(entry.getKey(), oldGroupKey)) != null) {
onEntryRemovedInternal(entry, oldGroupKey, oldIsGroup, oldIsGroupSummary);
}
onEntryAddedInternal(entry);
mIsUpdatingUnchangedGroup = false;
if (isIsolated(entry.getSbn().getKey())) {
mIsolatedEntries.put(entry.getKey(), entry.getSbn());
if (groupKeysChanged) {
updateSuppression(mGroupMap.get(oldGroupKey));
}
// Always update the suppression of the group from which you're isolated, in case
// this entry was or now is the alertOverride for that group.
updateSuppression(mGroupMap.get(newGroupKey));
} else if (!wasGroupChild && isGroupChild) {
onEntryBecomingChild(entry);
}
mEventDispatcher.closeBufferScope();
}
/**
* Whether the given notification is the summary of a group that is being suppressed
*/
public boolean isSummaryOfSuppressedGroup(StatusBarNotification sbn) {
return sbn.getNotification().isGroupSummary() && isGroupSuppressed(getGroupKey(sbn));
}
/**
* If the given notification is a summary, get the group for it.
*/
public NotificationGroup getGroupForSummary(StatusBarNotification sbn) {
if (sbn.getNotification().isGroupSummary()) {
return mGroupMap.get(getGroupKey(sbn));
}
return null;
}
private boolean isOnlyChild(StatusBarNotification sbn) {
return !sbn.getNotification().isGroupSummary()
&& getTotalNumberOfChildren(sbn) == 1;
}
@Override
public boolean isOnlyChildInGroup(NotificationEntry entry) {
final StatusBarNotification sbn = entry.getSbn();
if (!isOnlyChild(sbn)) {
return false;
}
NotificationEntry logicalGroupSummary = getLogicalGroupSummary(entry);
return logicalGroupSummary != null && !logicalGroupSummary.getSbn().equals(sbn);
}
private int getTotalNumberOfChildren(StatusBarNotification sbn) {
int isolatedChildren = getNumberOfIsolatedChildren(sbn.getGroupKey());
NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
int realChildren = group != null ? group.children.size() : 0;
return isolatedChildren + realChildren;
}
private boolean isGroupSuppressed(String groupKey) {
NotificationGroup group = mGroupMap.get(groupKey);
return group != null && group.suppressed;
}
private void setStatusBarState(int newState) {
mBarState = newState;
if (mBarState == StatusBarState.KEYGUARD) {
if (newState == StatusBarState.KEYGUARD) {
collapseGroups();
}
}
@Override
public void collapseGroups() {
private void collapseGroups() {
// Because notifications can become isolated when the group becomes suppressed it can
// lead to concurrent modifications while looping. We need to make a copy.
ArrayList<NotificationGroup> groupCopy = new ArrayList<>(mGroupMap.values());
@@ -612,7 +453,6 @@ public class NotificationGroupManagerLegacy implements
}
}
@Override
public boolean isChildInGroup(NotificationEntry entry) {
final StatusBarNotification sbn = entry.getSbn();
if (!isGroupChild(sbn)) {
@@ -631,67 +471,6 @@ public class NotificationGroupManagerLegacy implements
return true;
}
@Override
public boolean isGroupSummary(NotificationEntry entry) {
final StatusBarNotification sbn = entry.getSbn();
if (!isGroupSummary(sbn)) {
return false;
}
NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
if (group == null || group.summary == null) {
return false;
}
return !group.children.isEmpty() && Objects.equals(group.summary.getSbn(), sbn);
}
@Override
public NotificationEntry getGroupSummary(NotificationEntry entry) {
return getGroupSummary(getGroupKey(entry.getSbn()));
}
@Override
public NotificationEntry getLogicalGroupSummary(NotificationEntry entry) {
return getGroupSummary(entry.getSbn().getGroupKey());
}
@Nullable
private NotificationEntry getGroupSummary(String groupKey) {
NotificationGroup group = mGroupMap.get(groupKey);
//TODO: see if this can become an Entry
return group == null ? null
: group.summary;
}
/**
* Get the children that are logically in the summary's group, whether or not they are isolated.
*
* @param summary summary of a group
* @return list of the children
*/
public ArrayList<NotificationEntry> getLogicalChildren(StatusBarNotification summary) {
NotificationGroup group = mGroupMap.get(summary.getGroupKey());
if (group == null) {
return null;
}
ArrayList<NotificationEntry> children = new ArrayList<>(group.children.values());
for (StatusBarNotification sbn : mIsolatedEntries.values()) {
if (sbn.getGroupKey().equals(summary.getGroupKey())) {
children.add(mGroupMap.get(sbn.getKey()).summary);
}
}
return children;
}
@Override
public @Nullable List<NotificationEntry> getChildren(ListEntry listEntrySummary) {
NotificationEntry summary = listEntrySummary.getRepresentativeEntry();
NotificationGroup group = mGroupMap.get(summary.getSbn().getGroupKey());
if (group == null) {
return null;
}
return new ArrayList<>(group.children.values());
}
/**
* If there is a {@link NotificationGroup} associated with the provided entry, this method
* will update the suppression of that group.
@@ -710,7 +489,7 @@ public class NotificationGroupManagerLegacy implements
* @param sbn notification to check
* @return the key of the notification
*/
public String getGroupKey(StatusBarNotification sbn) {
private String getGroupKey(StatusBarNotification sbn) {
return getGroupKey(sbn.getKey(), sbn.getGroupKey());
}
@@ -721,37 +500,17 @@ public class NotificationGroupManagerLegacy implements
return groupKey;
}
@Override
public boolean toggleGroupExpansion(NotificationEntry entry) {
NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn()));
if (group == null) {
return false;
}
setGroupExpanded(group, !group.expanded);
return group.expanded;
}
private boolean isIsolated(String sbnKey) {
return mIsolatedEntries.containsKey(sbnKey);
}
/**
* Is this notification the summary of a group?
*/
public boolean isGroupSummary(StatusBarNotification sbn) {
if (isIsolated(sbn.getKey())) {
return true;
}
return sbn.getNotification().isGroupSummary();
}
/**
* Whether a notification is visually a group child.
*
* @param sbn notification to check
* @return true if it is visually a group child
*/
public boolean isGroupChild(StatusBarNotification sbn) {
private boolean isGroupChild(StatusBarNotification sbn) {
return isGroupChild(sbn.getKey(), sbn.isGroup(), sbn.getNotification().isGroupSummary());
}
@@ -762,11 +521,6 @@ public class NotificationGroupManagerLegacy implements
return isGroup && !isGroupSummary;
}
@Override
public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
updateIsolation(entry);
}
/**
* Whether a notification that is normally part of a group should be temporarily isolated from
* the group and put in their own group visually. This generally happens when the notification
@@ -783,9 +537,6 @@ public class NotificationGroupManagerLegacy implements
if (isImportantConversation(entry)) {
return true;
}
if (mHeadsUpManager != null && !mHeadsUpManager.isAlerting(entry.getKey())) {
return false;
}
NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
return (sbn.getNotification().fullScreenIntent != null
|| notificationGroup == null
@@ -825,7 +576,7 @@ public class NotificationGroupManagerLegacy implements
/**
* Update the isolation of an entry, splitting it from the group.
*/
public void updateIsolation(NotificationEntry entry) {
private void updateIsolation(NotificationEntry entry) {
// We need to buffer a few events because we do isolation changes in 3 steps:
// removeInternal, update mIsolatedEntries, addInternal. This means that often the
// alertOverride will update on the removal, however processing the event in that case can
@@ -866,13 +617,6 @@ public class NotificationGroupManagerLegacy implements
|| notificationGroup.summary.isGroupNotFullyVisible();
}
/**
* Directly set the heads up manager to avoid circular dependencies
*/
public void setHeadsUpManager(HeadsUpManager headsUpManager) {
mHeadsUpManager = headsUpManager;
}
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("GroupManagerLegacy state:");
@@ -893,7 +637,7 @@ public class NotificationGroupManagerLegacy implements
}
/** Get the group key, reformatted for logging, for the (optional) group */
public static String logGroupKey(NotificationGroup group) {
private static String logGroupKey(NotificationGroup group) {
if (group == null) {
return "null";
}

View File

@@ -16,84 +16,32 @@
package com.android.systemui.statusbar.notification.collection.legacy;
import android.os.Handler;
import android.os.SystemClock;
import android.view.View;
import androidx.collection.ArraySet;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import java.io.PrintWriter;
import java.util.ArrayList;
/**
* A manager that ensures that notifications are visually stable. It will suppress reorderings
* and reorder at the right time when they are out of view.
*/
public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpable {
public class VisualStabilityManager {
private static final long TEMPORARY_REORDERING_ALLOWED_DURATION = 1000;
private final ArrayList<Callback> mReorderingAllowedCallbacks = new ArrayList<>();
private final ArraySet<Callback> mPersistentReorderingCallbacks = new ArraySet<>();
private final ArrayList<Callback> mGroupChangesAllowedCallbacks = new ArrayList<>();
private final ArraySet<Callback> mPersistentGroupCallbacks = new ArraySet<>();
private final Handler mHandler;
private final VisualStabilityProvider mVisualStabilityProvider;
private boolean mPanelExpanded;
private boolean mScreenOn;
private boolean mReorderingAllowed;
private boolean mGroupChangedAllowed;
private boolean mIsTemporaryReorderingAllowed;
private long mTemporaryReorderingStart;
private VisibilityLocationProvider mVisibilityLocationProvider;
private ArraySet<View> mAllowedReorderViews = new ArraySet<>();
private ArraySet<NotificationEntry> mLowPriorityReorderingViews = new ArraySet<>();
private ArraySet<View> mAddedChildren = new ArraySet<>();
private boolean mPulsing;
/**
* Injected constructor. See {@link NotificationsModule}.
*/
public VisualStabilityManager(
NotificationEntryManager notificationEntryManager,
VisualStabilityProvider visualStabilityProvider,
@Main Handler handler,
StatusBarStateController statusBarStateController,
WakefulnessLifecycle wakefulnessLifecycle,
DumpManager dumpManager) {
WakefulnessLifecycle wakefulnessLifecycle) {
mVisualStabilityProvider = visualStabilityProvider;
mHandler = handler;
dumpManager.registerDumpable(this);
if (notificationEntryManager != null) {
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onPreEntryUpdated(NotificationEntry entry) {
final boolean ambientStateHasChanged =
entry.isAmbient() != entry.getRow().isLowPriority();
if (ambientStateHasChanged) {
// note: entries are removed in onReorderingFinished
mLowPriorityReorderingViews.add(entry);
}
}
});
}
if (statusBarStateController != null) {
setPulsing(statusBarStateController.isPulsing());
@@ -115,40 +63,6 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl
}
}
/**
* Add a callback to invoke when reordering is allowed again.
*
* @param callback the callback to add
* @param persistent {@code true} if this callback should this callback be persisted, otherwise
* it will be removed after a single invocation
*/
public void addReorderingAllowedCallback(Callback callback, boolean persistent) {
if (persistent) {
mPersistentReorderingCallbacks.add(callback);
}
if (mReorderingAllowedCallbacks.contains(callback)) {
return;
}
mReorderingAllowedCallbacks.add(callback);
}
/**
* Add a callback to invoke when group changes are allowed again.
*
* @param callback the callback to add
* @param persistent {@code true} if this callback should this callback be persisted, otherwise
* it will be removed after a single invocation
*/
public void addGroupChangesAllowedCallback(Callback callback, boolean persistent) {
if (persistent) {
mPersistentGroupCallbacks.add(callback);
}
if (mGroupChangesAllowedCallbacks.contains(callback)) {
return;
}
mGroupChangesAllowedCallbacks.add(callback);
}
/**
* @param screenOn whether the screen is on
*/
@@ -177,133 +91,8 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl
}
private void updateAllowedStates() {
boolean reorderingAllowed =
(!mScreenOn || !mPanelExpanded || mIsTemporaryReorderingAllowed) && !mPulsing;
boolean changedToTrue = reorderingAllowed && !mReorderingAllowed;
mReorderingAllowed = reorderingAllowed;
if (changedToTrue) {
notifyChangeAllowed(mReorderingAllowedCallbacks, mPersistentReorderingCallbacks);
}
boolean reorderingAllowed = (!mScreenOn || !mPanelExpanded) && !mPulsing;
mVisualStabilityProvider.setReorderingAllowed(reorderingAllowed);
boolean groupChangesAllowed = (!mScreenOn || !mPanelExpanded) && !mPulsing;
changedToTrue = groupChangesAllowed && !mGroupChangedAllowed;
mGroupChangedAllowed = groupChangesAllowed;
if (changedToTrue) {
notifyChangeAllowed(mGroupChangesAllowedCallbacks, mPersistentGroupCallbacks);
}
}
private void notifyChangeAllowed(ArrayList<Callback> callbacks,
ArraySet<Callback> persistentCallbacks) {
for (int i = 0; i < callbacks.size(); i++) {
Callback callback = callbacks.get(i);
callback.onChangeAllowed();
if (!persistentCallbacks.contains(callback)) {
callbacks.remove(callback);
i--;
}
}
}
/**
* @return whether reordering is currently allowed in general.
*/
public boolean isReorderingAllowed() {
return mReorderingAllowed;
}
/**
* @return whether changes in the grouping should be allowed right now.
*/
public boolean areGroupChangesAllowed() {
return mGroupChangedAllowed;
}
/**
* @return whether a specific notification is allowed to reorder. Certain notifications are
* allowed to reorder even if {@link #isReorderingAllowed()} returns false, like newly added
* notifications or heads-up notifications that are out of view.
*/
public boolean canReorderNotification(ExpandableNotificationRow row) {
if (mReorderingAllowed) {
return true;
}
if (mAddedChildren.contains(row)) {
return true;
}
if (mLowPriorityReorderingViews.contains(row.getEntry())) {
return true;
}
if (mAllowedReorderViews.contains(row)
&& !mVisibilityLocationProvider.isInVisibleLocation(row.getEntry())) {
return true;
}
return false;
}
public void setVisibilityLocationProvider(
VisibilityLocationProvider visibilityLocationProvider) {
mVisibilityLocationProvider = visibilityLocationProvider;
}
/**
* Notifications have been reordered, so reset all the allowed list of views that are allowed
* to reorder.
*/
public void onReorderingFinished() {
mAllowedReorderViews.clear();
mAddedChildren.clear();
mLowPriorityReorderingViews.clear();
}
@Override
public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
if (isHeadsUp) {
// Heads up notifications should in general be allowed to reorder if they are out of
// view and stay at the current location if they aren't.
mAllowedReorderViews.add(entry.getRow());
}
}
/**
* Temporarily allows reordering of the entire shade for a period of 1000ms. Subsequent calls
* to this method will extend the timer.
*/
public void temporarilyAllowReordering() {
mHandler.removeCallbacks(mOnTemporaryReorderingExpired);
mHandler.postDelayed(mOnTemporaryReorderingExpired, TEMPORARY_REORDERING_ALLOWED_DURATION);
if (!mIsTemporaryReorderingAllowed) {
mTemporaryReorderingStart = SystemClock.elapsedRealtime();
}
mIsTemporaryReorderingAllowed = true;
updateAllowedStates();
}
private final Runnable mOnTemporaryReorderingExpired = () -> {
mIsTemporaryReorderingAllowed = false;
updateAllowedStates();
};
/**
* Notify the visual stability manager that a new view was added and should be allowed to
* reorder next time.
*/
public void notifyViewAddition(View view) {
mAddedChildren.add(view);
}
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("VisualStabilityManager state:");
pw.print(" mIsTemporaryReorderingAllowed="); pw.println(mIsTemporaryReorderingAllowed);
pw.print(" mTemporaryReorderingStart="); pw.println(mTemporaryReorderingStart);
long now = SystemClock.elapsedRealtime();
pw.print(" Temporary reordering window has been open for ");
pw.print(now - (mIsTemporaryReorderingAllowed ? mTemporaryReorderingStart : now));
pw.println("ms");
pw.println();
}
final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {

View File

@@ -30,7 +30,6 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -53,7 +52,6 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger
import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager;
import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl;
import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
@@ -123,22 +121,13 @@ public interface NotificationsModule {
NotificationEntryManagerLogger logger,
NotificationGroupManagerLegacy groupManager,
NotifPipelineFlags notifPipelineFlags,
Lazy<NotificationRowBinder> notificationRowBinderLazy,
Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
LeakDetector leakDetector,
IStatusBarService statusBarService,
DumpManager dumpManager,
@Background Executor bgExecutor) {
return new NotificationEntryManager(
logger,
groupManager,
notifPipelineFlags,
notificationRowBinderLazy,
notificationRemoteInputManagerLazy,
leakDetector,
statusBarService,
dumpManager,
bgExecutor);
logger
);
}
/** Provides an instance of {@link NotificationGutsManager} */
@@ -162,8 +151,7 @@ public interface NotificationsModule {
Optional<BubblesManager> bubblesManagerOptional,
UiEventLogger uiEventLogger,
OnUserInteractionCallback onUserInteractionCallback,
ShadeController shadeController,
DumpManager dumpManager) {
ShadeController shadeController) {
return new NotificationGutsManager(
context,
centralSurfacesOptionalLazy,
@@ -181,8 +169,8 @@ public interface NotificationsModule {
bubblesManagerOptional,
uiEventLogger,
onUserInteractionCallback,
shadeController,
dumpManager);
shadeController
);
}
/** Provides an instance of {@link NotifGutsViewManager} */
@@ -193,19 +181,14 @@ public interface NotificationsModule {
@SysUISingleton
@Provides
static VisualStabilityManager provideVisualStabilityManager(
NotificationEntryManager notificationEntryManager,
VisualStabilityProvider visualStabilityProvider,
@Main Handler handler,
StatusBarStateController statusBarStateController,
WakefulnessLifecycle wakefulnessLifecycle,
DumpManager dumpManager) {
WakefulnessLifecycle wakefulnessLifecycle) {
return new VisualStabilityManager(
notificationEntryManager,
visualStabilityProvider,
handler,
statusBarStateController,
wakefulnessLifecycle,
dumpManager);
wakefulnessLifecycle
);
}
/** Provides an instance of {@link NotificationLogger} */

View File

@@ -1,185 +0,0 @@
/*
* Copyright (C) 2020 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.interruption;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
import android.app.Notification;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import androidx.annotation.NonNull;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import javax.inject.Inject;
/**
* Controller class for old pipeline heads up logic. It listens to {@link NotificationEntryManager}
* entry events and appropriately binds or unbinds the heads up view and promotes it to the top
* of the screen.
*/
@SysUISingleton
public class HeadsUpController {
private final HeadsUpViewBinder mHeadsUpViewBinder;
private final NotificationInterruptStateProvider mInterruptStateProvider;
private final NotificationRemoteInputManager mRemoteInputManager;
private final VisualStabilityManager mVisualStabilityManager;
private final StatusBarStateController mStatusBarStateController;
private final NotificationListener mNotificationListener;
private final HeadsUpManager mHeadsUpManager;
@Inject
HeadsUpController(
HeadsUpViewBinder headsUpViewBinder,
NotificationInterruptStateProvider notificationInterruptStateProvider,
HeadsUpManager headsUpManager,
NotificationRemoteInputManager remoteInputManager,
StatusBarStateController statusBarStateController,
VisualStabilityManager visualStabilityManager,
NotificationListener notificationListener) {
mHeadsUpViewBinder = headsUpViewBinder;
mHeadsUpManager = headsUpManager;
mInterruptStateProvider = notificationInterruptStateProvider;
mRemoteInputManager = remoteInputManager;
mStatusBarStateController = statusBarStateController;
mVisualStabilityManager = visualStabilityManager;
mNotificationListener = notificationListener;
}
/**
* Attach this controller and add its listeners.
*/
public void attach(
NotificationEntryManager entryManager,
HeadsUpManager headsUpManager) {
entryManager.addCollectionListener(mCollectionListener);
headsUpManager.addListener(mOnHeadsUpChangedListener);
}
private NotifCollectionListener mCollectionListener = new NotifCollectionListener() {
@Override
public void onEntryAdded(NotificationEntry entry) {
if (mInterruptStateProvider.shouldHeadsUp(entry)) {
mHeadsUpViewBinder.bindHeadsUpView(
entry, HeadsUpController.this::showAlertingView);
}
}
@Override
public void onEntryUpdated(NotificationEntry entry) {
updateHunState(entry);
}
@Override
public void onEntryRemoved(NotificationEntry entry, int reason) {
stopAlerting(entry);
}
@Override
public void onEntryCleanUp(NotificationEntry entry) {
mHeadsUpViewBinder.abortBindCallback(entry);
}
};
/**
* Adds the entry to the HUN manager and show it for the first time.
*/
private void showAlertingView(NotificationEntry entry) {
mHeadsUpManager.showNotification(entry);
if (!mStatusBarStateController.isDozing()) {
// Mark as seen immediately
setNotificationShown(entry.getSbn());
}
}
private void updateHunState(NotificationEntry entry) {
boolean hunAgain = alertAgain(entry, entry.getSbn().getNotification());
// includes check for whether this notification should be filtered:
boolean shouldHeadsUp = mInterruptStateProvider.shouldHeadsUp(entry);
final boolean wasHeadsUp = mHeadsUpManager.isAlerting(entry.getKey());
if (wasHeadsUp) {
if (shouldHeadsUp) {
mHeadsUpManager.updateNotification(entry.getKey(), hunAgain);
} else {
// We don't want this to be interrupting anymore, let's remove it
mHeadsUpManager.removeNotification(entry.getKey(), false /* removeImmediately */);
}
} else if (shouldHeadsUp && hunAgain) {
mHeadsUpViewBinder.bindHeadsUpView(entry, mHeadsUpManager::showNotification);
}
}
private void setNotificationShown(StatusBarNotification n) {
try {
mNotificationListener.setNotificationsShown(new String[]{n.getKey()});
} catch (RuntimeException e) {
Log.d(TAG, "failed setNotificationsShown: ", e);
}
}
private void stopAlerting(NotificationEntry entry) {
// Attempt to remove notifications from their HUN manager.
// Though the remove itself may fail, it lets the manager know to remove as soon as
// possible.
String key = entry.getKey();
if (mHeadsUpManager.isAlerting(key)) {
// A cancel() in response to a remote input shouldn't be delayed, as it makes the
// sending look longer than it takes.
// Also we should not defer the removal if reordering isn't allowed since otherwise
// some notifications can't disappear before the panel is closed.
boolean ignoreEarliestRemovalTime =
mRemoteInputManager.isSpinning(key)
&& !FORCE_REMOTE_INPUT_HISTORY
|| !mVisualStabilityManager.isReorderingAllowed();
mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
}
}
/**
* Checks whether an update for a notification warrants an alert for the user.
*
* @param oldEntry the entry for this notification.
* @param newNotification the new notification for this entry.
* @return whether this notification should alert the user.
*/
public static boolean alertAgain(
NotificationEntry oldEntry, Notification newNotification) {
return oldEntry == null || !oldEntry.hasInterrupted()
|| (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
}
private OnHeadsUpChangedListener mOnHeadsUpChangedListener = new OnHeadsUpChangedListener() {
@Override
public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) {
if (!isHeadsUp && !entry.getRow().isRemoved()) {
mHeadsUpViewBinder.unbindHeadsUpView(entry);
}
}
};
private static final String TAG = "HeadsUpBindController";
}

View File

@@ -41,8 +41,7 @@ import javax.inject.Inject;
* figuring out the right heads up inflation parameters and inflating/freeing the heads up
* content view.
*
* TODO: This should be moved into {@link HeadsUpCoordinator} when the old pipeline is deprecated
* (i.e. when {@link HeadsUpController} is removed).
* TODO: This should be moved into {@link HeadsUpCoordinator} when the old pipeline is deprecated.
*/
@SysUISingleton
public class HeadsUpViewBinder {

View File

@@ -94,7 +94,6 @@ import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
@@ -904,21 +903,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mChildrenContainer == null ? null : mChildrenContainer.getAttachedChildren();
}
/**
* Apply the order given in the list to the children.
*
* @param childOrder the new list order
* @param visualStabilityManager
* @param callback the callback to invoked in case it is not allowed
* @return whether the list order has changed
*/
public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder,
VisualStabilityManager visualStabilityManager,
VisualStabilityManager.Callback callback) {
return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder,
visualStabilityManager, callback);
}
/** Updates states of all children. */
public void updateChildrenStates(AmbientState ambientState) {
if (mIsSummaryWithChildren) {

View File

@@ -55,7 +55,6 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.StatusBarState;
@@ -81,8 +80,7 @@ import dagger.Lazy;
* Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
* closing guts, and keeping track of the currently exposed notification guts.
*/
public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender,
NotifGutsViewManager {
public class NotificationGutsManager implements NotifGutsViewManager {
private static final String TAG = "NotificationGutsManager";
// Must match constant in Settings. Used to highlight preferences when linking to Settings.
@@ -107,13 +105,10 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
// which notification is currently being longpress-examined by the user
private NotificationGuts mNotificationGutsExposed;
private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
private NotificationSafeToRemoveCallback mNotificationLifetimeFinishedCallback;
private NotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
private NotificationListContainer mListContainer;
private OnSettingsClickListener mOnSettingsClickListener;
@VisibleForTesting
protected String mKeyToRemoveOnGutsClosed;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
private final Handler mMainHandler;
@@ -148,8 +143,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
Optional<BubblesManager> bubblesManagerOptional,
UiEventLogger uiEventLogger,
OnUserInteractionCallback onUserInteractionCallback,
ShadeController shadeController,
DumpManager dumpManager) {
ShadeController shadeController) {
mContext = context;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mMainHandler = mainHandler;
@@ -167,8 +161,6 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
mUiEventLogger = uiEventLogger;
mOnUserInteractionCallback = onUserInteractionCallback;
mShadeController = shadeController;
dumpManager.registerDumpable(this);
}
public void setUpWithPresenter(NotificationPresenter presenter,
@@ -266,12 +258,6 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
mGutsListener.onGutsClose(entry);
}
String key = entry.getKey();
if (key.equals(mKeyToRemoveOnGutsClosed)) {
mKeyToRemoveOnGutsClosed = null;
if (mNotificationLifetimeFinishedCallback != null) {
mNotificationLifetimeFinishedCallback.onSafeToRemove(key);
}
}
});
View gutsView = item.getGutsView();
@@ -658,46 +644,6 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
return true;
}
@Override
public void setCallback(NotificationSafeToRemoveCallback callback) {
mNotificationLifetimeFinishedCallback = callback;
}
@Override
public boolean shouldExtendLifetime(NotificationEntry entry) {
return entry != null
&&(mNotificationGutsExposed != null
&& entry.getGuts() != null
&& mNotificationGutsExposed == entry.getGuts()
&& !mNotificationGutsExposed.isLeavebehind());
}
@Override
public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) {
if (shouldExtend) {
mKeyToRemoveOnGutsClosed = entry.getKey();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Keeping notification because it's showing guts. " + entry.getKey());
}
} else {
if (mKeyToRemoveOnGutsClosed != null
&& mKeyToRemoveOnGutsClosed.equals(entry.getKey())) {
mKeyToRemoveOnGutsClosed = null;
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Notification that was kept for guts was updated. "
+ entry.getKey());
}
}
}
}
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("NotificationGutsManager state:");
pw.print(" mKeyToRemoveOnGutsClosed (legacy): ");
pw.println(mKeyToRemoveOnGutsClosed);
}
/**
* @param gutsListener the listener for open and close guts events
*/

View File

@@ -43,7 +43,6 @@ import com.android.systemui.statusbar.NotificationGroupingUtil;
import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.HybridGroupManager;
@@ -462,39 +461,6 @@ public class NotificationChildrenContainer extends ViewGroup
return mAttachedChildren;
}
/**
* Apply the order given in the list to the children.
*
* @param childOrder the new list order
* @param visualStabilityManager
* @param callback
* @return whether the list order has changed
*/
public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder,
VisualStabilityManager visualStabilityManager,
VisualStabilityManager.Callback callback) {
if (childOrder == null) {
return false;
}
boolean result = false;
for (int i = 0; i < mAttachedChildren.size() && i < childOrder.size(); i++) {
ExpandableNotificationRow child = mAttachedChildren.get(i);
ExpandableNotificationRow desiredChild = childOrder.get(i);
if (child != desiredChild) {
if (visualStabilityManager.canReorderNotification(desiredChild)) {
mAttachedChildren.remove(desiredChild);
mAttachedChildren.add(i, desiredChild);
result = true;
} else {
visualStabilityManager.addReorderingAllowedCallback(callback,
false /* persistent */);
}
}
}
updateExpansionStates();
return result;
}
/** To be called any time the rows have been updated */
public void updateExpansionStates() {
if (mChildrenExpanded || mUserLocked) {

View File

@@ -20,9 +20,6 @@ import android.util.Log
import android.view.View
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.media.KeyguardMediaController
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
@@ -33,32 +30,20 @@ import com.android.systemui.statusbar.notification.dagger.PeopleHeader
import com.android.systemui.statusbar.notification.dagger.SilentHeader
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.children
import com.android.systemui.util.foldToSparseArray
import com.android.systemui.util.takeUntil
import com.android.systemui.util.traceSection
import javax.inject.Inject
/**
* Manages the boundaries of the notification sections (incoming, conversations, high priority, and
* low priority).
*
* In the legacy notification pipeline, this is responsible for correctly positioning all section
* headers after the [NotificationStackScrollLayout] has had notifications added/removed/changed. In
* the new pipeline, this is handled as part of the [ShadeViewManager].
* Manages section headers in the NSSL.
*
* TODO: Move remaining sections logic from NSSL into this class.
*/
class NotificationSectionsManager @Inject internal constructor(
private val statusBarStateController: StatusBarStateController,
private val configurationController: ConfigurationController,
private val keyguardMediaController: KeyguardMediaController,
private val sectionsFeatureManager: NotificationSectionsFeatureManager,
private val logger: NotificationSectionsLogger,
private val notifPipelineFlags: NotifPipelineFlags,
private val mediaContainerController: MediaContainerController,
@IncomingHeader private val incomingHeaderController: SectionHeaderController,
@PeopleHeader private val peopleHeaderController: SectionHeaderController,
@@ -139,205 +124,6 @@ class NotificationSectionsManager @Inject internal constructor(
else -> null
}
private fun logShadeChild(i: Int, child: View) {
when {
child === incomingHeaderView -> logger.logIncomingHeader(i)
child === mediaControlsView -> logger.logMediaControls(i)
child === peopleHeaderView -> logger.logConversationsHeader(i)
child === alertingHeaderView -> logger.logAlertingHeader(i)
child === silentHeaderView -> logger.logSilentHeader(i)
child !is ExpandableNotificationRow -> logger.logOther(i, child.javaClass)
else -> {
val isHeadsUp = child.isHeadsUp
when (child.entry.bucket) {
BUCKET_HEADS_UP -> logger.logHeadsUp(i, isHeadsUp)
BUCKET_PEOPLE -> logger.logConversation(i, isHeadsUp)
BUCKET_ALERTING -> logger.logAlerting(i, isHeadsUp)
BUCKET_SILENT -> logger.logSilent(i, isHeadsUp)
}
}
}
}
private fun logShadeContents() = traceSection("NotifSectionsManager.logShadeContents") {
parent.children.forEachIndexed(::logShadeChild)
}
private val isUsingMultipleSections: Boolean
get() = sectionsFeatureManager.getNumberOfBuckets() > 1
@VisibleForTesting
fun updateSectionBoundaries() = updateSectionBoundaries("test")
private interface SectionUpdateState<out T : ExpandableView> {
val header: T
var currentPosition: Int?
var targetPosition: Int?
fun adjustViewPosition()
}
private fun <T : ExpandableView> expandableViewHeaderState(header: T): SectionUpdateState<T> =
object : SectionUpdateState<T> {
override val header = header
override var currentPosition: Int? = null
override var targetPosition: Int? = null
override fun adjustViewPosition() {
notifPipelineFlags.checkLegacyPipelineEnabled()
val target = targetPosition
val current = currentPosition
if (target == null) {
if (current != null) {
parent.removeView(header)
}
} else {
if (current == null) {
// If the header is animating away, it will still have a parent, so
// detach it first
// TODO: We should really cancel the active animations here. This will
// happen automatically when the view's intro animation starts, but
// it's a fragile link.
header.removeFromTransientContainer()
parent.addView(header, target)
} else {
parent.changeViewPosition(header, target)
}
}
}
}
private fun <T : StackScrollerDecorView> decorViewHeaderState(
header: T
): SectionUpdateState<T> {
notifPipelineFlags.checkLegacyPipelineEnabled()
val inner = expandableViewHeaderState(header)
return object : SectionUpdateState<T> by inner {
override fun adjustViewPosition() {
inner.adjustViewPosition()
if (targetPosition != null && currentPosition == null) {
header.isContentVisible = true
}
}
}
}
/**
* Should be called whenever notifs are added, removed, or updated. Updates section boundary
* bookkeeping and adds/moves/removes section headers if appropriate.
*/
fun updateSectionBoundaries(reason: String) = traceSection("NotifSectionsManager.update") {
notifPipelineFlags.checkLegacyPipelineEnabled()
if (!isUsingMultipleSections) {
return@traceSection
}
logger.logStartSectionUpdate(reason)
// The overall strategy here is to iterate over the current children of mParent, looking
// for where the sections headers are currently positioned, and where each section begins.
// Then, once we find the start of a new section, we track that position as the "target" for
// the section header, adjusted for the case where existing headers are in front of that
// target, but won't be once they are moved / removed after the pass has completed.
val showHeaders = statusBarStateController.state != StatusBarState.KEYGUARD
val usingMediaControls = sectionsFeatureManager.isMediaControlsEnabled()
val mediaState = mediaControlsView?.let(::expandableViewHeaderState)
val incomingState = incomingHeaderView?.let(::decorViewHeaderState)
val peopleState = peopleHeaderView?.let(::decorViewHeaderState)
val alertingState = alertingHeaderView?.let(::decorViewHeaderState)
val gentleState = silentHeaderView?.let(::decorViewHeaderState)
fun getSectionState(view: View): SectionUpdateState<ExpandableView>? = when {
view === mediaControlsView -> mediaState
view === incomingHeaderView -> incomingState
view === peopleHeaderView -> peopleState
view === alertingHeaderView -> alertingState
view === silentHeaderView -> gentleState
else -> null
}
val headersOrdered = sequenceOf(
mediaState, incomingState, peopleState, alertingState, gentleState
).filterNotNull()
var peopleNotifsPresent = false
var nextBucket: Int? = null
var inIncomingSection = false
// Iterating backwards allows for easier construction of the Incoming section, as opposed
// to backtracking when a discontinuity in the sections is discovered.
// Iterating to -1 in order to support the case where a header is at the very top of the
// shade.
for (i in parent.childCount - 1 downTo -1) {
val child: View? = parent.getChildAt(i)
child?.let {
logShadeChild(i, child)
// If this child is a header, update the tracked positions
getSectionState(child)?.let { state ->
state.currentPosition = i
// If headers that should appear above this one in the shade already have a
// target index, then we need to decrement them in order to account for this one
// being either removed, or moved below them.
headersOrdered.takeUntil { it === state }
.forEach { it.targetPosition = it.targetPosition?.minus(1) }
}
}
val row = (child as? ExpandableNotificationRow)
?.takeUnless { it.visibility == View.GONE }
// Is there a section discontinuity? This usually occurs due to HUNs
inIncomingSection = inIncomingSection || nextBucket?.let { next ->
row?.entry?.bucket?.let { curr -> next < curr }
} == true
if (inIncomingSection) {
// Update the bucket to reflect that it's being placed in the Incoming section
row?.entry?.bucket = BUCKET_HEADS_UP
}
// Insert a header in front of the next row, if there's a boundary between it and this
// row, or if it is the topmost row.
val isSectionBoundary = nextBucket != null &&
(child == null || row != null && nextBucket != row.entry.bucket)
if (isSectionBoundary && showHeaders) {
when (nextBucket) {
BUCKET_SILENT -> gentleState?.targetPosition = i + 1
}
}
row ?: continue
// Check if there are any people notifications
peopleNotifsPresent = peopleNotifsPresent || row.entry.bucket == BUCKET_PEOPLE
nextBucket = row.entry.bucket
}
mediaState?.targetPosition = if (usingMediaControls) 0 else null
logger.logStr("New header target positions:")
logger.logMediaControls(mediaState?.targetPosition ?: -1)
logger.logIncomingHeader(incomingState?.targetPosition ?: -1)
logger.logConversationsHeader(peopleState?.targetPosition ?: -1)
logger.logAlertingHeader(alertingState?.targetPosition ?: -1)
logger.logSilentHeader(gentleState?.targetPosition ?: -1)
// Update headers in reverse order to preserve indices, otherwise movements earlier in the
// list will affect the target indices of the headers later in the list.
headersOrdered.asIterable().reversed().forEach { it.adjustViewPosition() }
logger.logStr("Final order:")
logShadeContents()
logger.logStr("Section boundary update complete")
// Update headers to reflect state of section contents
silentHeaderView?.run {
val hasActiveClearableNotifications = this@NotificationSectionsManager.parent
.hasActiveClearableNotifications(NotificationStackScrollLayout.ROWS_GENTLE)
setClearSectionButtonEnabled(hasActiveClearableNotifications)
}
}
private sealed class SectionBounds {
data class Many(

View File

@@ -5822,12 +5822,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mSpeedBumpIndexDirty = true;
}
/** Updates the indices of the boundaries between sections. */
@ShadeViewRefactor(RefactorComponent.INPUT)
public void updateSectionBoundaries(String reason) {
mSectionsManager.updateSectionBoundaries(reason);
}
void updateContinuousBackgroundDrawing() {
boolean continuousBackground = !mAmbientState.isFullyAwake()
&& mSwipeHelper.isSwiping();

View File

@@ -90,7 +90,6 @@ import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
import com.android.systemui.statusbar.notification.collection.PipelineDumper;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
@@ -162,7 +161,6 @@ public class NotificationStackScrollLayoutController {
private final NotificationEntryManager mNotificationEntryManager;
private final UiEventLogger mUiEventLogger;
private final NotificationRemoteInputManager mRemoteInputManager;
private final VisualStabilityManager mVisualStabilityManager;
private final ShadeController mShadeController;
private final KeyguardMediaController mKeyguardMediaController;
private final SysuiStatusBarStateController mStatusBarStateController;
@@ -646,7 +644,6 @@ public class NotificationStackScrollLayoutController {
ShadeTransitionController shadeTransitionController,
UiEventLogger uiEventLogger,
NotificationRemoteInputManager remoteInputManager,
VisualStabilityManager visualStabilityManager,
ShadeController shadeController,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
@@ -693,7 +690,6 @@ public class NotificationStackScrollLayoutController {
mNotificationEntryManager = notificationEntryManager;
mUiEventLogger = uiEventLogger;
mRemoteInputManager = remoteInputManager;
mVisualStabilityManager = visualStabilityManager;
mShadeController = shadeController;
updateResources();
}
@@ -765,8 +761,6 @@ public class NotificationStackScrollLayoutController {
mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
mVisualStabilityManager.setVisibilityLocationProvider(this::isInVisibleLocation);
mTunerService.addTunable(
(key, newValue) -> {
switch (key) {

View File

@@ -206,7 +206,6 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -689,7 +688,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
WakefulnessLifecycle wakefulnessLifecycle,
SysuiStatusBarStateController statusBarStateController,
Optional<Bubbles> bubblesOptional,
VisualStabilityManager visualStabilityManager,
DeviceProvisionedController deviceProvisionedController,
NavigationBarController navigationBarController,
AccessibilityFloatingMenuController accessibilityFloatingMenuController,
@@ -777,7 +775,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
mBubblesOptional = bubblesOptional;
mVisualStabilityManager = visualStabilityManager;
mDeviceProvisionedController = deviceProvisionedController;
mNavigationBarController = navigationBarController;
mAccessibilityFloatingMenuController = accessibilityFloatingMenuController;
@@ -3914,9 +3911,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
// all notifications
protected NotificationStackScrollLayout mStackScroller;
// handling reordering
private final VisualStabilityManager mVisualStabilityManager;
protected AccessibilityManager mAccessibilityManager;
protected boolean mDeviceInteractive;

View File

@@ -330,14 +330,6 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
dumpInternal(pw, args);
}
@Override
public boolean shouldExtendLifetime(NotificationEntry entry) {
// We should not defer the removal if reordering isn't allowed since otherwise
// these won't disappear until reordering is allowed again, which happens only once
// the notification panel is collapsed again.
return mVisualStabilityProvider.isReorderingAllowed() && super.shouldExtendLifetime(entry);
}
///////////////////////////////////////////////////////////////////////////////////////////////
// OnReorderingAllowedListener:

View File

@@ -1,717 +0,0 @@
/*
* Copyright (C) 2018 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.phone;
import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
import static com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.logGroupKey;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.os.SystemClock;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.NotificationGroup;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.notification.row.RowContentBindParams;
import com.android.systemui.statusbar.notification.row.RowContentBindStage;
import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.util.Compile;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.inject.Inject;
/**
* A helper class dealing with the alert interactions between {@link NotificationGroupManagerLegacy}
* and {@link HeadsUpManager}. In particular, this class deals with keeping
* the correct notification in a group alerting based off the group suppression and alertOverride.
*/
@SysUISingleton
public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener,
StateListener {
private static final long ALERT_TRANSFER_TIMEOUT = 300;
private static final String TAG = "NotifGroupAlertTransfer";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final boolean SPEW = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
/**
* The list of entries containing group alert metadata for each group. Keyed by group key.
*/
private final ArrayMap<String, GroupAlertEntry> mGroupAlertEntries = new ArrayMap<>();
/**
* The list of entries currently inflating that should alert after inflation. Keyed by
* notification key.
*/
private final ArrayMap<String, PendingAlertInfo> mPendingAlerts = new ArrayMap<>();
private HeadsUpManager mHeadsUpManager;
private final RowContentBindStage mRowContentBindStage;
private final NotificationGroupManagerLegacy mGroupManager;
private NotificationEntryManager mEntryManager;
private boolean mIsDozing;
/**
* Injected constructor. See {@link StatusBarPhoneModule}.
*/
@Inject
public NotificationGroupAlertTransferHelper(
RowContentBindStage bindStage,
StatusBarStateController statusBarStateController,
NotificationGroupManagerLegacy notificationGroupManagerLegacy) {
mRowContentBindStage = bindStage;
mGroupManager = notificationGroupManagerLegacy;
statusBarStateController.addCallback(this);
}
/** Causes the TransferHelper to register itself as a listener to the appropriate classes. */
public void bind(NotificationEntryManager entryManager,
NotificationGroupManagerLegacy groupManager) {
if (mEntryManager != null) {
throw new IllegalStateException("Already bound.");
}
// TODO(b/119637830): It would be good if GroupManager already had all pending notifications
// as normal children (i.e. add notifications to GroupManager before inflation) so that we
// don't have to have this dependency. We'd also have to worry less about the suppression
// not being up to date.
mEntryManager = entryManager;
mEntryManager.addNotificationEntryListener(mNotificationEntryListener);
groupManager.registerGroupChangeListener(mOnGroupChangeListener);
}
/**
* Whether or not a notification has transferred its alert state to the notification and
* the notification should alert after inflating.
*
* @param entry notification to check
* @return true if the entry was transferred to and should inflate + alert
*/
public boolean isAlertTransferPending(@NonNull NotificationEntry entry) {
PendingAlertInfo alertInfo = mPendingAlerts.get(entry.getKey());
return alertInfo != null && alertInfo.isStillValid();
}
public void setHeadsUpManager(HeadsUpManager headsUpManager) {
mHeadsUpManager = headsUpManager;
}
@Override
public void onStateChanged(int newState) {}
@Override
public void onDozingChanged(boolean isDozing) {
if (mIsDozing != isDozing) {
for (GroupAlertEntry groupAlertEntry : mGroupAlertEntries.values()) {
groupAlertEntry.mLastAlertTransferTime = 0;
groupAlertEntry.mAlertSummaryOnNextAddition = false;
}
}
mIsDozing = isDozing;
}
private final NotificationGroupManagerLegacy.OnGroupChangeListener mOnGroupChangeListener =
new NotificationGroupManagerLegacy.OnGroupChangeListener() {
@Override
public void onGroupCreated(NotificationGroup group, String groupKey) {
mGroupAlertEntries.put(groupKey, new GroupAlertEntry(group));
}
@Override
public void onGroupRemoved(NotificationGroup group, String groupKey) {
mGroupAlertEntries.remove(groupKey);
}
@Override
public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) {
if (DEBUG) {
Log.d(TAG, "!! onGroupSuppressionChanged:"
+ " group=" + logGroupKey(group)
+ " group.summary=" + logKey(group.summary)
+ " suppressed=" + suppressed);
}
NotificationEntry oldAlertOverride = group.alertOverride;
onGroupChanged(group, oldAlertOverride);
}
@Override
public void onGroupAlertOverrideChanged(NotificationGroup group,
@Nullable NotificationEntry oldAlertOverride,
@Nullable NotificationEntry newAlertOverride) {
if (DEBUG) {
Log.d(TAG, "!! onGroupAlertOverrideChanged:"
+ " group=" + logGroupKey(group)
+ " group.summary=" + logKey(group.summary)
+ " oldAlertOverride=" + logKey(oldAlertOverride)
+ " newAlertOverride=" + logKey(newAlertOverride));
}
onGroupChanged(group, oldAlertOverride);
}
};
/**
* Called when either the suppressed or alertOverride fields of the group changed
*
* @param group the group which changed
* @param oldAlertOverride the previous value of group.alertOverride
*/
private void onGroupChanged(NotificationGroup group,
NotificationEntry oldAlertOverride) {
// Group summary can be null if we are no longer suppressed because the summary was
// removed. In that case, we don't need to alert the summary.
if (group.summary == null) {
if (DEBUG) {
Log.d(TAG, "onGroupChanged: summary is null");
}
return;
}
if (group.suppressed || group.alertOverride != null) {
checkForForwardAlertTransfer(group.summary, oldAlertOverride);
} else {
if (DEBUG) {
Log.d(TAG, "onGroupChanged: maybe transfer back");
}
GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(
group.summary.getSbn()));
// Group is no longer suppressed or overridden.
// We should check if we need to transfer the alert back to the summary.
if (groupAlertEntry.mAlertSummaryOnNextAddition) {
if (!mHeadsUpManager.isAlerting(group.summary.getKey())) {
alertNotificationWhenPossible(group.summary);
}
groupAlertEntry.mAlertSummaryOnNextAddition = false;
} else {
checkShouldTransferBack(groupAlertEntry);
}
}
}
@Override
public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
if (DEBUG) {
Log.d(TAG, "!! onHeadsUpStateChanged:"
+ " entry=" + logKey(entry)
+ " isHeadsUp=" + isHeadsUp);
}
if (isHeadsUp && entry.getSbn().getNotification().isGroupSummary()) {
// a group summary is alerting; trigger the forward transfer checks
checkForForwardAlertTransfer(entry, /* oldAlertOverride */ null);
}
}
/**
* Handles changes in a group's suppression or alertOverride, but where at least one of those
* conditions is still true (either the group is suppressed, the group has an alertOverride,
* or both). The method determined which kind of child needs to receive the alert, finds the
* entry currently alerting, and makes the transfer.
*
* Internally, this is handled with two main cases: the override needs the alert, or there is
* no override but the summary is suppressed (so an isolated child needs the alert).
*
* @param summary the notification entry of the summary of the logical group.
* @param oldAlertOverride the former value of group.alertOverride, before whatever event
* required us to check for for a transfer condition.
*/
private void checkForForwardAlertTransfer(NotificationEntry summary,
NotificationEntry oldAlertOverride) {
if (DEBUG) {
Log.d(TAG, "checkForForwardAlertTransfer: enter");
}
NotificationGroup group = mGroupManager.getGroupForSummary(summary.getSbn());
if (group != null && group.alertOverride != null) {
handleOverriddenSummaryAlerted(summary);
} else if (mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())) {
handleSuppressedSummaryAlerted(summary, oldAlertOverride);
}
if (DEBUG) {
Log.d(TAG, "checkForForwardAlertTransfer: done");
}
}
private final NotificationEntryListener mNotificationEntryListener =
new NotificationEntryListener() {
// Called when a new notification has been posted but is not inflated yet. We use this to
// see as early as we can if we need to abort a transfer.
@Override
public void onPendingEntryAdded(NotificationEntry entry) {
if (DEBUG) {
Log.d(TAG, "!! onPendingEntryAdded: entry=" + logKey(entry));
}
String groupKey = mGroupManager.getGroupKey(entry.getSbn());
GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey);
if (groupAlertEntry != null && groupAlertEntry.mGroup.alertOverride == null) {
// new pending group entries require us to transfer back from the child to the
// group, but alertOverrides are only present in very limited circumstances, so
// while it's possible the group should ALSO alert, the previous detection which set
// this alertOverride won't be invalidated by this notification added to this group.
checkShouldTransferBack(groupAlertEntry);
}
}
@Override
public void onEntryRemoved(
@Nullable NotificationEntry entry,
NotificationVisibility visibility,
boolean removedByUser,
int reason) {
// Removes any alerts pending on this entry. Note that this will not stop any inflation
// tasks started by a transfer, so this should only be used as clean-up for when
// inflation is stopped and the pending alert no longer needs to happen.
mPendingAlerts.remove(entry.getKey());
}
};
/**
* Gets the number of new notifications pending inflation that will be added to the group
* but currently aren't and should not alert.
*
* @param group group to check
* @return the number of new notifications that will be added to the group
*/
private int getPendingChildrenNotAlerting(@NonNull NotificationGroup group) {
if (mEntryManager == null) {
return 0;
}
int number = 0;
Iterable<NotificationEntry> values = mEntryManager.getPendingNotificationsIterator();
for (NotificationEntry entry : values) {
if (isPendingNotificationInGroup(entry, group) && onlySummaryAlerts(entry)) {
number++;
}
}
return number;
}
/**
* Checks if the pending inflations will add children to this group.
*
* @param group group to check
* @return true if a pending notification will add to this group
*/
private boolean pendingInflationsWillAddChildren(@NonNull NotificationGroup group) {
if (mEntryManager == null) {
return false;
}
Iterable<NotificationEntry> values = mEntryManager.getPendingNotificationsIterator();
for (NotificationEntry entry : values) {
if (isPendingNotificationInGroup(entry, group)) {
return true;
}
}
return false;
}
/**
* Checks if a new pending notification will be added to the group.
*
* @param entry pending notification
* @param group group to check
* @return true if the notification will add to the group, false o/w
*/
private boolean isPendingNotificationInGroup(@NonNull NotificationEntry entry,
@NonNull NotificationGroup group) {
String groupKey = mGroupManager.getGroupKey(group.summary.getSbn());
return mGroupManager.isGroupChild(entry.getSbn())
&& Objects.equals(mGroupManager.getGroupKey(entry.getSbn()), groupKey)
&& !group.children.containsKey(entry.getKey());
}
/**
* Handles the scenario where a summary that has been suppressed is itself, or has a former
* alertOverride (in the form of an isolated logical child) which was alerted. A suppressed
* summary should for all intents and purposes be invisible to the user and as a result should
* not alert. When this is the case, it is our responsibility to pass the alert to the
* appropriate child which will be the representative notification alerting for the group.
*
* @param summary the summary that is suppressed and (potentially) alerting
* @param oldAlertOverride the alertOverride before whatever event triggered this method. If
* the alert override was removed, this will be the entry that should
* be transferred back from.
*/
private void handleSuppressedSummaryAlerted(@NonNull NotificationEntry summary,
NotificationEntry oldAlertOverride) {
if (DEBUG) {
Log.d(TAG, "handleSuppressedSummaryAlerted: summary=" + logKey(summary));
}
GroupAlertEntry groupAlertEntry =
mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn()));
if (!mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())
|| groupAlertEntry == null) {
if (DEBUG) {
Log.d(TAG, "handleSuppressedSummaryAlerted: invalid state");
}
return;
}
boolean summaryIsAlerting = mHeadsUpManager.isAlerting(summary.getKey());
boolean priorityIsAlerting = oldAlertOverride != null
&& mHeadsUpManager.isAlerting(oldAlertOverride.getKey());
if (!summaryIsAlerting && !priorityIsAlerting) {
if (DEBUG) {
Log.d(TAG, "handleSuppressedSummaryAlerted: no summary or override alerting");
}
return;
}
if (pendingInflationsWillAddChildren(groupAlertEntry.mGroup)) {
// New children will actually be added to this group, let's not transfer the alert.
if (DEBUG) {
Log.d(TAG, "handleSuppressedSummaryAlerted: pending inflations");
}
return;
}
NotificationEntry child =
mGroupManager.getLogicalChildren(summary.getSbn()).iterator().next();
if (summaryIsAlerting) {
if (DEBUG) {
Log.d(TAG, "handleSuppressedSummaryAlerted: transfer summary -> child");
}
tryTransferAlertState(summary, /*from*/ summary, /*to*/ child, groupAlertEntry);
return;
}
// Summary didn't have the alert, so we're in "transfer back" territory. First, make sure
// it's not too late to transfer back, then transfer the alert from the oldAlertOverride to
// the isolated child which should receive the alert.
if (!canStillTransferBack(groupAlertEntry)) {
if (DEBUG) {
Log.d(TAG, "handleSuppressedSummaryAlerted: transfer from override: too late");
}
return;
}
if (DEBUG) {
Log.d(TAG, "handleSuppressedSummaryAlerted: transfer override -> child");
}
tryTransferAlertState(summary, /*from*/ oldAlertOverride, /*to*/ child, groupAlertEntry);
}
/**
* Checks for and handles the scenario where the given entry is the summary of a group which
* has an alertOverride, and either the summary itself or one of its logical isolated children
* is currently alerting (which happens if the summary is suppressed).
*/
private void handleOverriddenSummaryAlerted(NotificationEntry summary) {
if (DEBUG) {
Log.d(TAG, "handleOverriddenSummaryAlerted: summary=" + logKey(summary));
}
GroupAlertEntry groupAlertEntry =
mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn()));
NotificationGroup group = mGroupManager.getGroupForSummary(summary.getSbn());
if (group == null || group.alertOverride == null || groupAlertEntry == null) {
if (DEBUG) {
Log.d(TAG, "handleOverriddenSummaryAlerted: invalid state");
}
return;
}
boolean summaryIsAlerting = mHeadsUpManager.isAlerting(summary.getKey());
if (summaryIsAlerting) {
if (DEBUG) {
Log.d(TAG, "handleOverriddenSummaryAlerted: transfer summary -> override");
}
tryTransferAlertState(summary, /*from*/ summary, group.alertOverride, groupAlertEntry);
return;
}
// Summary didn't have the alert, so we're in "transfer back" territory. First, make sure
// it's not too late to transfer back, then remove the alert from any of the logical
// children, and if one of them was alerting, we can alert the override.
if (!canStillTransferBack(groupAlertEntry)) {
if (DEBUG) {
Log.d(TAG, "handleOverriddenSummaryAlerted: transfer from child: too late");
}
return;
}
List<NotificationEntry> children = mGroupManager.getLogicalChildren(summary.getSbn());
if (children == null) {
if (DEBUG) {
Log.d(TAG, "handleOverriddenSummaryAlerted: no children");
}
return;
}
children.remove(group.alertOverride); // do not release the alert on our desired destination
boolean releasedChild = releaseChildAlerts(children);
if (releasedChild) {
if (DEBUG) {
Log.d(TAG, "handleOverriddenSummaryAlerted: transfer child -> override");
}
tryTransferAlertState(summary, /*from*/ null, group.alertOverride, groupAlertEntry);
} else {
if (DEBUG) {
Log.d(TAG, "handleOverriddenSummaryAlerted: no child alert released");
}
}
}
/**
* Transfers the alert state one entry to another. We remove the alert from the first entry
* immediately to have the incorrect one up as short as possible. The second should alert
* when possible.
*
* @param summary entry of the summary
* @param fromEntry entry to transfer alert from
* @param toEntry entry to transfer to
*/
private void tryTransferAlertState(
NotificationEntry summary,
NotificationEntry fromEntry,
NotificationEntry toEntry,
GroupAlertEntry groupAlertEntry) {
if (toEntry != null) {
if (toEntry.getRow().keepInParent()
|| toEntry.isRowRemoved()
|| toEntry.isRowDismissed()) {
// The notification is actually already removed. No need to alert it.
return;
}
if (!mHeadsUpManager.isAlerting(toEntry.getKey()) && onlySummaryAlerts(summary)) {
groupAlertEntry.mLastAlertTransferTime = SystemClock.elapsedRealtime();
}
if (DEBUG) {
Log.d(TAG, "transferAlertState:"
+ " fromEntry=" + logKey(fromEntry)
+ " toEntry=" + logKey(toEntry));
}
transferAlertState(fromEntry, toEntry);
}
}
private void transferAlertState(@Nullable NotificationEntry fromEntry,
@NonNull NotificationEntry toEntry) {
if (fromEntry != null) {
mHeadsUpManager.removeNotification(fromEntry.getKey(), true /* releaseImmediately */);
}
alertNotificationWhenPossible(toEntry);
}
/**
* Determines if we need to transfer the alert back to the summary from the child and does
* so if needed.
*
* This can happen since notification groups are not delivered as a whole unit and it is
* possible we erroneously transfer the alert from the summary to the child even though
* more children are coming. Thus, if a child is added within a certain timeframe after we
* transfer, we back out and alert the summary again.
*
* An alert can only transfer back within a small window of time after a transfer away from the
* summary to a child happened.
*
* @param groupAlertEntry group alert entry to check
*/
private void checkShouldTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
if (canStillTransferBack(groupAlertEntry)) {
NotificationEntry summary = groupAlertEntry.mGroup.summary;
if (!onlySummaryAlerts(summary)) {
return;
}
ArrayList<NotificationEntry> children = mGroupManager.getLogicalChildren(
summary.getSbn());
int numActiveChildren = children.size();
int numPendingChildren = getPendingChildrenNotAlerting(groupAlertEntry.mGroup);
int numChildren = numActiveChildren + numPendingChildren;
if (numChildren <= 1) {
return;
}
boolean releasedChild = releaseChildAlerts(children);
if (releasedChild && !mHeadsUpManager.isAlerting(summary.getKey())) {
boolean notifyImmediately = numActiveChildren > 1;
if (notifyImmediately) {
alertNotificationWhenPossible(summary);
} else {
// Should wait until the pending child inflates before alerting.
groupAlertEntry.mAlertSummaryOnNextAddition = true;
}
groupAlertEntry.mLastAlertTransferTime = 0;
}
}
}
private boolean canStillTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
return SystemClock.elapsedRealtime() - groupAlertEntry.mLastAlertTransferTime
< ALERT_TRANSFER_TIMEOUT;
}
private boolean releaseChildAlerts(List<NotificationEntry> children) {
boolean releasedChild = false;
if (SPEW) {
Log.d(TAG, "releaseChildAlerts: numChildren=" + children.size());
}
for (int i = 0; i < children.size(); i++) {
NotificationEntry entry = children.get(i);
if (SPEW) {
Log.d(TAG, "releaseChildAlerts: checking i=" + i + " entry=" + entry
+ " onlySummaryAlerts=" + onlySummaryAlerts(entry)
+ " isAlerting=" + mHeadsUpManager.isAlerting(entry.getKey())
+ " isPendingAlert=" + mPendingAlerts.containsKey(entry.getKey()));
}
if (onlySummaryAlerts(entry) && mHeadsUpManager.isAlerting(entry.getKey())) {
releasedChild = true;
mHeadsUpManager.removeNotification(
entry.getKey(), true /* releaseImmediately */);
}
if (mPendingAlerts.containsKey(entry.getKey())) {
// This is the child that would've been removed if it was inflated.
releasedChild = true;
mPendingAlerts.get(entry.getKey()).mAbortOnInflation = true;
}
}
if (SPEW) {
Log.d(TAG, "releaseChildAlerts: didRelease=" + releasedChild);
}
return releasedChild;
}
/**
* Tries to alert the notification. If its content view is not inflated, we inflate and continue
* when the entry finishes inflating the view.
*
* @param entry entry to show
*/
private void alertNotificationWhenPossible(@NonNull NotificationEntry entry) {
@InflationFlag int contentFlag = mHeadsUpManager.getContentFlag();
final RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
if ((params.getContentViews() & contentFlag) == 0) {
if (DEBUG) {
Log.d(TAG, "alertNotificationWhenPossible:"
+ " async requestRebind entry=" + logKey(entry));
}
mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry));
params.requireContentViews(contentFlag);
mRowContentBindStage.requestRebind(entry, en -> {
PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey());
if (alertInfo != null) {
if (alertInfo.isStillValid()) {
alertNotificationWhenPossible(entry);
} else {
if (DEBUG) {
Log.d(TAG, "alertNotificationWhenPossible:"
+ " markContentViewsFreeable entry=" + logKey(entry));
}
// The transfer is no longer valid. Free the content.
mRowContentBindStage.getStageParams(entry).markContentViewsFreeable(
contentFlag);
mRowContentBindStage.requestRebind(entry, null);
}
}
});
return;
}
if (mHeadsUpManager.isAlerting(entry.getKey())) {
if (DEBUG) {
Log.d(TAG, "alertNotificationWhenPossible:"
+ " continue alerting entry=" + logKey(entry));
}
mHeadsUpManager.updateNotification(entry.getKey(), true /* alert */);
} else {
if (DEBUG) {
Log.d(TAG, "alertNotificationWhenPossible:"
+ " start alerting entry=" + logKey(entry));
}
mHeadsUpManager.showNotification(entry);
}
}
private boolean onlySummaryAlerts(NotificationEntry entry) {
return entry.getSbn().getNotification().getGroupAlertBehavior()
== Notification.GROUP_ALERT_SUMMARY;
}
/**
* Information about a pending alert used to determine if the alert is still needed when
* inflation completes.
*/
private class PendingAlertInfo {
/**
* The original notification when the transfer is initiated. This is used to determine if
* the transfer is still valid if the notification is updated.
*/
final StatusBarNotification mOriginalNotification;
final NotificationEntry mEntry;
/**
* The notification is still pending inflation but we've decided that we no longer need
* the content view (e.g. suppression might have changed and we decided we need to transfer
* back).
*
* TODO: Replace this entire structure with {@link RowContentBindStage#requestRebind)}.
*/
boolean mAbortOnInflation;
PendingAlertInfo(NotificationEntry entry) {
mOriginalNotification = entry.getSbn();
mEntry = entry;
}
/**
* Whether or not the pending alert is still valid and should still alert after inflation.
*
* @return true if the pending alert should still occur, false o/w
*/
private boolean isStillValid() {
if (mAbortOnInflation) {
// Notification is aborted due to the transfer being explicitly cancelled
return false;
}
if (!mEntry.getSbn().getGroupKey().equals(mOriginalNotification.getGroupKey())) {
// Groups have changed
return false;
}
if (mEntry.getSbn().getNotification().isGroupSummary()
!= mOriginalNotification.getNotification().isGroupSummary()) {
// Notification has changed from group summary to not or vice versa
return false;
}
return true;
}
}
/**
* Contains alert metadata for the notification group used to determine when/how the alert
* should be transferred.
*/
private static class GroupAlertEntry {
/**
* The time when the last alert transfer from summary to child happened.
*/
long mLastAlertTransferTime;
boolean mAlertSummaryOnNextAddition;
final NotificationGroup mGroup;
GroupAlertEntry(NotificationGroup group) {
this.mGroup = group;
}
}
}

View File

@@ -43,7 +43,6 @@ import android.widget.TextView;
import androidx.core.graphics.drawable.IconCompat;
import androidx.test.filters.SmallTest;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -54,7 +53,7 @@ import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import org.junit.Before;
import org.junit.Test;
@@ -83,8 +82,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
LocalBluetoothLeBroadcast.class);
private ActivityStarter mStarter = mock(ActivityStarter.class);
private BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
private NotificationEntryManager mNotificationEntryManager =
mock(NotificationEntryManager.class);
private final CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
private NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
NearbyMediaDevicesManager.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
@@ -120,7 +118,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotificationEntryManager, mDialogLaunchAnimator,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
mMediaOutputController);

View File

@@ -47,7 +47,7 @@ import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import org.junit.After;
import org.junit.Before;
@@ -78,8 +78,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
private final LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class);
private final MediaDevice mMediaDevice = mock(MediaDevice.class);
private final NotificationEntryManager mNotificationEntryManager =
mock(NotificationEntryManager.class);
private final CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
@@ -104,7 +103,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotificationEntryManager, mDialogLaunchAnimator,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,

View File

@@ -63,7 +63,6 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.people.widget.PeopleTileKey;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -196,8 +195,6 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
@Mock
private PackageManager mPackageManager;
@Mock
private NotificationEntryManager mNotificationEntryManager;
@Mock
private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
@Mock
private BackupManager mBackupManager;
@@ -234,8 +231,6 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
when(mMockContext.getString(R.string.over_two_weeks_timestamp)).thenReturn(
mContext.getString(R.string.over_two_weeks_timestamp));
when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null);
when(mNotificationEntryManager.getVisibleNotifications())
.thenReturn(List.of(mNotificationEntry1, mNotificationEntry2, mNotificationEntry3));
}
@After

View File

@@ -25,7 +25,6 @@ import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.app.Notification;
@@ -216,41 +215,4 @@ public class AlertingNotificationManagerTest extends SysuiTestCase {
// The entry has just been added so we should not remove immediately.
assertFalse(mAlertingNotificationManager.canRemoveImmediately(mEntry.getKey()));
}
@Test
public void testShouldExtendLifetime() {
mAlertingNotificationManager.showNotification(mEntry);
// While the entry is alerting, it should not be removable.
assertTrue(mAlertingNotificationManager.shouldExtendLifetime(mEntry));
}
@Test
public void testSetShouldManageLifetime_setShouldManage() {
mAlertingNotificationManager.showNotification(mEntry);
mAlertingNotificationManager.setShouldManageLifetime(mEntry, true /* shouldManage */);
assertTrue(mAlertingNotificationManager.mExtendedLifetimeAlertEntries.contains(mEntry));
}
@Test
public void testSetShouldManageLifetime_setShouldManageCallsRemoval() {
mAlertingNotificationManager.showNotification(mEntry);
mAlertingNotificationManager.setShouldManageLifetime(mEntry, true /* shouldManage */);
if (mAlertingNotificationManager instanceof TestableAlertingNotificationManager) {
TestableAlertingNotificationManager testableManager =
(TestableAlertingNotificationManager) mAlertingNotificationManager;
verify(testableManager.mLastCreatedEntry).removeAsSoonAsPossible();
}
}
@Test
public void testSetShouldManageLifetime_setShouldNotManage() {
mAlertingNotificationManager.mExtendedLifetimeAlertEntries.add(mEntry);
mAlertingNotificationManager.setShouldManageLifetime(mEntry, false /* shouldManage */);
assertFalse(mAlertingNotificationManager.mExtendedLifetimeAlertEntries.contains(mEntry));
}
}

View File

@@ -161,10 +161,8 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
smartReplyController,
visibilityProvider,
notificationEntryManager,
rebuilder,
centralSurfacesOptionalLazy,
statusBarStateController,
mainHandler,
remoteInputUriController,
clickNotifier,
actionClickLogger,

View File

@@ -1,684 +0,0 @@
/*
* Copyright (C) 2017 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 static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArraySet;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Unit tests for {@link NotificationEntryManager}.
*/
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper()
public class NotificationEntryManagerTest extends SysuiTestCase {
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = 0;
@Mock private NotificationPresenter mPresenter;
@Mock private KeyguardEnvironment mEnvironment;
@Mock private ExpandableNotificationRow mRow;
@Mock private NotificationEntryListener mEntryListener;
@Mock private NotifCollectionListener mNotifCollectionListener;
@Mock private NotificationRemoveInterceptor mRemoveInterceptor;
@Mock private HeadsUpManager mHeadsUpManager;
@Mock private RankingMap mRankingMap;
@Mock private NotificationGroupManagerLegacy mGroupManager;
@Mock private NotificationRemoteInputManager mRemoteInputManager;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private RowInflaterTask mAsyncInflationTask;
@Mock private NotificationEntryManagerLogger mLogger;
@Mock private NotifPipelineFlags mNotifPipelineFlags;
@Mock private LeakDetector mLeakDetector;
@Mock private NotificationMediaManager mNotificationMediaManager;
@Mock private NotificationRowBinder mNotificationRowBinder;
@Mock private NotificationListener mNotificationListener;
@Mock private IStatusBarService mStatusBarService;
private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock);
private int mId;
private NotificationEntry mEntry;
private DismissedByUserStats mStats;
private StatusBarNotification mSbn;
private NotificationEntryManager mEntryManager;
private void setUserSentiment(String key, int sentiment) {
doAnswer(invocationOnMock -> {
Ranking ranking = (Ranking)
invocationOnMock.getArguments()[1];
ranking.populate(
key,
0,
false,
0,
0,
IMPORTANCE_DEFAULT,
null, null,
null, null, null, true, sentiment, false, -1, false, null, null, false, false,
false, null, 0, false);
return true;
}).when(mRankingMap).getRanking(eq(key), any(Ranking.class));
}
private void setSmartActions(String key, ArrayList<Notification.Action> smartActions) {
doAnswer(invocationOnMock -> {
Ranking ranking = (Ranking)
invocationOnMock.getArguments()[1];
ranking.populate(
key,
0,
false,
0,
0,
IMPORTANCE_DEFAULT,
null, null,
null, null, null, true,
Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
false, smartActions, null, false, false, false, null, 0, false);
return true;
}).when(mRankingMap).getRanking(eq(key), any(Ranking.class));
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mDependency.injectMockDependency(SmartReplyController.class);
allowTestableLooperAsMainThread();
mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
Handler.createAsync(TestableLooper.get(this).getLooper()));
mEntry = createNotification();
mStats = defaultStats(mEntry);
mSbn = mEntry.getSbn();
mEntryManager = new NotificationEntryManager(
mLogger,
mGroupManager,
mNotifPipelineFlags,
() -> mNotificationRowBinder,
() -> mRemoteInputManager,
mLeakDetector,
mStatusBarService,
mock(DumpManager.class),
mBgExecutor
);
mEntryManager.initialize(
mNotificationListener,
new NotificationRankingManager(
() -> mNotificationMediaManager,
mGroupManager,
mHeadsUpManager,
mock(NotificationFilter.class),
mLogger,
mock(NotificationSectionsFeatureManager.class),
mock(PeopleNotificationIdentifier.class),
mock(HighPriorityProvider.class),
mEnvironment));
mEntryManager.addNotificationEntryListener(mEntryListener);
mEntryManager.addCollectionListener(mNotifCollectionListener);
mEntryManager.addNotificationRemoveInterceptor(mRemoveInterceptor);
setUserSentiment(mSbn.getKey(), Ranking.USER_SENTIMENT_NEUTRAL);
}
@Test
public void testAddNotification_noDuplicateEntriesCreated() {
// GIVEN a notification has been added
mEntryManager.addNotification(mSbn, mRankingMap);
// WHEN the same notification is added multiple times before the previous entry (with
// the same key) didn't finish inflating
mEntryManager.addNotification(mSbn, mRankingMap);
mEntryManager.addNotification(mSbn, mRankingMap);
mEntryManager.addNotification(mSbn, mRankingMap);
// THEN getAllNotifs() only contains exactly one notification with this key
int count = 0;
for (NotificationEntry entry : mEntryManager.getAllNotifs()) {
if (entry.getKey().equals(mSbn.getKey())) {
count++;
}
}
assertEquals("Should only be one entry with key=" + mSbn.getKey() + " in mAllNotifs. "
+ "Instead there are " + count, 1, count);
}
@Test
public void testAddNotification_setsUserSentiment() {
mEntryManager.addNotification(mSbn, mRankingMap);
ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
NotificationEntry.class);
verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
NotificationEntry entry = entryCaptor.getValue();
assertEquals(entry.getUserSentiment(), Ranking.USER_SENTIMENT_NEUTRAL);
}
@Test
public void testUpdateNotification_prePostEntryOrder() throws Exception {
TestableLooper.get(this).processAllMessages();
mEntryManager.addActiveNotificationForTest(mEntry);
mEntryManager.updateNotification(mSbn, mRankingMap);
// Ensure that update callbacks happen in correct order
InOrder order = inOrder(mEntryListener, mPresenter, mEntryListener);
order.verify(mEntryListener).onPreEntryUpdated(mEntry);
order.verify(mEntryListener).onPostEntryUpdated(mEntry);
}
@Test
public void testRemoveNotification() {
mEntry.setRow(mRow);
mEntryManager.addActiveNotificationForTest(mEntry);
mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
verify(mEntryListener).onEntryRemoved(
argThat(matchEntryOnKey()), any(),
eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
verify(mRow).setRemoved();
assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
}
@Test
public void testRemoveUninflatedNotification_removesNotificationFromAllNotifsList() {
// GIVEN an uninflated entry is added
mEntryManager.addNotification(mSbn, mRankingMap);
assertTrue(entriesContainKey(mEntryManager.getAllNotifs(), mSbn.getKey()));
// WHEN the uninflated entry is removed
mEntryManager.performRemoveNotification(mSbn, mock(DismissedByUserStats.class),
UNDEFINED_DISMISS_REASON);
// THEN the entry is still removed from the allNotifications list
assertFalse(entriesContainKey(mEntryManager.getAllNotifs(), mSbn.getKey()));
}
@Test
public void testPerformRemoveNotification_sendRemovalToServer() throws RemoteException {
// GIVEN an entry manager with a notification
mEntryManager.addActiveNotificationForTest(mEntry);
// GIVEN interceptor that doesn't intercept
when(mRemoveInterceptor.onNotificationRemoveRequested(
eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt()))
.thenReturn(false);
// WHEN the notification entry is removed
mEntryManager.performRemoveNotification(mSbn, mStats, REASON_CANCEL);
// THEN notification removal is sent to the server
FakeExecutor.exhaustExecutors(mBgExecutor);
verify(mStatusBarService).onNotificationClear(
mSbn.getPackageName(),
mSbn.getUser().getIdentifier(),
mSbn.getKey(),
mStats.dismissalSurface,
mStats.dismissalSentiment,
mStats.notificationVisibility);
verifyNoMoreInteractions(mStatusBarService);
}
@Test
public void testRemoveNotification_onEntryRemoveNotFiredIfEntryDoesntExist() {
mEntryManager.removeNotification("not_a_real_key", mRankingMap, UNDEFINED_DISMISS_REASON);
verify(mEntryListener, never()).onEntryRemoved(argThat(matchEntryOnKey()), any(),
eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
}
/** Regression test for b/201097913. */
@Test
public void testRemoveNotification_whilePending_onlyCollectionListenerNotified() {
// Add and then remove a pending entry (entry that hasn't been inflated).
mEntryManager.addNotification(mSbn, mRankingMap);
mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
// Verify that only the listener for the NEW pipeline is notified.
// Old pipeline:
verify(mEntryListener, never()).onEntryRemoved(
argThat(matchEntryOnKey()), any(), anyBoolean(), anyInt());
// New pipeline:
verify(mNotifCollectionListener).onEntryRemoved(
argThat(matchEntryOnKey()), anyInt());
}
@Test
public void testUpdateNotificationRanking_noChange() {
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
mEntry.setRow(mRow);
mEntryManager.addActiveNotificationForTest(mEntry);
setSmartActions(mEntry.getKey(), null);
mEntryManager.updateNotificationRanking(mRankingMap);
assertThat(mEntry.getSmartActions()).isEmpty();
}
@Test
public void testUpdateNotificationRanking_pendingNotification() {
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
mEntry.setRow(null);
mEntryManager.mPendingNotifications.put(mEntry.getKey(), mEntry);
setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction())));
mEntryManager.updateNotificationRanking(mRankingMap);
assertEquals(1, mEntry.getSmartActions().size());
assertEquals("action", mEntry.getSmartActions().get(0).title);
}
@Test
public void testUpdatePendingNotification_rankingUpdated() {
// GIVEN a notification with ranking is pending
final Ranking originalRanking = mEntry.getRanking();
mEntryManager.mPendingNotifications.put(mEntry.getKey(), mEntry);
// WHEN the same notification has been updated with a new ranking
final int newRank = 2345;
doAnswer(invocationOnMock -> {
Ranking ranking = (Ranking)
invocationOnMock.getArguments()[1];
ranking.populate(
mEntry.getKey(),
newRank, /* this changed!! */
false,
0,
0,
IMPORTANCE_DEFAULT,
null, null,
null, null, null, true,
Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
false, null, null, false, false, false, null, 0, false);
return true;
}).when(mRankingMap).getRanking(eq(mEntry.getKey()), any(Ranking.class));
mEntryManager.addNotification(mSbn, mRankingMap);
// THEN ranking for the entry has been updated with new ranking
assertEquals(newRank, mEntry.getRanking().getRank());
}
@Test
public void testNotifyChannelModified_notifiesListeners() {
NotificationChannel channel = mock(NotificationChannel.class);
String pkg = "PKG";
mEntryManager.notifyChannelModified(pkg, UserHandle.CURRENT, channel,
NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
verify(mNotifCollectionListener).onNotificationChannelModified(eq(pkg),
eq(UserHandle.CURRENT), eq(channel), eq(NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
verify(mEntryListener).onNotificationChannelModified(eq(pkg),
eq(UserHandle.CURRENT), eq(channel), eq(NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
}
@Test
public void testLifetimeExtenders_ifNotificationIsRetainedItIsntRemoved() {
// GIVEN an entry manager with a notification
mEntryManager.addActiveNotificationForTest(mEntry);
// GIVEN a lifetime extender that always tries to extend lifetime
NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class);
when(extender.shouldExtendLifetime(mEntry)).thenReturn(true);
mEntryManager.addNotificationLifetimeExtender(extender);
// WHEN the notification is removed
mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
// THEN the extender is asked to manage the lifetime
verify(extender).setShouldManageLifetime(mEntry, true);
// THEN the notification is retained
assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
verify(mEntryListener, never()).onEntryRemoved(
argThat(matchEntryOnKey()), any(), eq(false), eq(UNDEFINED_DISMISS_REASON));
}
@Test
public void testLifetimeExtenders_whenRetentionEndsNotificationIsRemoved() {
// GIVEN an entry manager with a notification whose life has been extended
mEntryManager.addActiveNotificationForTest(mEntry);
final FakeNotificationLifetimeExtender extender = new FakeNotificationLifetimeExtender();
mEntryManager.addNotificationLifetimeExtender(extender);
mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
assertTrue(extender.isManaging(mEntry.getKey()));
// WHEN the extender finishes its extension
extender.setExtendLifetimes(false);
extender.getCallback().onSafeToRemove(mEntry.getKey());
// THEN the notification is removed
assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
verify(mEntryListener).onEntryRemoved(
argThat(matchEntryOnKey()), any(), eq(false), eq(UNDEFINED_DISMISS_REASON));
}
@Test
public void testLifetimeExtenders_whenNotificationUpdatedRetainersAreCanceled() {
// GIVEN an entry manager with a notification whose life has been extended
mEntryManager.addActiveNotificationForTest(mEntry);
NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class);
when(extender.shouldExtendLifetime(mEntry)).thenReturn(true);
mEntryManager.addNotificationLifetimeExtender(extender);
mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
// WHEN the notification is updated
mEntryManager.updateNotification(mEntry.getSbn(), mRankingMap);
// THEN the lifetime extension is canceled
verify(extender).setShouldManageLifetime(mEntry, false);
}
@Test
public void testLifetimeExtenders_whenNewExtenderTakesPrecedenceOldExtenderIsCanceled() {
// GIVEN an entry manager with a notification
mEntryManager.addActiveNotificationForTest(mEntry);
// GIVEN two lifetime extenders, the first which never extends and the second which
// always extends
NotificationLifetimeExtender extender1 = mock(NotificationLifetimeExtender.class);
when(extender1.shouldExtendLifetime(mEntry)).thenReturn(false);
NotificationLifetimeExtender extender2 = mock(NotificationLifetimeExtender.class);
when(extender2.shouldExtendLifetime(mEntry)).thenReturn(true);
mEntryManager.addNotificationLifetimeExtender(extender1);
mEntryManager.addNotificationLifetimeExtender(extender2);
// GIVEN a notification was lifetime-extended and extender2 is managing it
mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
verify(extender1, never()).setShouldManageLifetime(mEntry, true);
verify(extender2).setShouldManageLifetime(mEntry, true);
// WHEN the extender1 changes its mind and wants to extend the lifetime of the notif
when(extender1.shouldExtendLifetime(mEntry)).thenReturn(true);
mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
// THEN extender2 stops managing the notif and extender1 starts managing it
verify(extender1).setShouldManageLifetime(mEntry, true);
verify(extender2).setShouldManageLifetime(mEntry, false);
}
/**
* Ensure that calling NotificationEntryManager.performRemoveNotification() doesn't crash when
* given a notification that has already been removed from NotificationData.
*/
@Test
public void testPerformRemoveNotification_removedEntry() {
mEntryManager.removeNotification(mSbn.getKey(), null, 0);
mEntryManager.performRemoveNotification(mSbn, mock(DismissedByUserStats.class),
REASON_CANCEL);
}
@Test
public void testRemoveInterceptor_interceptsDontGetRemoved() throws InterruptedException {
// GIVEN an entry manager with a notification
mEntryManager.addActiveNotificationForTest(mEntry);
// GIVEN interceptor that intercepts that entry
when(mRemoveInterceptor.onNotificationRemoveRequested(
eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt()))
.thenReturn(true);
// WHEN the notification is removed
mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
// THEN the interceptor intercepts & the entry is not removed & no listeners are called
assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
verify(mEntryListener, never()).onEntryRemoved(argThat(matchEntryOnKey()),
any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON));
}
@Test
public void testRemoveInterceptor_notInterceptedGetsRemoved() {
// GIVEN an entry manager with a notification
mEntryManager.addActiveNotificationForTest(mEntry);
// GIVEN interceptor that doesn't intercept
when(mRemoveInterceptor.onNotificationRemoveRequested(
eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt()))
.thenReturn(false);
// WHEN the notification is removed
mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
// THEN the interceptor intercepts & the entry is not removed & no listeners are called
assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
verify(mEntryListener, atLeastOnce()).onEntryRemoved(argThat(matchEntryOnKey()),
any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON));
}
/* Tests annexed from NotificationDataTest go here */
@Test
public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications() {
Notification.Builder n = new Notification.Builder(mContext, "di")
.setSmallIcon(R.drawable.ic_person)
.setContentTitle("Title")
.setContentText("Text");
NotificationEntry e2 = new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_NAME)
.setOpPkg(TEST_PACKAGE_NAME)
.setUid(TEST_UID)
.setId(mId++)
.setNotification(n.build())
.setUser(new UserHandle(ActivityManager.getCurrentUser()))
.setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
.build();
mEntryManager.addActiveNotificationForTest(mEntry);
mEntryManager.addActiveNotificationForTest(e2);
when(mEnvironment.isNotificationForCurrentProfiles(mEntry.getSbn())).thenReturn(false);
when(mEnvironment.isNotificationForCurrentProfiles(e2.getSbn())).thenReturn(true);
List<NotificationEntry> result = mEntryManager.getActiveNotificationsForCurrentUser();
assertEquals(result.size(), 1);
junit.framework.Assert.assertEquals(result.get(0), e2);
}
/* End annex */
private boolean entriesContainKey(Collection<NotificationEntry> entries, String key) {
for (NotificationEntry entry : entries) {
if (entry.getSbn().getKey().equals(key)) {
return true;
}
}
return false;
}
private Notification.Action createAction() {
return new Notification.Action.Builder(
Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
"action",
PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"),
PendingIntent.FLAG_IMMUTABLE)).build();
}
private ArgumentMatcher<NotificationEntry> matchEntryOnKey() {
return e -> e.getKey().equals(mEntry.getKey());
}
private static class FakeNotificationLifetimeExtender implements NotificationLifetimeExtender {
private NotificationSafeToRemoveCallback mCallback;
private boolean mExtendLifetimes = true;
private Set<String> mManagedNotifs = new ArraySet<>();
@Override
public void setCallback(@NonNull NotificationSafeToRemoveCallback callback) {
mCallback = callback;
}
@Override
public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
return mExtendLifetimes;
}
@Override
public void setShouldManageLifetime(
@NonNull NotificationEntry entry,
boolean shouldManage) {
final boolean hasEntry = mManagedNotifs.contains(entry.getKey());
if (shouldManage) {
if (hasEntry) {
throw new RuntimeException("Already managing this entry: " + entry.getKey());
}
mManagedNotifs.add(entry.getKey());
} else {
if (!hasEntry) {
throw new RuntimeException("Not managing this entry: " + entry.getKey());
}
mManagedNotifs.remove(entry.getKey());
}
}
public void setExtendLifetimes(boolean extendLifetimes) {
mExtendLifetimes = extendLifetimes;
}
public NotificationSafeToRemoveCallback getCallback() {
return mCallback;
}
public boolean isManaging(String notificationKey) {
return mManagedNotifs.contains(notificationKey);
}
}
private NotificationEntry createNotification() {
Notification.Builder n = new Notification.Builder(mContext, "id")
.setSmallIcon(R.drawable.ic_person)
.setContentTitle("Title")
.setContentText("Text");
return new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_NAME)
.setOpPkg(TEST_PACKAGE_NAME)
.setUid(TEST_UID)
.setId(mId++)
.setNotification(n.build())
.setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
.setUser(new UserHandle(ActivityManager.getCurrentUser()))
.build();
}
private static DismissedByUserStats defaultStats(NotificationEntry entry) {
return new DismissedByUserStats(
DISMISSAL_SHADE,
DISMISS_SENTIMENT_NEUTRAL,
NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
}
}

View File

@@ -1,268 +0,0 @@
/*
* Copyright (C) 2018 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 static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.Notification;
import android.app.Notification.MediaStyle;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.media.session.MediaSession;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import androidx.annotation.NonNull;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.provider.DebugModeFilterProvider;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.wm.shell.bubbles.Bubbles;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Optional;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class NotificationFilterTest extends SysuiTestCase {
private static final int UID_NORMAL = 123;
private static final int UID_ALLOW_DURING_SETUP = 456;
private static final String TEST_HIDDEN_NOTIFICATION_KEY = "testHiddenNotificationKey";
private final StatusBarNotification mMockStatusBarNotification =
mock(StatusBarNotification.class);
@Mock
DebugModeFilterProvider mDebugModeFilterProvider;
@Mock
StatusBarStateController mStatusBarStateController;
@Mock
KeyguardEnvironment mEnvironment;
@Mock
ForegroundServiceController mFsc;
@Mock
NotificationLockscreenUserManager mUserManager;
@Mock
MediaFeatureFlag mMediaFeatureFlag;
private final IPackageManager mMockPackageManager = mock(IPackageManager.class);
private NotificationFilter mNotificationFilter;
private ExpandableNotificationRow mRow;
private NotificationEntry mMediaEntry;
private MediaSession mMediaSession;
@Before
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
MockitoAnnotations.initMocks(this);
when(mMockStatusBarNotification.getUid()).thenReturn(UID_NORMAL);
mMediaSession = new MediaSession(mContext, "TEST_MEDIA_SESSION");
NotificationEntryBuilder builder = new NotificationEntryBuilder();
builder.modifyNotification(mContext).setStyle(
new MediaStyle().setMediaSession(mMediaSession.getSessionToken()));
mMediaEntry = builder.build();
when(mMockPackageManager.checkUidPermission(
eq(Manifest.permission.NOTIFICATION_DURING_SETUP),
eq(UID_NORMAL)))
.thenReturn(PackageManager.PERMISSION_DENIED);
when(mMockPackageManager.checkUidPermission(
eq(Manifest.permission.NOTIFICATION_DURING_SETUP),
eq(UID_ALLOW_DURING_SETUP)))
.thenReturn(PackageManager.PERMISSION_GRANTED);
mDependency.injectTestDependency(ForegroundServiceController.class, mFsc);
mDependency.injectTestDependency(NotificationGroupManagerLegacy.class,
new NotificationGroupManagerLegacy(
mock(StatusBarStateController.class),
() -> mock(PeopleNotificationIdentifier.class),
Optional.of(mock(Bubbles.class)),
mock(DumpManager.class)));
mDependency.injectMockDependency(ShadeController.class);
mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
NotificationTestHelper testHelper = new NotificationTestHelper(
mContext,
mDependency,
TestableLooper.get(this));
mRow = testHelper.createRow();
mNotificationFilter = newNotificationFilter();
}
@NonNull
private NotificationFilter newNotificationFilter() {
return new NotificationFilter(
mDebugModeFilterProvider,
mStatusBarStateController,
mEnvironment,
mFsc,
mUserManager,
mMediaFeatureFlag);
}
@After
public void tearDown() {
mMediaSession.release();
}
@Test
@UiThreadTest
public void testShowNotificationEvenIfUnprovisioned_FalseIfNoExtra() {
initStatusBarNotification(false);
when(mMockStatusBarNotification.getUid()).thenReturn(UID_ALLOW_DURING_SETUP);
assertFalse(
NotificationFilter.showNotificationEvenIfUnprovisioned(
mMockPackageManager,
mMockStatusBarNotification));
}
@Test
@UiThreadTest
public void testShowNotificationEvenIfUnprovisioned_FalseIfNoPermission() {
initStatusBarNotification(true);
assertFalse(
NotificationFilter.showNotificationEvenIfUnprovisioned(
mMockPackageManager,
mMockStatusBarNotification));
}
@Test
@UiThreadTest
public void testShowNotificationEvenIfUnprovisioned_TrueIfHasPermissionAndExtra() {
initStatusBarNotification(true);
when(mMockStatusBarNotification.getUid()).thenReturn(UID_ALLOW_DURING_SETUP);
assertTrue(
NotificationFilter.showNotificationEvenIfUnprovisioned(
mMockPackageManager,
mMockStatusBarNotification));
}
@Test
public void testShouldFilterHiddenNotifications() {
initStatusBarNotification(false);
// setup
when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false);
// test should filter out hidden notifications:
// hidden
NotificationEntry entry = new NotificationEntryBuilder()
.setSuspended(true)
.build();
assertTrue(mNotificationFilter.shouldFilterOut(entry));
// not hidden
entry = new NotificationEntryBuilder()
.setSuspended(false)
.build();
assertFalse(mNotificationFilter.shouldFilterOut(entry));
}
@Test
public void shouldFilterOtherNotificationWhenDisabled() {
// GIVEN that the media feature is disabled
when(mMediaFeatureFlag.getEnabled()).thenReturn(false);
NotificationFilter filter = newNotificationFilter();
// WHEN the media filter is asked about an entry
NotificationEntry otherEntry = new NotificationEntryBuilder().build();
final boolean shouldFilter = filter.shouldFilterOut(otherEntry);
// THEN it shouldn't be filtered
assertFalse(shouldFilter);
}
@Test
public void shouldFilterOtherNotificationWhenEnabled() {
// GIVEN that the media feature is enabled
when(mMediaFeatureFlag.getEnabled()).thenReturn(true);
NotificationFilter filter = newNotificationFilter();
// WHEN the media filter is asked about an entry
NotificationEntry otherEntry = new NotificationEntryBuilder().build();
final boolean shouldFilter = filter.shouldFilterOut(otherEntry);
// THEN it shouldn't be filtered
assertFalse(shouldFilter);
}
@Test
public void shouldFilterMediaNotificationWhenDisabled() {
// GIVEN that the media feature is disabled
when(mMediaFeatureFlag.getEnabled()).thenReturn(false);
NotificationFilter filter = newNotificationFilter();
// WHEN the media filter is asked about a media entry
final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry);
// THEN it shouldn't be filtered
assertFalse(shouldFilter);
}
@Test
public void shouldFilterMediaNotificationWhenEnabled() {
// GIVEN that the media feature is enabled
when(mMediaFeatureFlag.getEnabled()).thenReturn(true);
NotificationFilter filter = newNotificationFilter();
// WHEN the media filter is asked about a media entry
final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry);
// THEN it should be filtered
assertTrue(shouldFilter);
}
private void initStatusBarNotification(boolean allowDuringSetup) {
Bundle bundle = new Bundle();
bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup);
Notification notification = new Notification.Builder(mContext, "test")
.addExtras(bundle)
.build();
when(mMockStatusBarNotification.getNotification()).thenReturn(notification);
}
}

View File

@@ -1,293 +0,0 @@
/*
* Copyright (C) 2017 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 static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper()
public class VisualStabilityManagerTest extends SysuiTestCase {
private TestableLooper mTestableLooper;
private VisualStabilityManager mVisualStabilityManager;
private VisualStabilityProvider mVisualStabilityProvider = mock(VisualStabilityProvider.class);
private VisualStabilityManager.Callback mCallback = mock(VisualStabilityManager.Callback.class);
private VisibilityLocationProvider mLocationProvider = mock(VisibilityLocationProvider.class);
private ExpandableNotificationRow mRow = mock(ExpandableNotificationRow.class);
private NotificationEntry mEntry;
private StatusBarStateController.StateListener mStatusBarStateListener;
private WakefulnessLifecycle.Observer mWakefulnessObserver;
@Before
public void setUp() {
StatusBarStateController statusBarStateController = mock(StatusBarStateController.class);
WakefulnessLifecycle wakefulnessLifecycle = mock(WakefulnessLifecycle.class);
mTestableLooper = TestableLooper.get(this);
mVisualStabilityManager = new VisualStabilityManager(
mock(NotificationEntryManager.class),
mVisualStabilityProvider,
new Handler(mTestableLooper.getLooper()),
statusBarStateController,
wakefulnessLifecycle,
mock(DumpManager.class));
mVisualStabilityManager.setVisibilityLocationProvider(mLocationProvider);
mEntry = new NotificationEntryBuilder().build();
mEntry.setRow(mRow);
when(mRow.getEntry()).thenReturn(mEntry);
ArgumentCaptor<StatusBarStateController.StateListener> stateListenerCaptor =
ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
verify(statusBarStateController).addCallback(stateListenerCaptor.capture());
mStatusBarStateListener = stateListenerCaptor.getValue();
ArgumentCaptor<WakefulnessLifecycle.Observer> wakefulnessObserverCaptor =
ArgumentCaptor.forClass(WakefulnessLifecycle.Observer.class);
verify(wakefulnessLifecycle).addObserver(wakefulnessObserverCaptor.capture());
mWakefulnessObserver = wakefulnessObserverCaptor.getValue();
}
@Test
public void testPanelExpansion() {
setPanelExpanded(true);
setScreenOn(true);
assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
setPanelExpanded(false);
assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
}
@Test
public void testScreenOn() {
setPanelExpanded(true);
setScreenOn(true);
assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
setScreenOn(false);
assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
}
@Test
public void testReorderingAllowedChangesScreenOn() {
setPanelExpanded(true);
setScreenOn(true);
assertFalse(mVisualStabilityManager.isReorderingAllowed());
setScreenOn(false);
assertTrue(mVisualStabilityManager.isReorderingAllowed());
}
@Test
public void testReorderingAllowedChangesPanel() {
setPanelExpanded(true);
setScreenOn(true);
assertFalse(mVisualStabilityManager.isReorderingAllowed());
setPanelExpanded(false);
assertTrue(mVisualStabilityManager.isReorderingAllowed());
}
@Test
public void testCallBackCalledScreenOn() {
setPanelExpanded(true);
setScreenOn(true);
mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
setScreenOn(false);
verify(mCallback).onChangeAllowed();
}
@Test
public void testCallBackCalledPanelExpanded() {
setPanelExpanded(true);
setScreenOn(true);
mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
setPanelExpanded(false);
verify(mCallback).onChangeAllowed();
}
@Test
public void testCallBackExactlyOnce() {
setPanelExpanded(true);
setScreenOn(true);
mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
setScreenOn(false);
setScreenOn(true);
setScreenOn(false);
verify(mCallback).onChangeAllowed();
}
@Test
public void testCallBackCalledContinuouslyWhenRequested() {
setPanelExpanded(true);
setScreenOn(true);
mVisualStabilityManager.addReorderingAllowedCallback(mCallback, true /* persistent */);
setScreenOn(false);
setScreenOn(true);
setScreenOn(false);
verify(mCallback, times(2)).onChangeAllowed();
}
@Test
public void testAddedCanReorder() {
setPanelExpanded(true);
setScreenOn(true);
mVisualStabilityManager.notifyViewAddition(mRow);
assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
}
@Test
public void testReorderingVisibleHeadsUpNotAllowed() {
setPanelExpanded(true);
setScreenOn(true);
when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(true);
mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
}
@Test
public void testReorderingVisibleHeadsUpAllowed() {
setPanelExpanded(true);
setScreenOn(true);
when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(false);
mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
}
@Test
public void testReorderingVisibleHeadsUpAllowedOnce() {
setPanelExpanded(true);
setScreenOn(true);
when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(false);
mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
mVisualStabilityManager.onReorderingFinished();
assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
}
@Test
public void testPulsing() {
setPulsing(true);
assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
setPulsing(false);
assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
}
@Test
public void testReorderingAllowedChanges_Pulsing() {
setPulsing(true);
assertFalse(mVisualStabilityManager.isReorderingAllowed());
setPulsing(false);
assertTrue(mVisualStabilityManager.isReorderingAllowed());
}
@Test
public void testCallBackCalled_Pulsing() {
setPulsing(true);
mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
setPulsing(false);
verify(mCallback).onChangeAllowed();
}
@Test
public void testTemporarilyAllowReorderingNotifiesCallbacks() {
// GIVEN having the panel open (which would block reordering)
setScreenOn(true);
setPanelExpanded(true);
mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
// WHEN we temprarily allow reordering
mVisualStabilityManager.temporarilyAllowReordering();
// THEN callbacks are notified that reordering is allowed
verify(mCallback).onChangeAllowed();
assertTrue(mVisualStabilityManager.isReorderingAllowed());
}
@Test
public void testTemporarilyAllowReorderingDoesntOverridePulsing() {
// GIVEN we are in a pulsing state
setPulsing(true);
mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
// WHEN we temprarily allow reordering
mVisualStabilityManager.temporarilyAllowReordering();
// THEN reordering is still not allowed
verify(mCallback, never()).onChangeAllowed();
assertFalse(mVisualStabilityManager.isReorderingAllowed());
}
@Test
public void testTemporarilyAllowReorderingExpires() {
// GIVEN having the panel open (which would block reordering)
setScreenOn(true);
setPanelExpanded(true);
mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
// WHEN we temprarily allow reordering and then wait until the window expires
mVisualStabilityManager.temporarilyAllowReordering();
assertTrue(mVisualStabilityManager.isReorderingAllowed());
mTestableLooper.processMessages(1);
// THEN reordering is no longer allowed
assertFalse(mVisualStabilityManager.isReorderingAllowed());
}
private void setPanelExpanded(boolean expanded) {
mStatusBarStateListener.onExpandedChanged(expanded);
}
private void setPulsing(boolean pulsing) {
mStatusBarStateListener.onPulsingChanged(pulsing);
}
private void setScreenOn(boolean screenOn) {
if (screenOn) {
mWakefulnessObserver.onStartedWakingUp();
} else {
mWakefulnessObserver.onFinishedGoingToSleep();
}
}
}

View File

@@ -1,509 +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.collection
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.NotificationManager.IMPORTANCE_LOW
import android.app.PendingIntent
import android.app.Person
import android.os.SystemClock
import android.service.notification.NotificationListenerService.RankingMap
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment
import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger
import com.android.systemui.statusbar.notification.NotificationFilter
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
import junit.framework.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
class NotificationRankingManagerTest : SysuiTestCase() {
private val lazyMedia: Lazy<NotificationMediaManager> = Lazy {
mock(NotificationMediaManager::class.java)
}
private lateinit var personNotificationIdentifier: PeopleNotificationIdentifier
private lateinit var rankingManager: TestableNotificationRankingManager
private lateinit var sectionsManager: NotificationSectionsFeatureManager
private lateinit var notificationFilter: NotificationFilter
@Before
fun setup() {
personNotificationIdentifier =
mock(PeopleNotificationIdentifier::class.java)
sectionsManager = mock(NotificationSectionsFeatureManager::class.java)
notificationFilter = mock(NotificationFilter::class.java)
rankingManager = TestableNotificationRankingManager(
lazyMedia,
mock(NotificationGroupManagerLegacy::class.java),
mock(HeadsUpManager::class.java),
notificationFilter,
mock(NotificationEntryManagerLogger::class.java),
sectionsManager,
personNotificationIdentifier,
HighPriorityProvider(
personNotificationIdentifier,
mock(NotificationGroupManagerLegacy::class.java)),
mock(KeyguardEnvironment::class.java)
)
}
@Test
fun testSort_highPriorityTrumpsNMSRank() {
// NMS rank says A and then B. But A is not high priority and B is, so B should sort in
// front
val a = NotificationEntryBuilder()
.setImportance(IMPORTANCE_LOW) // low priority
.setRank(1) // NMS says rank first
.setPkg("pkg")
.setOpPkg("pkg")
.setTag("tag")
.setNotification(
Notification.Builder(mContext, "test")
.build())
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
val b = NotificationEntryBuilder()
.setImportance(IMPORTANCE_HIGH) // high priority
.setRank(2) // NMS says rank second
.setPkg("pkg2")
.setOpPkg("pkg2")
.setTag("tag")
.setNotification(
Notification.Builder(mContext, "test")
.build())
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
assertEquals(
listOf(b, a),
rankingManager.updateRanking(null, listOf(a, b), "test"))
}
@Test
fun testSort_samePriorityUsesNMSRank() {
// NMS rank says A and then B, and they are the same priority so use that rank
val aN = Notification.Builder(mContext, "test")
.setStyle(Notification.MessagingStyle(""))
.build()
val a = NotificationEntryBuilder()
.setRank(1)
.setImportance(IMPORTANCE_HIGH)
.setPkg("pkg")
.setOpPkg("pkg")
.setTag("tag")
.setNotification(aN)
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
val bN = Notification.Builder(mContext, "test")
.setStyle(Notification.MessagingStyle(""))
.build()
val b = NotificationEntryBuilder()
.setRank(2)
.setImportance(IMPORTANCE_HIGH)
.setPkg("pkg2")
.setOpPkg("pkg2")
.setTag("tag")
.setNotification(bN)
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
assertEquals(
listOf(a, b),
rankingManager.updateRanking(null, listOf(a, b), "test"))
}
@Test
fun testSort_headsUp_trumpsPeople() {
whenever(sectionsManager.isFilteringEnabled()).thenReturn(true)
val aN = Notification.Builder(mContext, "test")
.setStyle(Notification.MessagingStyle(""))
.build()
val a = NotificationEntryBuilder()
.setImportance(IMPORTANCE_HIGH)
.setPkg("pkg")
.setOpPkg("pkg")
.setTag("tag")
.setNotification(aN)
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
whenever(personNotificationIdentifier.getPeopleNotificationType(a))
.thenReturn(TYPE_IMPORTANT_PERSON)
val bN = Notification.Builder(mContext, "test")
.setStyle(Notification.MessagingStyle(""))
.build()
val b = NotificationEntryBuilder()
.setImportance(IMPORTANCE_HIGH)
.setPkg("pkg2")
.setOpPkg("pkg2")
.setTag("tag")
.setNotification(bN)
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
b.row = mock(ExpandableNotificationRow::class.java).also {
whenever(it.isHeadsUp).thenReturn(true)
}
whenever(personNotificationIdentifier.getPeopleNotificationType(a))
.thenReturn(TYPE_PERSON)
assertEquals(listOf(b, a), rankingManager.updateRanking(null, listOf(a, b), "test"))
}
@Test
fun testSort_importantPeople() {
whenever(sectionsManager.isFilteringEnabled()).thenReturn(true)
val aN = Notification.Builder(mContext, "test")
.setStyle(Notification.MessagingStyle(""))
.build()
val a = NotificationEntryBuilder()
.setImportance(IMPORTANCE_HIGH)
.setPkg("pkg")
.setOpPkg("pkg")
.setTag("tag")
.setNotification(aN)
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.user)
.setOverrideGroupKey("")
.build()
whenever(personNotificationIdentifier.getPeopleNotificationType(a))
.thenReturn(TYPE_PERSON)
val bN = Notification.Builder(mContext, "test")
.setStyle(Notification.MessagingStyle(""))
.build()
val b = NotificationEntryBuilder()
.setImportance(IMPORTANCE_HIGH)
.setPkg("pkg2")
.setOpPkg("pkg2")
.setTag("tag")
.setNotification(bN)
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.user)
.setOverrideGroupKey("")
.build()
whenever(personNotificationIdentifier.getPeopleNotificationType(b))
.thenReturn(TYPE_IMPORTANT_PERSON)
whenever(personNotificationIdentifier.compareTo(TYPE_PERSON, TYPE_IMPORTANT_PERSON))
.thenReturn(TYPE_IMPORTANT_PERSON.compareTo(TYPE_PERSON))
whenever(personNotificationIdentifier.compareTo(TYPE_IMPORTANT_PERSON, TYPE_PERSON))
.thenReturn(TYPE_PERSON.compareTo(TYPE_IMPORTANT_PERSON))
assertEquals(
listOf(b, a),
rankingManager.updateRanking(null, listOf(a, b), "test"))
}
@Test
fun testSort_fullPeople() {
whenever(sectionsManager.isFilteringEnabled()).thenReturn(true)
val aN = Notification.Builder(mContext, "test")
.setStyle(Notification.MessagingStyle(""))
.build()
val a = NotificationEntryBuilder()
.setImportance(IMPORTANCE_HIGH)
.setPkg("pkg")
.setOpPkg("pkg")
.setTag("tag")
.setNotification(aN)
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.user)
.setOverrideGroupKey("")
.build()
whenever(personNotificationIdentifier.getPeopleNotificationType(a))
.thenReturn(TYPE_PERSON)
val bN = Notification.Builder(mContext, "test")
.setStyle(Notification.MessagingStyle(""))
.build()
val b = NotificationEntryBuilder()
.setImportance(IMPORTANCE_HIGH)
.setPkg("pkg2")
.setOpPkg("pkg2")
.setTag("tag")
.setNotification(bN)
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.user)
.setOverrideGroupKey("")
.build()
whenever(personNotificationIdentifier.getPeopleNotificationType(b))
.thenReturn(TYPE_FULL_PERSON)
whenever(personNotificationIdentifier.compareTo(TYPE_PERSON, TYPE_FULL_PERSON))
.thenReturn(TYPE_FULL_PERSON.compareTo(TYPE_PERSON))
whenever(personNotificationIdentifier.compareTo(TYPE_FULL_PERSON, TYPE_PERSON))
.thenReturn(TYPE_PERSON.compareTo(TYPE_FULL_PERSON))
assertEquals(
listOf(b, a),
rankingManager.updateRanking(null, listOf(a, b), "test"))
}
@Test
fun testSort_properlySetsAlertingBucket() {
val notif = Notification.Builder(mContext, "test") .build()
val e = NotificationEntryBuilder()
.setPkg("pkg")
.setOpPkg("pkg")
.setTag("tag")
.setNotification(notif)
.setUser(mContext.user)
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setOverrideGroupKey("")
.build()
modifyRanking(e).setImportance(IMPORTANCE_DEFAULT).build()
rankingManager.updateRanking(RankingMap(arrayOf(e.ranking)), listOf(e), "test")
assertEquals(e.bucket, BUCKET_ALERTING)
}
@Test
fun testSort_properlySetsSilentBucket() {
val notif = Notification.Builder(mContext, "test") .build()
val e = NotificationEntryBuilder()
.setPkg("pkg")
.setOpPkg("pkg")
.setTag("tag")
.setNotification(notif)
.setUser(mContext.user)
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setOverrideGroupKey("")
.build()
modifyRanking(e).setImportance(IMPORTANCE_LOW).build()
rankingManager.updateRanking(RankingMap(arrayOf(e.ranking)), listOf(e), "test")
assertEquals(e.bucket, BUCKET_SILENT)
}
@Test
fun testFilter_resetsInitalizationTime() {
// GIVEN an entry that was initialized 1 second ago
val notif = Notification.Builder(mContext, "test") .build()
val e = NotificationEntryBuilder()
.setPkg("pkg")
.setOpPkg("pkg")
.setTag("tag")
.setNotification(notif)
.setUser(mContext.user)
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setOverrideGroupKey("")
.build()
e.setInitializationTime(SystemClock.elapsedRealtime() - 1000)
assertEquals(true, e.hasFinishedInitialization())
// WHEN we update ranking and filter out the notification entry
whenever(notificationFilter.shouldFilterOut(e)).thenReturn(true)
rankingManager.updateRanking(RankingMap(arrayOf(e.ranking)), listOf(e), "test")
// THEN the initialization time for the entry is reset
assertEquals(false, e.hasFinishedInitialization())
}
@Test
fun testSort_colorizedForegroundService() {
whenever(sectionsManager.isFilteringEnabled()).thenReturn(true)
val a = NotificationEntryBuilder()
.setImportance(IMPORTANCE_HIGH)
.setPkg("pkg")
.setOpPkg("pkg")
.setTag("tag")
.setNotification(
Notification.Builder(mContext, "test")
.build())
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
val b = NotificationEntryBuilder()
.setImportance(IMPORTANCE_DEFAULT) // high priority
.setPkg("pkg2")
.setOpPkg("pkg2")
.setTag("tag")
.setNotification(mock(Notification::class.java).also { notif ->
whenever(notif.isForegroundService).thenReturn(true)
whenever(notif.isColorized).thenReturn(true)
})
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
val cN = Notification.Builder(mContext, "test")
.setStyle(Notification.MessagingStyle(""))
.build()
val c = NotificationEntryBuilder()
.setImportance(IMPORTANCE_HIGH)
.setPkg("pkg")
.setOpPkg("pkg")
.setTag("tag")
.setNotification(cN)
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.user)
.setOverrideGroupKey("")
.build()
whenever(personNotificationIdentifier.getPeopleNotificationType(a))
.thenReturn(TYPE_IMPORTANT_PERSON)
assertThat(rankingManager.updateRanking(null, listOf(a, b, c), "test"))
.containsExactly(b, c, a)
assertThat(b.bucket).isEqualTo(BUCKET_FOREGROUND_SERVICE)
}
@Test
fun testSort_importantCall() {
whenever(sectionsManager.isFilteringEnabled()).thenReturn(true)
val a = NotificationEntryBuilder()
.setImportance(IMPORTANCE_HIGH)
.setPkg("pkg")
.setOpPkg("pkg")
.setTag("tag")
.setNotification(
Notification.Builder(mContext, "test")
.build())
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
val b = NotificationEntryBuilder()
.setImportance(IMPORTANCE_DEFAULT) // high priority
.setPkg("pkg2")
.setOpPkg("pkg2")
.setTag("tag")
.setNotification(mock(Notification::class.java).also { notif ->
whenever(notif.isForegroundService).thenReturn(true)
whenever(notif.isColorized).thenReturn(true)
})
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
val cN = Notification.Builder(mContext, "test")
.setStyle(Notification.MessagingStyle(""))
.build()
val c = NotificationEntryBuilder()
.setImportance(IMPORTANCE_HIGH)
.setPkg("pkg")
.setOpPkg("pkg")
.setTag("tag")
.setNotification(cN)
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.user)
.setOverrideGroupKey("")
.build()
val dN = Notification.Builder(mContext, "test")
.setStyle(Notification.CallStyle.forOngoingCall(
Person.Builder().setName("caller").build(),
mock(PendingIntent::class.java)))
.build()
val d = NotificationEntryBuilder()
.setImportance(IMPORTANCE_DEFAULT) // high priority
.setPkg("pkg2")
.setOpPkg("pkg2")
.setTag("tag")
.setNotification(dN)
.setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.user)
.setOverrideGroupKey("")
.build()
whenever(personNotificationIdentifier.getPeopleNotificationType(a))
.thenReturn(TYPE_IMPORTANT_PERSON)
assertThat(rankingManager.updateRanking(null, listOf(a, b, c, d), "test"))
.containsExactly(b, d, c, a)
assertThat(d.bucket).isEqualTo(BUCKET_FOREGROUND_SERVICE)
}
internal class TestableNotificationRankingManager(
mediaManager: Lazy<NotificationMediaManager>,
groupManager: NotificationGroupManagerLegacy,
headsUpManager: HeadsUpManager,
filter: NotificationFilter,
logger: NotificationEntryManagerLogger,
sectionsFeatureManager: NotificationSectionsFeatureManager,
peopleNotificationIdentifier: PeopleNotificationIdentifier,
highPriorityProvider: HighPriorityProvider,
keyguardEnvironment: KeyguardEnvironment
) : NotificationRankingManager(
mediaManager,
groupManager,
headsUpManager,
filter,
logger,
sectionsFeatureManager,
peopleNotificationIdentifier,
highPriorityProvider,
keyguardEnvironment
) {
fun applyTestRankingMap(r: RankingMap) {
rankingMap = r
}
}
}

View File

@@ -33,9 +33,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -57,7 +55,6 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -85,8 +82,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
@Mock
AmbientDisplayConfiguration mAmbientDisplayConfiguration;
@Mock
NotificationFilter mNotificationFilter;
@Mock
StatusBarStateController mStatusBarStateController;
@Mock
KeyguardStateController mKeyguardStateController;
@@ -129,22 +124,12 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
mNotifInterruptionStateProvider.mUseHeadsUp = true;
}
/**
* Sets up the state such that any requests to
* {@link NotificationInterruptStateProviderImpl#canAlertCommon(NotificationEntry)} will
* pass as long its provided NotificationEntry fulfills group suppression check.
*/
private void ensureStateForAlertCommon() {
when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
}
/**
* Sets up the state such that any requests to
* {@link NotificationInterruptStateProviderImpl#shouldHeadsUp(NotificationEntry)} will
* pass as long its provided NotificationEntry fulfills importance & DND checks.
*/
private void ensureStateForHeadsUpWhenAwake() throws RemoteException {
ensureStateForAlertCommon();
when(mHeadsUpManager.isSnoozed(any())).thenReturn(false);
when(mStatusBarStateController.isDozing()).thenReturn(false);
@@ -158,21 +143,10 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
* pass as long its provided NotificationEntry fulfills importance & DND checks.
*/
private void ensureStateForHeadsUpWhenDozing() {
ensureStateForAlertCommon();
when(mStatusBarStateController.isDozing()).thenReturn(true);
when(mAmbientDisplayConfiguration.pulseOnNotificationEnabled(anyInt())).thenReturn(true);
}
/**
* Sets up the state such that any requests to
* {@link NotificationInterruptStateProviderImpl#shouldBubbleUp(NotificationEntry)} will
* pass as long its provided NotificationEntry fulfills importance & bubble checks.
*/
private void ensureStateForBubbleUp() {
ensureStateForAlertCommon();
}
@Test
public void testDefaultSuppressorDoesNotSuppress() {
// GIVEN a suppressor without any overrides
@@ -200,27 +174,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
}
@Test
public void testShouldNotHeadsUpAwake_flteredOut() throws RemoteException {
// GIVEN state for "heads up when awake" is true
ensureStateForHeadsUpWhenAwake();
// WHEN this entry should be filtered out
NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
when(mNotificationFilter.shouldFilterOut(entry)).thenReturn(true);
// THEN we shouldn't heads up this entry
assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
}
@Test
public void testDoNotRunFilterOnNewPipeline() {
// WHEN this entry should be filtered out
NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
mNotifInterruptionStateProvider.shouldHeadsUp(entry);
verify(mNotificationFilter, times(0)).shouldFilterOut(eq(entry));
}
@Test
public void testShouldNotHeadsUp_suppressedForGroups() throws RemoteException {
// GIVEN state for "heads up when awake" is true
@@ -654,7 +607,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
*/
@Test
public void testShouldBubbleUp() {
ensureStateForBubbleUp();
assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isTrue();
}
@@ -664,7 +616,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
*/
@Test
public void testShouldBubbleUp_notifInGroupWithOnlySummaryAlerts() {
ensureStateForBubbleUp();
NotificationEntry bubble = createBubble("testgroup", GROUP_ALERT_SUMMARY);
assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(bubble)).isTrue();
}
@@ -674,8 +625,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
*/
@Test
public void shouldNotBubbleUp_notAllowedToBubble() {
ensureStateForBubbleUp();
NotificationEntry entry = createBubble();
modifyRanking(entry)
.setCanBubble(false)
@@ -689,8 +638,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
*/
@Test
public void shouldNotBubbleUp_notABubble() {
ensureStateForBubbleUp();
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
modifyRanking(entry)
.setCanBubble(true)
@@ -704,8 +651,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
*/
@Test
public void shouldNotBubbleUp_invalidMetadata() {
ensureStateForBubbleUp();
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
modifyRanking(entry)
.setCanBubble(true)
@@ -717,8 +662,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
@Test
public void shouldNotBubbleUp_suppressedInterruptions() {
ensureStateForBubbleUp();
// If the notification can't heads up in general, it shouldn't bubble.
mNotifInterruptionStateProvider.addSuppressor(mSuppressInterruptions);
@@ -727,8 +670,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
@Test
public void shouldNotBubbleUp_filteredOut() {
ensureStateForBubbleUp();
// Make canAlertCommon false by saying it's filtered out
when(mKeyguardNotificationVisibilityProvider.shouldHideNotification(any()))
.thenReturn(true);

View File

@@ -26,7 +26,6 @@ import static android.service.notification.NotificationListenerService.Ranking.U
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
@@ -37,7 +36,6 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -77,7 +75,6 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -88,7 +85,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.wmshell.BubblesManager;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -128,7 +124,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
@Mock private AccessibilityManager mAccessibilityManager;
@Mock private HighPriorityProvider mHighPriorityProvider;
@Mock private INotificationManager mINotificationManager;
@Mock private NotificationEntryManager mNotificationEntryManager;
@Mock private LauncherApps mLauncherApps;
@Mock private ShortcutManager mShortcutManager;
@Mock private ChannelEditorDialogController mChannelEditorDialogController;
@@ -160,7 +155,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback,
mShadeController, mock(DumpManager.class));
mShadeController);
mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
mOnSettingsClickListener);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
@@ -446,36 +441,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
eq(mAssistantFeedbackController));
}
@Test
public void testShouldExtendLifetime() {
NotificationGuts guts = new NotificationGuts(mContext);
ExpandableNotificationRow row = spy(createTestNotificationRow());
doReturn(guts).when(row).getGuts();
NotificationEntry entry = row.getEntry();
entry.setRow(row);
mGutsManager.setExposedGuts(guts);
assertTrue(mGutsManager.shouldExtendLifetime(entry));
}
@Test
@Ignore
public void testSetShouldManageLifetime_setShouldManage() {
NotificationEntry entry = createTestNotificationRow().getEntry();
mGutsManager.setShouldManageLifetime(entry, true /* shouldManage */);
assertTrue(entry.getKey().equals(mGutsManager.mKeyToRemoveOnGutsClosed));
}
@Test
public void testSetShouldManageLifetime_setShouldNotManage() {
NotificationEntry entry = createTestNotificationRow().getEntry();
mGutsManager.mKeyToRemoveOnGutsClosed = entry.getKey();
mGutsManager.setShouldManageLifetime(entry, false /* shouldManage */);
assertNull(mGutsManager.mKeyToRemoveOnGutsClosed);
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Utility methods:

View File

@@ -51,7 +51,6 @@ import com.android.internal.logging.MetricsLogger;
import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -62,10 +61,11 @@ import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.icon.IconBuilder;
import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -84,7 +84,6 @@ import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
import com.android.systemui.tests.R;
import com.android.systemui.wmshell.BubblesManager;
import com.android.systemui.wmshell.BubblesTestActivity;
import com.android.wm.shell.bubbles.Bubbles;
import org.mockito.ArgumentCaptor;
@@ -112,8 +111,8 @@ public class NotificationTestHelper {
private final Context mContext;
private final TestableLooper mTestLooper;
private int mId;
private final NotificationGroupManagerLegacy mGroupMembershipManager;
private final NotificationGroupManagerLegacy mGroupExpansionManager;
private final GroupMembershipManager mGroupMembershipManager;
private final GroupExpansionManager mGroupExpansionManager;
private ExpandableNotificationRow mRow;
private HeadsUpManagerPhone mHeadsUpManager;
private final NotifBindPipeline mBindPipeline;
@@ -136,23 +135,18 @@ public class NotificationTestHelper {
dependency.injectMockDependency(NotificationShadeWindowController.class);
dependency.injectMockDependency(MediaOutputDialogFactory.class);
mStatusBarStateController = mock(StatusBarStateController.class);
mGroupMembershipManager = new NotificationGroupManagerLegacy(
mStatusBarStateController,
() -> mock(PeopleNotificationIdentifier.class),
Optional.of((mock(Bubbles.class))),
mock(DumpManager.class));
mGroupExpansionManager = mGroupMembershipManager;
mGroupMembershipManager = mock(GroupMembershipManager.class);
mGroupExpansionManager = mock(GroupExpansionManager.class);
mHeadsUpManager = new HeadsUpManagerPhone(
mContext,
mock(HeadsUpManagerLogger.class),
mStatusBarStateController,
mock(KeyguardBypassController.class),
mock(NotificationGroupManagerLegacy.class),
mock(GroupMembershipManager.class),
mock(VisualStabilityProvider.class),
mock(ConfigurationControllerImpl.class),
new Handler(mTestLooper.getLooper())
);
mGroupMembershipManager.setHeadsUpManager(mHeadsUpManager);
mIconManager = new IconManager(
mock(CommonNotifCollection.class),
mock(LauncherApps.class),
@@ -528,10 +522,6 @@ public class NotificationTestHelper {
mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
inflateAndWait(entry);
// This would be done as part of onAsyncInflationFinished, but we skip large amounts of
// the callback chain, so we need to make up for not adding it to the group manager
// here.
mGroupMembershipManager.onEntryAdded(entry);
return row;
}

View File

@@ -18,24 +18,7 @@ package com.android.systemui.statusbar.notification.stack;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE;
import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP;
import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE;
import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
@@ -50,28 +33,19 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.MediaContainerController;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
import com.android.systemui.statusbar.policy.ConfigurationController;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.List;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -84,15 +58,11 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
@Mock private ConfigurationController mConfigurationController;
@Mock private KeyguardMediaController mKeyguardMediaController;
@Mock private NotificationSectionsFeatureManager mSectionsFeatureManager;
@Mock private NotificationRowComponent mNotificationRowComponent;
@Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
@Mock private NotificationSectionsLogger mLogger;
@Mock private MediaContainerController mMediaContainerController;
@Mock private SectionHeaderController mIncomingHeaderController;
@Mock private SectionHeaderController mPeopleHeaderController;
@Mock private SectionHeaderController mAlertingHeaderController;
@Mock private SectionHeaderController mSilentHeaderController;
@Mock private NotifPipelineFlags mNotifPipelineFlags;
private NotificationSectionsManager mSectionsManager;
@@ -113,22 +83,11 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
}
return count;
});
when(mNotificationRowComponent.getActivatableNotificationViewController())
.thenReturn(mActivatableNotificationViewController);
when(mMediaContainerController.getMediaContainerView())
.thenReturn(mock(MediaContainerView.class));
when(mIncomingHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class));
when(mPeopleHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class));
when(mAlertingHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class));
when(mSilentHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class));
mSectionsManager =
new NotificationSectionsManager(
mStatusBarStateController,
mConfigurationController,
mKeyguardMediaController,
mSectionsFeatureManager,
mLogger,
mNotifPipelineFlags,
mMediaContainerController,
mIncomingHeaderController,
mPeopleHeaderController,
@@ -141,7 +100,6 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
mSectionsManager.initialize(mNssl);
when(mNssl.indexOfChild(any(View.class))).thenReturn(-1);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
}
@Test(expected = IllegalStateException.class)
@@ -149,641 +107,4 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
mSectionsManager.initialize(mNssl);
}
@Test
public void testInsertHeader() {
// GIVEN a stack with HI and LO rows but no section headers
setStackState(
ALERTING,
ALERTING,
ALERTING,
GENTLE);
// WHEN we update the section headers
mSectionsManager.updateSectionBoundaries();
// THEN a LO section header is added
verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 3);
}
@Test
public void testRemoveHeader() {
// GIVEN a stack that originally had a header between the HI and LO sections
setStackState(
ALERTING,
ALERTING,
GENTLE);
mSectionsManager.updateSectionBoundaries();
// WHEN the last LO row is replaced with a HI row
setStackState(
ALERTING,
ALERTING,
GENTLE_HEADER,
ALERTING);
clearInvocations(mNssl);
mSectionsManager.updateSectionBoundaries();
// THEN the LO section header is removed
verify(mNssl).removeView(mSectionsManager.getSilentHeaderView());
}
@Test
public void testDoNothingIfHeaderAlreadyRemoved() {
// GIVEN a stack with only HI rows
setStackState(
ALERTING,
ALERTING,
ALERTING);
// WHEN we update the sections headers
mSectionsManager.updateSectionBoundaries();
// THEN we don't add any section headers
verify(mNssl, never()).addView(eq(mSectionsManager.getSilentHeaderView()), anyInt());
}
@Test
public void testMoveHeaderForward() {
// GIVEN a stack that originally had a header between the HI and LO sections
setStackState(
ALERTING,
ALERTING,
ALERTING,
GENTLE);
mSectionsManager.updateSectionBoundaries();
// WHEN the LO section moves forward
setStackState(
ALERTING,
ALERTING,
GENTLE,
GENTLE_HEADER,
GENTLE);
mSectionsManager.updateSectionBoundaries();
// THEN the LO section header is also moved forward
verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 2);
}
@Test
public void testMoveHeaderBackward() {
// GIVEN a stack that originally had a header between the HI and LO sections
setStackState(
ALERTING,
GENTLE,
GENTLE,
GENTLE);
mSectionsManager.updateSectionBoundaries();
// WHEN the LO section moves backward
setStackState(
ALERTING,
GENTLE_HEADER,
ALERTING,
ALERTING,
GENTLE);
mSectionsManager.updateSectionBoundaries();
// THEN the LO section header is also moved backward (with appropriate index shifting)
verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 3);
}
@Test
public void testHeaderRemovedFromTransientParent() {
// GIVEN a stack where the header is animating away
setStackState(
ALERTING,
GENTLE_HEADER);
mSectionsManager.updateSectionBoundaries();
clearInvocations(mNssl);
SectionHeaderView silentHeaderView = mSectionsManager.getSilentHeaderView();
ViewGroup transientParent = mock(ViewGroup.class);
when(silentHeaderView.getTransientContainer()).thenReturn(transientParent);
// WHEN the LO section reappears
setStackState(
ALERTING,
GENTLE);
mSectionsManager.updateSectionBoundaries();
// THEN the header is first removed from the transient parent before being added to the
// NSSL.
final InOrder inOrder = inOrder(silentHeaderView, mNssl);
inOrder.verify(silentHeaderView).removeFromTransientContainer();
inOrder.verify(mNssl).addView(eq(silentHeaderView), eq(1));
}
@Test
public void testHeaderNotShownOnLockscreen() {
// GIVEN a stack of HI and LO notifs on the lockscreen
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
setStackState(
ALERTING,
ALERTING,
ALERTING,
GENTLE);
// WHEN we update the section headers
mSectionsManager.updateSectionBoundaries();
// Then the section header is not added
verify(mNssl, never()).addView(eq(mSectionsManager.getSilentHeaderView()), anyInt());
}
@Test
public void testHeaderShownWhenEnterLockscreen() {
// GIVEN a stack of HI and LO notifs on the lockscreen
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
setStackState(
ALERTING,
ALERTING,
ALERTING,
GENTLE);
mSectionsManager.updateSectionBoundaries();
// WHEN we unlock
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
mSectionsManager.updateSectionBoundaries();
// Then the section header is added
verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 3);
}
@Test
public void testHeaderHiddenWhenEnterLockscreen() {
// GIVEN a stack of HI and LO notifs on the shade
setStackState(
ALERTING,
GENTLE_HEADER,
GENTLE);
// WHEN we go back to the keyguard
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
mSectionsManager.updateSectionBoundaries();
// Then the section header is removed
verify(mNssl).removeView(mSectionsManager.getSilentHeaderView());
}
@Test
public void testPeopleFiltering_onlyAddSilentHeader() {
enablePeopleFiltering();
setStackState(
PERSON,
ALERTING,
GENTLE);
mSectionsManager.updateSectionBoundaries();
verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 2);
}
@Test
public void testPeopleFiltering_AlertingHunWhilePeopleVisible() {
enablePeopleFiltering();
setupMockStack(
PEOPLE_HEADER,
ALERTING,
PERSON,
ALERTING_HEADER,
GENTLE_HEADER,
GENTLE
);
mSectionsManager.updateSectionBoundaries();
verifyMockStack(
ChildType.HEADS_UP,
ChildType.PERSON,
ChildType.GENTLE_HEADER,
ChildType.GENTLE
);
}
@Test
public void testPeopleFiltering_PersonHunWhileAlertingHunVisible() {
enablePeopleFiltering();
setupMockStack(
PERSON,
INCOMING_HEADER,
ALERTING,
PEOPLE_HEADER,
PERSON
);
mSectionsManager.updateSectionBoundaries();
verifyMockStack(
ChildType.HEADS_UP,
ChildType.HEADS_UP,
ChildType.PERSON
);
}
@Test
public void testPeopleFiltering_PersonHun() {
enablePeopleFiltering();
setupMockStack(
PERSON,
PEOPLE_HEADER,
PERSON
);
mSectionsManager.updateSectionBoundaries();
verifyMockStack(
ChildType.PERSON,
ChildType.PERSON
);
}
@Test
public void testPeopleFiltering_AlertingHunWhilePersonHunning() {
enablePeopleFiltering();
setupMockStack(
ALERTING,
PERSON
);
mSectionsManager.updateSectionBoundaries();
verifyMockStack(
ChildType.HEADS_UP,
ChildType.PERSON
);
}
@Test
public void testPeopleFiltering_Fsn() {
enablePeopleFiltering();
setupMockStack(
INCOMING_HEADER,
ALERTING,
PEOPLE_HEADER,
FSN,
PERSON,
ALERTING,
GENTLE
);
mSectionsManager.updateSectionBoundaries();
verifyMockStack(
ChildType.HEADS_UP,
ChildType.FSN,
ChildType.PERSON,
ChildType.ALERTING,
ChildType.GENTLE_HEADER,
ChildType.GENTLE
);
}
@Test
public void testMediaControls_AddWhenEnterKeyguard() {
enableMediaControls();
// GIVEN a stack that doesn't include media controls
setStackState(ALERTING, GENTLE_HEADER, GENTLE);
// WHEN we go back to the keyguard
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
mSectionsManager.updateSectionBoundaries();
// Then the media controls are added
verify(mNssl).addView(mSectionsManager.getMediaControlsView(), 0);
}
@Test
public void testMediaControls_AddWhenEnterKeyguardWithHeadsUp() {
enableMediaControls();
// GIVEN a stack that doesn't include media
setupMockStack(
ALERTING,
ALERTING,
GENTLE_HEADER,
GENTLE);
// WHEN we go back to the keyguard
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
mSectionsManager.updateSectionBoundaries();
verifyMockStack(
ChildType.MEDIA_CONTROLS,
ChildType.ALERTING,
ChildType.ALERTING,
ChildType.GENTLE);
}
@Test
public void testRemoveNonSilentHeader() {
enablePeopleFiltering();
enableMediaControls();
setupMockStack(
MEDIA_CONTROLS,
INCOMING_HEADER,
PERSON,
ALERTING,
PEOPLE_HEADER,
ALERTING_HEADER,
ALERTING,
ALERTING,
GENTLE_HEADER,
GENTLE,
GENTLE
);
mSectionsManager.updateSectionBoundaries();
verifyMockStack(
ChildType.MEDIA_CONTROLS,
ChildType.PERSON,
ChildType.ALERTING,
ChildType.ALERTING,
ChildType.ALERTING,
ChildType.GENTLE_HEADER,
ChildType.GENTLE,
ChildType.GENTLE
);
}
@Test
public void testExpandIncomingSection() {
enablePeopleFiltering();
setupMockStack(
INCOMING_HEADER,
PERSON,
ALERTING,
PEOPLE_HEADER,
ALERTING,
PERSON,
ALERTING_HEADER,
ALERTING
);
mSectionsManager.updateSectionBoundaries();
verifyMockStack(
ChildType.HEADS_UP,
ChildType.HEADS_UP,
ChildType.HEADS_UP,
ChildType.PERSON,
ChildType.ALERTING
);
}
@Test
public void testIgnoreGoneView() {
enablePeopleFiltering();
setupMockStack(
PERSON.gone(),
ALERTING,
GENTLE
);
mSectionsManager.updateSectionBoundaries();
verifyMockStack(
ChildType.PERSON,
ChildType.ALERTING,
ChildType.GENTLE_HEADER,
ChildType.GENTLE
);
}
private void enablePeopleFiltering() {
when(mSectionsFeatureManager.isFilteringEnabled()).thenReturn(true);
}
private void enableMediaControls() {
when(mSectionsFeatureManager.isMediaControlsEnabled()).thenReturn(true);
}
private enum ChildType {
INCOMING_HEADER, MEDIA_CONTROLS, PEOPLE_HEADER, ALERTING_HEADER, GENTLE_HEADER, HEADS_UP,
FSN, PERSON, ALERTING, GENTLE, OTHER
}
private void setStackState(StackEntry... children) {
when(mNssl.getChildCount()).thenReturn(children.length);
for (int i = 0; i < children.length; i++) {
View child;
StackEntry entry = children[i];
switch (entry.mChildType) {
case INCOMING_HEADER:
child = mSectionsManager.getIncomingHeaderView();
break;
case MEDIA_CONTROLS:
child = mSectionsManager.getMediaControlsView();
break;
case PEOPLE_HEADER:
child = mSectionsManager.getPeopleHeaderView();
break;
case ALERTING_HEADER:
child = mSectionsManager.getAlertingHeaderView();
break;
case GENTLE_HEADER:
child = mSectionsManager.getSilentHeaderView();
break;
case FSN:
child = mockNotification(BUCKET_FOREGROUND_SERVICE, entry.mIsGone);
break;
case PERSON:
child = mockNotification(BUCKET_PEOPLE, entry.mIsGone);
break;
case ALERTING:
child = mockNotification(BUCKET_ALERTING, entry.mIsGone);
break;
case GENTLE:
child = mockNotification(BUCKET_SILENT, entry.mIsGone);
break;
case OTHER:
child = mock(View.class);
when(child.getVisibility()).thenReturn(View.VISIBLE);
when(child.getParent()).thenReturn(mNssl);
break;
default:
throw new RuntimeException("Unknown ChildType: " + children[i]);
}
when(mNssl.getChildAt(i)).thenReturn(child);
when(mNssl.indexOfChild(child)).thenReturn(i);
}
}
private View mockNotification(@PriorityBucket int bucket, boolean isGone) {
ExpandableNotificationRow notifRow =
mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
when(notifRow.getParent()).thenReturn(mNssl);
NotificationEntry mockEntry = mock(NotificationEntry.class);
when(notifRow.getEntry()).thenReturn(mockEntry);
int[] bucketRef = new int[] { bucket };
when(mockEntry.getBucket()).thenAnswer(invocation -> bucketRef[0]);
doAnswer(invocation -> {
bucketRef[0] = invocation.getArgument(0);
return null;
}).when(mockEntry).setBucket(anyInt());
when(notifRow.getVisibility()).thenReturn(isGone ? View.GONE : View.VISIBLE);
return notifRow;
}
private void verifyMockStack(ChildType... expected) {
final List<ChildType> actual = new ArrayList<>();
int childCount = mNssl.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = mNssl.getChildAt(i);
if (child == mSectionsManager.getIncomingHeaderView()) {
actual.add(ChildType.INCOMING_HEADER);
continue;
}
if (child == mSectionsManager.getMediaControlsView()) {
actual.add(ChildType.MEDIA_CONTROLS);
continue;
}
if (child == mSectionsManager.getPeopleHeaderView()) {
actual.add(ChildType.PEOPLE_HEADER);
continue;
}
if (child == mSectionsManager.getAlertingHeaderView()) {
actual.add(ChildType.ALERTING_HEADER);
continue;
}
if (child == mSectionsManager.getSilentHeaderView()) {
actual.add(ChildType.GENTLE_HEADER);
continue;
}
if (child instanceof ExpandableNotificationRow) {
switch (((ExpandableNotificationRow) child).getEntry().getBucket()) {
case BUCKET_HEADS_UP:
actual.add(ChildType.HEADS_UP);
break;
case BUCKET_FOREGROUND_SERVICE:
actual.add(ChildType.FSN);
break;
case BUCKET_PEOPLE:
actual.add(ChildType.PERSON);
break;
case BUCKET_ALERTING:
actual.add(ChildType.ALERTING);
break;
case BUCKET_SILENT:
actual.add(ChildType.GENTLE);
break;
default:
actual.add(ChildType.OTHER);
break;
}
continue;
}
actual.add(ChildType.OTHER);
}
assertThat(actual).containsExactly((Object[]) expected).inOrder();
}
private void setupMockStack(StackEntry... entries) {
final List<View> children = new ArrayList<>();
when(mNssl.getChildCount()).thenAnswer(invocation -> children.size());
when(mNssl.getChildAt(anyInt()))
.thenAnswer(invocation -> {
Integer index = invocation.getArgument(0);
if (index == null || index < 0 || index >= children.size()) {
return null;
}
return children.get(index);
});
when(mNssl.indexOfChild(any()))
.thenAnswer(invocation -> children.indexOf(invocation.getArgument(0)));
doAnswer(invocation -> {
View child = invocation.getArgument(0);
int index = invocation.getArgument(1);
children.add(index, child);
return null;
}).when(mNssl).addView(any(), anyInt());
doAnswer(invocation -> {
View child = invocation.getArgument(0);
children.remove(child);
return null;
}).when(mNssl).removeView(any());
doAnswer(invocation -> {
View child = invocation.getArgument(0);
int newIndex = invocation.getArgument(1);
children.remove(child);
children.add(newIndex, child);
return null;
}).when(mNssl).changeViewPosition(any(), anyInt());
for (StackEntry entry : entries) {
View child;
switch (entry.mChildType) {
case INCOMING_HEADER:
child = mSectionsManager.getIncomingHeaderView();
break;
case MEDIA_CONTROLS:
child = mSectionsManager.getMediaControlsView();
break;
case PEOPLE_HEADER:
child = mSectionsManager.getPeopleHeaderView();
break;
case ALERTING_HEADER:
child = mSectionsManager.getAlertingHeaderView();
break;
case GENTLE_HEADER:
child = mSectionsManager.getSilentHeaderView();
break;
case FSN:
child = mockNotification(BUCKET_FOREGROUND_SERVICE, entry.mIsGone);
break;
case PERSON:
child = mockNotification(BUCKET_PEOPLE, entry.mIsGone);
break;
case ALERTING:
child = mockNotification(BUCKET_ALERTING, entry.mIsGone);
break;
case GENTLE:
child = mockNotification(BUCKET_SILENT, entry.mIsGone);
break;
case OTHER:
child = mock(View.class);
when(child.getVisibility()).thenReturn(View.VISIBLE);
when(child.getParent()).thenReturn(mNssl);
break;
default:
throw new RuntimeException("Unknown ChildType: " + entry.mChildType);
}
children.add(child);
}
}
private static final StackEntry INCOMING_HEADER = new StackEntry(ChildType.INCOMING_HEADER);
private static final StackEntry MEDIA_CONTROLS = new StackEntry(ChildType.MEDIA_CONTROLS);
private static final StackEntry PEOPLE_HEADER = new StackEntry(ChildType.PEOPLE_HEADER);
private static final StackEntry ALERTING_HEADER = new StackEntry(ChildType.ALERTING_HEADER);
private static final StackEntry GENTLE_HEADER = new StackEntry(ChildType.GENTLE_HEADER);
private static final StackEntry FSN = new StackEntry(ChildType.FSN);
private static final StackEntry PERSON = new StackEntry(ChildType.PERSON);
private static final StackEntry ALERTING = new StackEntry(ChildType.ALERTING);
private static final StackEntry GENTLE = new StackEntry(ChildType.GENTLE);
private static class StackEntry {
final ChildType mChildType;
final boolean mIsGone;
StackEntry(ChildType childType) {
this(childType, false);
}
StackEntry(ChildType childType, boolean isGone) {
mChildType = childType;
mIsGone = isGone;
}
public StackEntry gone() {
return new StackEntry(mChildType, true);
}
}
}

View File

@@ -67,6 +67,7 @@ import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -121,7 +122,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private NotificationSwipeHelper mNotificationSwipeHelper;
@Mock private CentralSurfaces mCentralSurfaces;
@Mock private ScrimController mScrimController;
@Mock private NotificationGroupManagerLegacy mLegacyGroupManager;
@Mock private NotificationGroupManagerLegacy mGroupManagerLegacy;
@Mock private GroupExpansionManager mGroupExpansionManager;
@Mock private SectionHeaderController mSilentHeaderController;
@Mock private NotifPipelineFlags mNotifPipelineFlags;
@Mock private NotifPipeline mNotifPipeline;
@@ -174,8 +176,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mNotificationSwipeHelperBuilder,
mCentralSurfaces,
mScrimController,
mLegacyGroupManager,
mLegacyGroupManager,
mGroupManagerLegacy,
mGroupExpansionManager,
mSilentHeaderController,
mNotifPipeline,
mNotifCollection,
@@ -184,7 +186,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mShadeTransitionController,
mUiEventLogger,
mRemoteInputManager,
mVisualStabilityManager,
mShadeController,
mJankMonitor,
mStackLogger,

View File

@@ -66,7 +66,6 @@ import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -101,8 +100,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Rule public MockitoRule mockito = MockitoJUnit.rule();
@Mock private CentralSurfaces mCentralSurfaces;
@Mock private SysuiStatusBarStateController mBarState;
@Mock private NotificationGroupManagerLegacy mGroupMembershipManger;
@Mock private NotificationGroupManagerLegacy mGroupExpansionManager;
@Mock private GroupMembershipManager mGroupMembershipManger;
@Mock private GroupExpansionManager mGroupExpansionManager;
@Mock private DumpManager mDumpManager;
@Mock private ExpandHelper mExpandHelper;
@Mock private EmptyShadeView mEmptyShadeView;

View File

@@ -418,7 +418,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
wakefulnessLifecycle,
mStatusBarStateController,
Optional.of(mBubbles),
mVisualStabilityManager,
mDeviceProvisionedController,
mNavigationBarController,
mAccessibilityFloatingMenuController,

View File

@@ -26,7 +26,6 @@ import android.content.Context;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
import androidx.test.filters.SmallTest;
@@ -36,8 +35,8 @@ import com.android.systemui.statusbar.AlertingNotificationManagerTest;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
@@ -61,19 +60,18 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest {
private HeadsUpManagerPhone mHeadsUpManager;
@Mock private HeadsUpManagerLogger mHeadsUpManagerLogger;
@Mock private NotificationGroupManagerLegacy mGroupManager;
@Mock private View mNotificationShadeWindowView;
@Mock private GroupMembershipManager mGroupManager;
@Mock private VisualStabilityProvider mVSProvider;
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private KeyguardBypassController mBypassController;
@Mock private ConfigurationControllerImpl mConfigurationController;
private boolean mLivesPastNormalTime;
private final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
TestableHeadsUpManagerPhone(
Context context,
HeadsUpManagerLogger headsUpManagerLogger,
NotificationGroupManagerLegacy groupManager,
GroupMembershipManager groupManager,
VisualStabilityProvider visualStabilityProvider,
StatusBarStateController statusBarStateController,
KeyguardBypassController keyguardBypassController,

View File

@@ -1,490 +0,0 @@
/*
* Copyright (C) 2018 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.phone;
import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
import android.os.Handler;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback;
import com.android.systemui.statusbar.notification.row.RowContentBindParams;
import com.android.systemui.statusbar.notification.row.RowContentBindStage;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
import com.android.wm.shell.bubbles.Bubbles;
import org.junit.Before;
import org.junit.Rule;
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.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.HashMap;
import java.util.Optional;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
@Rule public MockitoRule rule = MockitoJUnit.rule();
private NotificationGroupAlertTransferHelper mGroupAlertTransferHelper;
private NotificationGroupManagerLegacy mGroupManager;
private HeadsUpManager mHeadsUpManager;
@Mock private NotificationEntryManager mNotificationEntryManager;
@Mock private RowContentBindStage mBindStage;
@Mock PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@Mock StatusBarStateController mStatusBarStateController;
@Captor private ArgumentCaptor<NotificationEntryListener> mListenerCaptor;
private NotificationEntryListener mNotificationEntryListener;
private final HashMap<String, NotificationEntry> mPendingEntries = new HashMap<>();
private final NotificationGroupTestHelper mGroupTestHelper =
new NotificationGroupTestHelper(mContext);
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mHeadsUpManager = new HeadsUpManager(mContext, mock(HeadsUpManagerLogger.class),
mock(Handler.class)) {};
when(mNotificationEntryManager.getPendingNotificationsIterator())
.thenReturn(mPendingEntries.values());
mGroupManager = new NotificationGroupManagerLegacy(
mStatusBarStateController,
() -> mPeopleNotificationIdentifier,
Optional.of(mock(Bubbles.class)),
mock(DumpManager.class));
mDependency.injectTestDependency(NotificationGroupManagerLegacy.class, mGroupManager);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(
mBindStage, mStatusBarStateController, mGroupManager);
mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager);
mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager);
verify(mNotificationEntryManager).addNotificationEntryListener(mListenerCaptor.capture());
mNotificationEntryListener = mListenerCaptor.getValue();
mHeadsUpManager.addListener(mGroupAlertTransferHelper);
}
private void mockHasHeadsUpContentView(NotificationEntry entry,
boolean hasHeadsUpContentView) {
RowContentBindParams params = new RowContentBindParams();
if (hasHeadsUpContentView) {
params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
}
when(mBindStage.getStageParams(eq(entry))).thenReturn(params);
}
private void mockHasHeadsUpContentView(NotificationEntry entry) {
mockHasHeadsUpContentView(entry, true);
}
private void mockIsPriority(NotificationEntry priorityEntry) {
when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
.thenReturn(PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON);
}
@Test
public void testSuppressedSummaryHeadsUpTransfersToChild() {
NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mHeadsUpManager.showNotification(summaryEntry);
NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
mockHasHeadsUpContentView(childEntry);
// Summary will be suppressed because there is only one child.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// A suppressed summary should transfer its alert state to the child.
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
assertTrue(mHeadsUpManager.isAlerting(childEntry.getKey()));
}
@Test
public void testSuppressedSummaryHeadsUpTransfersToChildButBackAgain() {
NotificationEntry summaryEntry =
mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationEntry childEntry =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationEntry childEntry2 =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
mHeadsUpManager.showNotification(summaryEntry);
// Trigger a transfer of alert state from summary to child.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// Add second child notification so that summary is no longer suppressed.
mPendingEntries.put(childEntry2.getKey(), childEntry2);
mNotificationEntryListener.onPendingEntryAdded(childEntry2);
mGroupManager.onEntryAdded(childEntry2);
// The alert state should transfer back to the summary as there is now more than one
// child and the summary should no longer be suppressed.
assertTrue(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
}
@Test
public void testSuppressedSummaryHeadsUpDoesntTransferBackOnDozingChanged() {
NotificationEntry summaryEntry =
mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationEntry childEntry =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationEntry childEntry2 =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
mHeadsUpManager.showNotification(summaryEntry);
// Trigger a transfer of alert state from summary to child.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// Set dozing to true.
mGroupAlertTransferHelper.onDozingChanged(true);
// Add second child notification so that summary is no longer suppressed.
mPendingEntries.put(childEntry2.getKey(), childEntry2);
mNotificationEntryListener.onPendingEntryAdded(childEntry2);
mGroupManager.onEntryAdded(childEntry2);
// Dozing changed so no reason to re-alert summary.
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
}
@Test
public void testSuppressedSummaryHeadsUpTransferDoesNotAlertChildIfUninflated() {
NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mHeadsUpManager.showNotification(summaryEntry);
NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
mockHasHeadsUpContentView(childEntry, false);
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// Alert is immediately removed from summary, but we do not show child yet either as its
// content is not inflated.
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
assertTrue(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
}
@Test
public void testSuppressedSummaryHeadsUpTransferAlertsChildOnInflation() {
NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mHeadsUpManager.showNotification(summaryEntry);
NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
mockHasHeadsUpContentView(childEntry, false);
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// Child entry finishes its inflation.
ArgumentCaptor<BindCallback> callbackCaptor = ArgumentCaptor.forClass(BindCallback.class);
verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture());
callbackCaptor.getValue().onBindFinished(childEntry);
// Alert is immediately removed from summary, and we show child as its content is inflated.
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
assertTrue(mHeadsUpManager.isAlerting(childEntry.getKey()));
}
@Test
public void testSuppressedSummaryHeadsUpTransferBackAbortsChildInflation() {
NotificationEntry summaryEntry =
mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationEntry childEntry =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
RowContentBindParams params = new RowContentBindParams();
when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
NotificationEntry childEntry2 =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
mHeadsUpManager.showNotification(summaryEntry);
// Trigger a transfer of alert state from summary to child.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// Add second child notification so that summary is no longer suppressed.
mPendingEntries.put(childEntry2.getKey(), childEntry2);
mNotificationEntryListener.onPendingEntryAdded(childEntry2);
mGroupManager.onEntryAdded(childEntry2);
// Child entry finishes its inflation.
ArgumentCaptor<BindCallback> callbackCaptor = ArgumentCaptor.forClass(BindCallback.class);
verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture());
callbackCaptor.getValue().onBindFinished(childEntry);
assertTrue((params.getContentViews() & FLAG_CONTENT_VIEW_HEADS_UP) == 0);
assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
}
@Test
public void testCleanUpPendingAlertInfo() {
NotificationEntry summaryEntry =
mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationEntry childEntry =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
mockHasHeadsUpContentView(childEntry, false);
mHeadsUpManager.showNotification(summaryEntry);
// Trigger a transfer of alert state from summary to child.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mNotificationEntryListener.onEntryRemoved(
childEntry, null, false, UNDEFINED_DISMISS_REASON);
assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
}
@Test
public void testUpdateGroupChangeDoesNotTransfer() {
NotificationEntry summaryEntry =
mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationEntry childEntry =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
mockHasHeadsUpContentView(childEntry, false);
mHeadsUpManager.showNotification(summaryEntry);
// Trigger a transfer of alert state from summary to child.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// Notify that entry changed groups.
StatusBarNotification oldNotification = childEntry.getSbn();
StatusBarNotification newSbn = spy(childEntry.getSbn().clone());
doReturn("other_group").when(newSbn).getGroupKey();
childEntry.setSbn(newSbn);
mGroupManager.onEntryUpdated(childEntry, oldNotification);
assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
}
@Test
public void testUpdateChildToSummaryDoesNotTransfer() {
final String tag = "fooTag";
NotificationEntry summaryEntry =
mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
NotificationEntry childEntry =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY, 47, tag);
mockHasHeadsUpContentView(childEntry, false);
mHeadsUpManager.showNotification(summaryEntry);
// Trigger a transfer of alert state from summary to child.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
// Update that child to a summary.
StatusBarNotification oldNotification = childEntry.getSbn();
childEntry.setSbn(
mGroupTestHelper.createSummaryNotification(
Notification.GROUP_ALERT_SUMMARY, 47, tag).getSbn());
mGroupManager.onEntryUpdated(childEntry, oldNotification);
assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
}
@Test
public void testOverriddenSummaryHeadsUpTransfersToPriority() {
// Creation order is oldest to newest, meaning the priority will be deemed newest
int groupAlert = Notification.GROUP_ALERT_SUMMARY;
NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
NotificationEntry childEntry = mGroupTestHelper.createChildNotification(groupAlert);
NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
mockIsPriority(priorityEntry);
// summary gets heads up
mHeadsUpManager.showNotification(summaryEntry);
mockHasHeadsUpContentView(summaryEntry);
mockHasHeadsUpContentView(priorityEntry);
mockHasHeadsUpContentView(childEntry);
// Summary will have an alertOverride.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(priorityEntry);
mGroupManager.onEntryAdded(childEntry);
// An overridden summary should transfer its alert state to the priority.
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
assertTrue(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
}
@Test
public void testOverriddenSummaryHeadsUpTransferDoesNotAlertPriorityIfUninflated() {
// Creation order is oldest to newest, meaning the priority will be deemed newest
int groupAlert = Notification.GROUP_ALERT_SUMMARY;
NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
NotificationEntry childEntry = mGroupTestHelper.createChildNotification(groupAlert);
NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
mockIsPriority(priorityEntry);
// summary gets heads up
mHeadsUpManager.showNotification(summaryEntry);
mockHasHeadsUpContentView(summaryEntry);
mockHasHeadsUpContentView(priorityEntry, false);
mockHasHeadsUpContentView(childEntry);
// Summary will have an alertOverride.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(priorityEntry);
mGroupManager.onEntryAdded(childEntry);
// Alert is immediately removed from summary, but we do not show priority yet either as its
// content is not inflated.
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
assertFalse(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
assertTrue(mGroupAlertTransferHelper.isAlertTransferPending(priorityEntry));
}
@Test
public void testOverriddenSummaryHeadsUpTransfersToPriorityButBackAgain() {
// Creation order is oldest to newest, meaning the child2 will ultimately be deemed newest
int groupAlert = Notification.GROUP_ALERT_SUMMARY;
NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
NotificationEntry childEntry = mGroupTestHelper.createChildNotification(groupAlert);
NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
NotificationEntry childEntry2 = mGroupTestHelper.createChildNotification(groupAlert);
mockIsPriority(priorityEntry);
// summary gets heads up
mHeadsUpManager.showNotification(summaryEntry);
mockHasHeadsUpContentView(summaryEntry);
mockHasHeadsUpContentView(priorityEntry);
mockHasHeadsUpContentView(childEntry);
mockHasHeadsUpContentView(childEntry2);
// Summary will have an alertOverride.
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(priorityEntry);
mGroupManager.onEntryAdded(childEntry);
// An overridden summary should transfer its alert state to the priority.
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
assertTrue(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
mGroupManager.onEntryAdded(childEntry2);
// An overridden summary should transfer its alert state to the priority.
assertTrue(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
assertFalse(mHeadsUpManager.isAlerting(childEntry2.getKey()));
assertFalse(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
}
@Test
public void testOverriddenSuppressedSummaryHeadsUpTransfersToChildThenToPriority() {
// Creation order is oldest to newest, meaning the priority will ultimately be deemed newest
int groupAlert = Notification.GROUP_ALERT_SUMMARY;
NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
NotificationEntry childEntry = mGroupTestHelper.createChildNotification(groupAlert);
NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
mockIsPriority(priorityEntry);
// summary gets heads up
mHeadsUpManager.showNotification(summaryEntry);
mockHasHeadsUpContentView(summaryEntry);
mockHasHeadsUpContentView(priorityEntry);
mockHasHeadsUpContentView(childEntry);
// Summary will be suppressed, and the child will receive the alert
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
assertTrue(mHeadsUpManager.isAlerting(childEntry.getKey()));
// Alert should be transferred "back" from the child to the priority
mGroupManager.onEntryAdded(priorityEntry);
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
assertTrue(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
}
@Test
public void testOverriddenSuppressedSummaryHeadsUpTransfersToPriorityThenToChild() {
// Creation order is oldest to newest, meaning the child will ultimately be deemed newest
int groupAlert = Notification.GROUP_ALERT_SUMMARY;
NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
NotificationEntry childEntry = mGroupTestHelper.createChildNotification(groupAlert);
mockIsPriority(priorityEntry);
// summary gets heads up
mHeadsUpManager.showNotification(summaryEntry);
mockHasHeadsUpContentView(summaryEntry);
mockHasHeadsUpContentView(priorityEntry);
mockHasHeadsUpContentView(childEntry);
// Summary will have alert override of the priority
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(priorityEntry);
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
assertTrue(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
// Alert should be transferred "back" from the priority to the child (which is newer)
mGroupManager.onEntryAdded(childEntry);
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
assertTrue(mHeadsUpManager.isAlerting(childEntry.getKey()));
assertFalse(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
}
}

View File

@@ -1,406 +0,0 @@
/*
* Copyright (C) 2018 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.phone;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.Notification;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Log;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.NotificationGroup;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.wm.shell.bubbles.Bubbles;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.Optional;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class NotificationGroupManagerLegacyTest extends SysuiTestCase {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
private NotificationGroupManagerLegacy mGroupManager;
private final NotificationGroupTestHelper mGroupTestHelper =
new NotificationGroupTestHelper(mContext);
@Mock
PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@Mock
HeadsUpManager mHeadsUpManager;
@Before
public void setup() {
mDependency.injectMockDependency(Bubbles.class);
initializeGroupManager();
}
private void initializeGroupManager() {
mGroupManager = new NotificationGroupManagerLegacy(
mock(StatusBarStateController.class),
() -> mPeopleNotificationIdentifier,
Optional.of(mock(Bubbles.class)),
mock(DumpManager.class));
mGroupManager.setHeadsUpManager(mHeadsUpManager);
}
@Test
public void testIsOnlyChildInGroup() {
NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
assertTrue(mGroupManager.isOnlyChildInGroup(childEntry));
}
@Test
public void testIsChildInGroupWithSummary() {
NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
assertTrue(mGroupManager.isChildInGroup(childEntry));
}
@Test
public void testIsSummaryOfGroupWithChildren() {
NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
assertTrue(mGroupManager.isGroupSummary(summaryEntry));
assertEquals(summaryEntry, mGroupManager.getGroupSummary(childEntry));
}
@Test
public void testRemoveChildFromGroupWithSummary() {
NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
mGroupManager.onEntryRemoved(childEntry);
assertFalse(mGroupManager.isChildInGroup(childEntry));
}
@Test
public void testRemoveSummaryFromGroupWithSummary() {
NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
mGroupManager.onEntryRemoved(summaryEntry);
assertNull(mGroupManager.getGroupSummary(childEntry));
assertFalse(mGroupManager.isGroupSummary(summaryEntry));
}
@Test
public void testHeadsUpEntryIsIsolated() {
NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
when(mHeadsUpManager.isAlerting(childEntry.getKey())).thenReturn(true);
mGroupManager.onHeadsUpStateChanged(childEntry, true);
// Child entries that are heads upped should be considered separate groups visually even if
// they are the same group logically
assertEquals(childEntry, mGroupManager.getGroupSummary(childEntry));
assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(childEntry));
}
@Test
public void testAlertOverrideWithSiblings_0() {
helpTestAlertOverrideWithSiblings(0);
}
@Test
public void testAlertOverrideWithSiblings_1() {
helpTestAlertOverrideWithSiblings(1);
}
@Test
public void testAlertOverrideWithSiblings_2() {
helpTestAlertOverrideWithSiblings(2);
}
@Test
public void testAlertOverrideWithSiblings_3() {
helpTestAlertOverrideWithSiblings(3);
}
@Test
public void testAlertOverrideWithSiblings_9() {
helpTestAlertOverrideWithSiblings(9);
}
/**
* Helper for testing various sibling counts
*/
private void helpTestAlertOverrideWithSiblings(int numSiblings) {
helpTestAlertOverride(
/* numSiblings */ numSiblings,
/* summaryGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
/* priorityGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
/* siblingGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
/* expectAlertOverride */ true);
}
@Test
public void testAlertOverrideWithParentAlertAll() {
// tests that summary can have GROUP_ALERT_ALL and this still works
helpTestAlertOverride(
/* numSiblings */ 1,
/* summaryGroupAlert */ Notification.GROUP_ALERT_ALL,
/* priorityGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
/* siblingGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
/* expectAlertOverride */ true);
}
@Test
public void testAlertOverrideWithParentAlertChild() {
// Tests that if the summary alerts CHILDREN, there's no alertOverride
helpTestAlertOverride(
/* numSiblings */ 1,
/* summaryGroupAlert */ Notification.GROUP_ALERT_CHILDREN,
/* priorityGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
/* siblingGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
/* expectAlertOverride */ false);
}
@Test
public void testAlertOverrideWithChildrenAlertAll() {
// Tests that if the children alert ALL, there's no alertOverride
helpTestAlertOverride(
/* numSiblings */ 1,
/* summaryGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
/* priorityGroupAlert */ Notification.GROUP_ALERT_ALL,
/* siblingGroupAlert */ Notification.GROUP_ALERT_ALL,
/* expectAlertOverride */ false);
}
/**
* This tests, for a group with a priority entry and the given number of siblings, that:
* 1) the priority entry is identified as the alertOverride for the group
* 2) the onAlertOverrideChanged method is called at that time
* 3) when the priority entry is removed, these are reversed
*/
private void helpTestAlertOverride(int numSiblings,
@Notification.GroupAlertBehavior int summaryGroupAlert,
@Notification.GroupAlertBehavior int priorityGroupAlert,
@Notification.GroupAlertBehavior int siblingGroupAlert,
boolean expectAlertOverride) {
long when = 10000;
// Create entries in an order so that the priority entry can be deemed the newest child.
NotificationEntry[] siblings = new NotificationEntry[numSiblings];
for (int i = 0; i < numSiblings; i++) {
siblings[i] = mGroupTestHelper
.createChildNotification(siblingGroupAlert, i, "sibling", ++when);
}
NotificationEntry priorityEntry =
mGroupTestHelper.createChildNotification(priorityGroupAlert, 0, "priority", ++when);
NotificationEntry summaryEntry =
mGroupTestHelper.createSummaryNotification(summaryGroupAlert, 0, "summary", ++when);
// The priority entry is an important conversation.
when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
.thenReturn(PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON);
// Register a listener so we can verify that the event is sent.
OnGroupChangeListener groupChangeListener = mock(OnGroupChangeListener.class);
mGroupManager.registerGroupChangeListener(groupChangeListener);
// Add all the entries. The order here shouldn't matter.
mGroupManager.onEntryAdded(summaryEntry);
for (int i = 0; i < numSiblings; i++) {
mGroupManager.onEntryAdded(siblings[i]);
}
mGroupManager.onEntryAdded(priorityEntry);
if (!expectAlertOverride) {
// Test expectation is that there will NOT be an alert, so verify that!
NotificationGroup summaryGroup =
mGroupManager.getGroupForSummary(summaryEntry.getSbn());
assertNull(summaryGroup.alertOverride);
return;
}
int max2Siblings = Math.min(2, numSiblings);
// Verify that the summary group has the priority child as its alertOverride
NotificationGroup summaryGroup = mGroupManager.getGroupForSummary(summaryEntry.getSbn());
assertEquals(priorityEntry, summaryGroup.alertOverride);
verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, null, priorityEntry);
verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, true);
if (numSiblings > 1) {
verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, false);
}
verify(groupChangeListener).onGroupCreated(any(), eq(priorityEntry.getKey()));
verify(groupChangeListener).onGroupCreated(any(), eq(summaryEntry.getSbn().getGroupKey()));
verify(groupChangeListener, times(max2Siblings + 1)).onGroupsChanged();
verifyNoMoreInteractions(groupChangeListener);
// Verify that only the priority notification is isolated from the group
assertEquals(priorityEntry, mGroupManager.getGroupSummary(priorityEntry));
assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(priorityEntry));
// Verify that the siblings are NOT isolated from the group
for (int i = 0; i < numSiblings; i++) {
assertEquals(summaryEntry, mGroupManager.getGroupSummary(siblings[i]));
assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(siblings[i]));
}
// Remove the priority notification to validate that it is removed as the alertOverride
mGroupManager.onEntryRemoved(priorityEntry);
// verify that the alertOverride is removed when the priority notification is
assertNull(summaryGroup.alertOverride);
verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, priorityEntry, null);
verify(groupChangeListener).onGroupRemoved(any(), eq(priorityEntry.getKey()));
verify(groupChangeListener, times(max2Siblings + 2)).onGroupsChanged();
if (numSiblings == 0) {
verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, false);
}
verifyNoMoreInteractions(groupChangeListener);
}
@Test
public void testAlertOverrideWhenUpdatingSummaryAtEnd() {
long when = 10000;
int numSiblings = 2;
int groupAlert = Notification.GROUP_ALERT_SUMMARY;
// Create entries in an order so that the priority entry can be deemed the newest child.
NotificationEntry[] siblings = new NotificationEntry[numSiblings];
for (int i = 0; i < numSiblings; i++) {
siblings[i] =
mGroupTestHelper.createChildNotification(groupAlert, i, "sibling", ++when);
}
NotificationEntry priorityEntry =
mGroupTestHelper.createChildNotification(groupAlert, 0, "priority", ++when);
NotificationEntry summaryEntry =
mGroupTestHelper.createSummaryNotification(groupAlert, 0, "summary", ++when);
// The priority entry is an important conversation.
when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
.thenReturn(PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON);
// Register a listener so we can verify that the event is sent.
OnGroupChangeListener groupChangeListener = mock(OnGroupChangeListener.class);
mGroupManager.registerGroupChangeListener(groupChangeListener);
// Add all the entries. The order here shouldn't matter.
mGroupManager.onEntryAdded(summaryEntry);
for (int i = 0; i < numSiblings; i++) {
mGroupManager.onEntryAdded(siblings[i]);
}
mGroupManager.onEntryAdded(priorityEntry);
int max2Siblings = Math.min(2, numSiblings);
// Verify that the summary group has the priority child as its alertOverride
NotificationGroup summaryGroup = mGroupManager.getGroupForSummary(summaryEntry.getSbn());
assertEquals(priorityEntry, summaryGroup.alertOverride);
verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, null, priorityEntry);
verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, true);
if (numSiblings > 1) {
verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, false);
}
verify(groupChangeListener).onGroupCreated(any(), eq(priorityEntry.getKey()));
verify(groupChangeListener).onGroupCreated(any(), eq(summaryEntry.getSbn().getGroupKey()));
verify(groupChangeListener, times(max2Siblings + 1)).onGroupsChanged();
verifyNoMoreInteractions(groupChangeListener);
// Verify that only the priority notification is isolated from the group
assertEquals(priorityEntry, mGroupManager.getGroupSummary(priorityEntry));
assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(priorityEntry));
// Verify that the siblings are NOT isolated from the group
for (int i = 0; i < numSiblings; i++) {
assertEquals(summaryEntry, mGroupManager.getGroupSummary(siblings[i]));
assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(siblings[i]));
}
Log.d("NotificationGroupManagerLegacyTest",
"testAlertOverrideWhenUpdatingSummaryAtEnd: About to update summary");
StatusBarNotification oldSummarySbn = mGroupTestHelper.incrementPost(summaryEntry, 10000);
mGroupManager.onEntryUpdated(summaryEntry, oldSummarySbn);
verify(groupChangeListener, times(max2Siblings + 2)).onGroupsChanged();
verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, priorityEntry, null);
verifyNoMoreInteractions(groupChangeListener);
Log.d("NotificationGroupManagerLegacyTest",
"testAlertOverrideWhenUpdatingSummaryAtEnd: About to update priority child");
StatusBarNotification oldPrioritySbn = mGroupTestHelper.incrementPost(priorityEntry, 10000);
mGroupManager.onEntryUpdated(priorityEntry, oldPrioritySbn);
verify(groupChangeListener).onGroupRemoved(any(), eq(priorityEntry.getKey()));
verify(groupChangeListener, times(2)).onGroupCreated(any(), eq(priorityEntry.getKey()));
verify(groupChangeListener, times(2))
.onGroupAlertOverrideChanged(summaryGroup, null, priorityEntry);
verify(groupChangeListener, times(max2Siblings + 3)).onGroupsChanged();
verifyNoMoreInteractions(groupChangeListener);
Log.d("NotificationGroupManagerLegacyTest",
"testAlertOverrideWhenUpdatingSummaryAtEnd: Done");
}
}

View File

@@ -178,10 +178,10 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
bubbleSbn.getNotification().contentIntent = mContentIntent;
bubbleSbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
ArrayList<NotificationEntry> activeNotifications = new ArrayList<>();
activeNotifications.add(mNotificationRow.getEntry());
activeNotifications.add(mBubbleNotificationRow.getEntry());
when(mEntryManager.getVisibleNotifications()).thenReturn(activeNotifications);
// ArrayList<NotificationEntry> activeNotifications = new ArrayList<>();
// activeNotifications.add(mNotificationRow.getEntry());
// activeNotifications.add(mBubbleNotificationRow.getEntry());
// when(mEntryManager.getVisibleNotifications()).thenReturn(activeNotifications);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
when(mOnUserInteractionCallback.registerFutureDismissal(eq(mNotificationRow.getEntry()),
anyInt())).thenReturn(mFutureDismissalRunnable);
@@ -347,9 +347,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
// The content intent should NOT be sent on click.
verifyZeroInteractions(mContentIntent);
// Notification should not be cancelled.
verify(mEntryManager, never()).performRemoveNotification(eq(sbn), any(), anyInt());
}
@Test
@@ -380,9 +377,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
verify(mContentIntent).getIntent();
verify(mContentIntent).isActivity();
verifyNoMoreInteractions(mContentIntent);
// Notification should not be cancelled.
verify(mEntryManager, never()).performRemoveNotification(eq(sbn), any(), anyInt());
}
@Test

View File

@@ -38,7 +38,7 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -79,7 +79,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
mNotificationLockscreenUserManager);
mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext,
mock(NotificationGroupManagerLegacy.class), mNotificationLockscreenUserManager,
mock(GroupExpansionManager.class), mNotificationLockscreenUserManager,
mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager,
mActivityStarter, mShadeController, new CommandQueue(mContext),
mock(ActionClickLogger.class), mFakeExecutor));