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:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,10 +115,8 @@ public interface CentralSurfacesDependenciesModule {
|
||||
smartReplyController,
|
||||
visibilityProvider,
|
||||
notificationEntryManager,
|
||||
rebuilder,
|
||||
centralSurfacesOptionalLazy,
|
||||
statusBarStateController,
|
||||
mainHandler,
|
||||
remoteInputUriController,
|
||||
clickNotifier,
|
||||
actionClickLogger,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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} */
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,10 +161,8 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
|
||||
smartReplyController,
|
||||
visibilityProvider,
|
||||
notificationEntryManager,
|
||||
rebuilder,
|
||||
centralSurfacesOptionalLazy,
|
||||
statusBarStateController,
|
||||
mainHandler,
|
||||
remoteInputUriController,
|
||||
clickNotifier,
|
||||
actionClickLogger,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -418,7 +418,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
|
||||
wakefulnessLifecycle,
|
||||
mStatusBarStateController,
|
||||
Optional.of(mBubbles),
|
||||
mVisualStabilityManager,
|
||||
mDeviceProvisionedController,
|
||||
mNavigationBarController,
|
||||
mAccessibilityFloatingMenuController,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user