Merge "Ability to hide/unhide suspended app notifications" into pi-dev am: 6c0f42b3b5
am: de3719dffe
Change-Id: I60893e37fba9da2e6f567baca6a57afebd910e8a
diff --git a/api/current.txt b/api/current.txt
index bdc5195..8441279 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -40058,6 +40058,7 @@
method public int getSuppressedVisualEffects();
method public int getUserSentiment();
method public boolean isAmbient();
+ method public boolean isSuspended();
method public boolean matchesInterruptionFilter();
field public static final int USER_SENTIMENT_NEGATIVE = -1; // 0xffffffff
field public static final int USER_SENTIMENT_NEUTRAL = 0; // 0x0
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index ea10ae7..3726e66 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1418,6 +1418,7 @@
private ArrayList<SnoozeCriterion> mSnoozeCriteria;
private boolean mShowBadge;
private @UserSentiment int mUserSentiment = USER_SENTIMENT_NEUTRAL;
+ private boolean mHidden;
public Ranking() {}
@@ -1557,6 +1558,16 @@
}
/**
+ * Returns whether the app that posted this notification is suspended, so this notification
+ * should be hidden.
+ *
+ * @return true if the notification should be hidden, false otherwise.
+ */
+ public boolean isSuspended() {
+ return mHidden;
+ }
+
+ /**
* @hide
*/
@VisibleForTesting
@@ -1565,7 +1576,7 @@
CharSequence explanation, String overrideGroupKey,
NotificationChannel channel, ArrayList<String> overridePeople,
ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge,
- int userSentiment) {
+ int userSentiment, boolean hidden) {
mKey = key;
mRank = rank;
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1580,6 +1591,7 @@
mSnoozeCriteria = snoozeCriteria;
mShowBadge = showBadge;
mUserSentiment = userSentiment;
+ mHidden = hidden;
}
/**
@@ -1628,6 +1640,7 @@
private ArrayMap<String, ArrayList<SnoozeCriterion>> mSnoozeCriteria;
private ArrayMap<String, Boolean> mShowBadge;
private ArrayMap<String, Integer> mUserSentiment;
+ private ArrayMap<String, Boolean> mHidden;
private RankingMap(NotificationRankingUpdate rankingUpdate) {
mRankingUpdate = rankingUpdate;
@@ -1656,7 +1669,7 @@
getVisibilityOverride(key), getSuppressedVisualEffects(key),
getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key),
getChannel(key), getOverridePeople(key), getSnoozeCriteria(key),
- getShowBadge(key), getUserSentiment(key));
+ getShowBadge(key), getUserSentiment(key), getHidden(key));
return rank >= 0;
}
@@ -1784,6 +1797,16 @@
? Ranking.USER_SENTIMENT_NEUTRAL : userSentiment.intValue();
}
+ private boolean getHidden(String key) {
+ synchronized (this) {
+ if (mHidden == null) {
+ buildHiddenLocked();
+ }
+ }
+ Boolean hidden = mHidden.get(key);
+ return hidden == null ? false : hidden.booleanValue();
+ }
+
// Locked by 'this'
private void buildRanksLocked() {
String[] orderedKeys = mRankingUpdate.getOrderedKeys();
@@ -1892,6 +1915,15 @@
}
}
+ // Locked by 'this'
+ private void buildHiddenLocked() {
+ Bundle hidden = mRankingUpdate.getHidden();
+ mHidden = new ArrayMap<>(hidden.size());
+ for (String key : hidden.keySet()) {
+ mHidden.put(key, hidden.getBoolean(key));
+ }
+ }
+
// ----------- Parcelable
@Override
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 6d51db0..00c47ec 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -36,12 +36,13 @@
private final Bundle mSnoozeCriteria;
private final Bundle mShowBadge;
private final Bundle mUserSentiment;
+ private final Bundle mHidden;
public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
Bundle visibilityOverrides, Bundle suppressedVisualEffects,
int[] importance, Bundle explanation, Bundle overrideGroupKeys,
Bundle channels, Bundle overridePeople, Bundle snoozeCriteria,
- Bundle showBadge, Bundle userSentiment) {
+ Bundle showBadge, Bundle userSentiment, Bundle hidden) {
mKeys = keys;
mInterceptedKeys = interceptedKeys;
mVisibilityOverrides = visibilityOverrides;
@@ -54,6 +55,7 @@
mSnoozeCriteria = snoozeCriteria;
mShowBadge = showBadge;
mUserSentiment = userSentiment;
+ mHidden = hidden;
}
public NotificationRankingUpdate(Parcel in) {
@@ -70,6 +72,7 @@
mSnoozeCriteria = in.readBundle();
mShowBadge = in.readBundle();
mUserSentiment = in.readBundle();
+ mHidden = in.readBundle();
}
@Override
@@ -91,6 +94,7 @@
out.writeBundle(mSnoozeCriteria);
out.writeBundle(mShowBadge);
out.writeBundle(mUserSentiment);
+ out.writeBundle(mHidden);
}
public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -151,4 +155,8 @@
public Bundle getUserSentiment() {
return mUserSentiment;
}
+
+ public Bundle getHidden() {
+ return mHidden;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 22a186f..58e492f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -529,6 +529,14 @@
return null;
}
+ public boolean shouldHide(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.isSuspended();
+ }
+ return false;
+ }
+
private void updateRankingAndSort(RankingMap ranking) {
if (ranking != null) {
mRankingMap = ranking;
@@ -618,6 +626,10 @@
return true;
}
+ if (shouldHide(sbn.getKey())) {
+ return true;
+ }
+
if (!StatusBar.ENABLE_CHILD_NOTIFICATIONS
&& mGroupManager.isChildInGroupWithSummary(sbn)) {
return true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
index b1e1c02..2000bff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
@@ -60,6 +60,7 @@
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);
@@ -247,6 +248,22 @@
assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry().notification));
}
+ @Test
+ public void testShouldFilterHiddenNotifications() {
+ // setup
+ when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false);
+ when(mFsc.isSystemAlertNotification(any())).thenReturn(false);
+
+ // test should filter out hidden notifications:
+ // hidden
+ when(mMockStatusBarNotification.getKey()).thenReturn(TEST_HIDDEN_NOTIFICATION_KEY);
+ assertTrue(mNotificationData.shouldFilterOut(mMockStatusBarNotification));
+
+ // not hidden
+ when(mMockStatusBarNotification.getKey()).thenReturn("not hidden");
+ assertFalse(mNotificationData.shouldFilterOut(mMockStatusBarNotification));
+ }
+
private void initStatusBarNotification(boolean allowDuringSetup) {
Bundle bundle = new Bundle();
bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup);
@@ -269,6 +286,21 @@
@Override
protected boolean getRanking(String key, NotificationListenerService.Ranking outRanking) {
super.getRanking(key, outRanking);
+ if (key.equals(TEST_HIDDEN_NOTIFICATION_KEY)) {
+ outRanking.populate(key, outRanking.getRank(),
+ outRanking.matchesInterruptionFilter(),
+ outRanking.getVisibilityOverride(), outRanking.getSuppressedVisualEffects(),
+ outRanking.getImportance(), outRanking.getImportanceExplanation(),
+ outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
+ outRanking.canShowBadge(), outRanking.getUserSentiment(), true);
+ } else {
+ outRanking.populate(key, outRanking.getRank(),
+ outRanking.matchesInterruptionFilter(),
+ outRanking.getVisibilityOverride(), outRanking.getSuppressedVisualEffects(),
+ outRanking.getImportance(), outRanking.getImportanceExplanation(),
+ outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
+ outRanking.canShowBadge(), outRanking.getUserSentiment(), false);
+ }
return true;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
index 7e5db34..3703d6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
@@ -140,7 +140,7 @@
0,
NotificationManager.IMPORTANCE_DEFAULT,
null, null,
- null, null, null, true, sentiment);
+ null, null, null, true, sentiment, false);
return true;
}).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index cd19eb8..27eeb93 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -959,6 +959,8 @@
boolean queryRemove = false;
boolean packageChanged = false;
boolean cancelNotifications = true;
+ boolean hideNotifications = false;
+ boolean unhideNotifications = false;
int reason = REASON_PACKAGE_CHANGED;
if (action.equals(Intent.ACTION_PACKAGE_ADDED)
@@ -967,7 +969,8 @@
|| (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED))
|| (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
|| action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)
- || action.equals(Intent.ACTION_PACKAGES_SUSPENDED)) {
+ || action.equals(Intent.ACTION_PACKAGES_SUSPENDED)
+ || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)) {
int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
UserHandle.USER_ALL);
String pkgList[] = null;
@@ -980,7 +983,12 @@
uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
} else if (action.equals(Intent.ACTION_PACKAGES_SUSPENDED)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- reason = REASON_PACKAGE_SUSPENDED;
+ cancelNotifications = false;
+ hideNotifications = true;
+ } else if (action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)) {
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ cancelNotifications = false;
+ unhideNotifications = true;
} else if (queryRestart) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)};
@@ -1022,9 +1030,15 @@
if (cancelNotifications) {
cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, null, 0, 0,
!queryRestart, changeUserId, reason, null);
+ } else if (hideNotifications) {
+ hideNotificationsForPackages(pkgList);
+ } else if (unhideNotifications) {
+ unhideNotificationsForPackages(pkgList);
}
+
}
}
+
mListeners.onPackagesChanged(removingPackage, pkgList, uidList);
mAssistants.onPackagesChanged(removingPackage, pkgList, uidList);
mConditionProviders.onPackagesChanged(removingPackage, pkgList, uidList);
@@ -1479,6 +1493,7 @@
IntentFilter suspendedPkgFilter = new IntentFilter();
suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
+ suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL,
suspendedPkgFilter, null, null);
@@ -2486,6 +2501,7 @@
try {
synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+
if (keys != null) {
final int N = keys.length;
for (int i = 0; i < N; i++) {
@@ -4271,6 +4287,14 @@
}
}
+ @GuardedBy("mNotificationLock")
+ private boolean isPackageSuspendedLocked(NotificationRecord r) {
+ final String pkg = r.sbn.getPackageName();
+ final int callingUid = r.sbn.getUid();
+
+ return isPackageSuspendedForUser(pkg, callingUid);
+ }
+
protected class PostNotificationRunnable implements Runnable {
private final String key;
@@ -4295,6 +4319,8 @@
Slog.i(TAG, "Cannot find enqueued record for key: " + key);
return;
}
+
+ r.setHidden(isPackageSuspendedLocked(r));
NotificationRecord old = mNotificationsByKey.get(key);
final StatusBarNotification n = r.sbn;
final Notification notification = n.getNotification();
@@ -4347,7 +4373,7 @@
} else {
Slog.e(TAG, "Not posting notification without small icon: " + notification);
if (old != null && !old.isCanceled) {
- mListeners.notifyRemovedLocked(n,
+ mListeners.notifyRemovedLocked(r,
NotificationListenerService.REASON_ERROR, null);
mHandler.post(new Runnable() {
@Override
@@ -4363,7 +4389,9 @@
+ n.getPackageName());
}
- buzzBeepBlinkLocked(r);
+ if (!r.isHidden()) {
+ buzzBeepBlinkLocked(r);
+ }
maybeRecordInterruptionLocked(r);
} finally {
int N = mEnqueuedNotifications.size();
@@ -5022,7 +5050,7 @@
private void handleSendRankingUpdate() {
synchronized (mNotificationLock) {
- mListeners.notifyRankingUpdateLocked();
+ mListeners.notifyRankingUpdateLocked(null);
}
}
@@ -5207,7 +5235,7 @@
if (reason != REASON_SNOOZED) {
r.isCanceled = true;
}
- mListeners.notifyRemovedLocked(r.sbn, reason, r.getStats());
+ mListeners.notifyRemovedLocked(r, reason, r.getStats());
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -5316,6 +5344,7 @@
final String pkg, final String tag, final int id,
final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete,
final int userId, final int reason, final ManagedServiceInfo listener) {
+
// In enqueueNotificationInternal notifications are added by scheduling the
// work on the worker handler. Hence, we also schedule the cancel on this
// handler to avoid a scenario where an add notification call followed by a
@@ -5707,6 +5736,42 @@
return -1;
}
+ @VisibleForTesting
+ protected void hideNotificationsForPackages(String[] pkgs) {
+ synchronized (mNotificationLock) {
+ List<String> pkgList = Arrays.asList(pkgs);
+ List<NotificationRecord> changedNotifications = new ArrayList<>();
+ int numNotifications = mNotificationList.size();
+ for (int i = 0; i < numNotifications; i++) {
+ NotificationRecord rec = mNotificationList.get(i);
+ if (pkgList.contains(rec.sbn.getPackageName())) {
+ rec.setHidden(true);
+ changedNotifications.add(rec);
+ }
+ }
+
+ mListeners.notifyHiddenLocked(changedNotifications);
+ }
+ }
+
+ @VisibleForTesting
+ protected void unhideNotificationsForPackages(String[] pkgs) {
+ synchronized (mNotificationLock) {
+ List<String> pkgList = Arrays.asList(pkgs);
+ List<NotificationRecord> changedNotifications = new ArrayList<>();
+ int numNotifications = mNotificationList.size();
+ for (int i = 0; i < numNotifications; i++) {
+ NotificationRecord rec = mNotificationList.get(i);
+ if (pkgList.contains(rec.sbn.getPackageName())) {
+ rec.setHidden(false);
+ changedNotifications.add(rec);
+ }
+ }
+
+ mListeners.notifyUnhiddenLocked(changedNotifications);
+ }
+ }
+
private void updateNotificationPulse() {
synchronized (mNotificationLock) {
updateLightsLocked();
@@ -5826,6 +5891,7 @@
Bundle snoozeCriteria = new Bundle();
Bundle showBadge = new Bundle();
Bundle userSentiment = new Bundle();
+ Bundle hidden = new Bundle();
for (int i = 0; i < N; i++) {
NotificationRecord record = mNotificationList.get(i);
if (!isVisibleToListener(record.sbn, info)) {
@@ -5852,6 +5918,7 @@
snoozeCriteria.putParcelableArrayList(key, record.getSnoozeCriteria());
showBadge.putBoolean(key, record.canShowBadge());
userSentiment.putInt(key, record.getUserSentiment());
+ hidden.putBoolean(key, record.isHidden());
}
final int M = keys.size();
String[] keysAr = keys.toArray(new String[M]);
@@ -5862,7 +5929,7 @@
}
return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
- channels, overridePeople, snoozeCriteria, showBadge, userSentiment);
+ channels, overridePeople, snoozeCriteria, showBadge, userSentiment, hidden);
}
boolean hasCompanionDevice(ManagedServiceInfo info) {
@@ -6151,6 +6218,16 @@
*/
@GuardedBy("mNotificationLock")
public void notifyPostedLocked(NotificationRecord r, StatusBarNotification oldSbn) {
+ notifyPostedLocked(r, oldSbn, true);
+ }
+
+ /**
+ * @param notifyAllListeners notifies all listeners if true, else only notifies listeners
+ * targetting <= O_MR1
+ */
+ @GuardedBy("mNotificationLock")
+ private void notifyPostedLocked(NotificationRecord r, StatusBarNotification oldSbn,
+ boolean notifyAllListeners) {
// Lazily initialized snapshots of the notification.
StatusBarNotification sbn = r.sbn;
TrimCache trimCache = new TrimCache(sbn);
@@ -6164,6 +6241,21 @@
if (!oldSbnVisible && !sbnVisible) {
continue;
}
+
+ // If the notification is hidden, don't notifyPosted listeners targeting < P.
+ // Instead, those listeners will receive notifyPosted when the notification is
+ // unhidden.
+ if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {
+ continue;
+ }
+
+ // If we shouldn't notify all listeners, this means the hidden state of
+ // a notification was changed. Don't notifyPosted listeners targeting >= P.
+ // Instead, those listeners will receive notifyRankingUpdate.
+ if (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) {
+ continue;
+ }
+
final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
// This notification became invisible -> remove the old one.
@@ -6218,8 +6310,9 @@
* asynchronously notify all listeners about a removed notification
*/
@GuardedBy("mNotificationLock")
- public void notifyRemovedLocked(StatusBarNotification sbn, int reason,
+ public void notifyRemovedLocked(NotificationRecord r, int reason,
NotificationStats notificationStats) {
+ final StatusBarNotification sbn = r.sbn;
// make a copy in case changes are made to the underlying Notification object
// NOTE: this copy is lightweight: it doesn't include heavyweight parts of the
// notification
@@ -6228,6 +6321,21 @@
if (!isVisibleToListener(sbn, info)) {
continue;
}
+
+ // don't notifyRemoved for listeners targeting < P
+ // if not for reason package suspended
+ if (r.isHidden() && reason != REASON_PACKAGE_SUSPENDED
+ && info.targetSdkVersion < Build.VERSION_CODES.P) {
+ continue;
+ }
+
+ // don't notifyRemoved for listeners targeting >= P
+ // if the reason is package suspended
+ if (reason == REASON_PACKAGE_SUSPENDED
+ && info.targetSdkVersion >= Build.VERSION_CODES.P) {
+ continue;
+ }
+
// Only assistants can get stats
final NotificationStats stats = mAssistants.isServiceTokenValidLocked(info.service)
? notificationStats : null;
@@ -6242,21 +6350,44 @@
}
/**
- * asynchronously notify all listeners about a reordering of notifications
+ * Asynchronously notify all listeners about a reordering of notifications
+ * unless changedHiddenNotifications is populated.
+ * If changedHiddenNotifications is populated, there was a change in the hidden state
+ * of the notifications. In this case, we only send updates to listeners that
+ * target >= P.
*/
@GuardedBy("mNotificationLock")
- public void notifyRankingUpdateLocked() {
+ public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) {
+ boolean isHiddenRankingUpdate = changedHiddenNotifications != null
+ && changedHiddenNotifications.size() > 0;
+
for (final ManagedServiceInfo serviceInfo : getServices()) {
if (!serviceInfo.isEnabledForCurrentProfiles()) {
continue;
}
- final NotificationRankingUpdate update = makeRankingUpdateLocked(serviceInfo);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- notifyRankingUpdate(serviceInfo, update);
+
+ boolean notifyThisListener = false;
+ if (isHiddenRankingUpdate && serviceInfo.targetSdkVersion >=
+ Build.VERSION_CODES.P) {
+ for (NotificationRecord rec : changedHiddenNotifications) {
+ if (isVisibleToListener(rec.sbn, serviceInfo)) {
+ notifyThisListener = true;
+ break;
+ }
}
- });
+ }
+
+ if (notifyThisListener || !isHiddenRankingUpdate) {
+ final NotificationRankingUpdate update = makeRankingUpdateLocked(
+ serviceInfo);
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ notifyRankingUpdate(serviceInfo, update);
+ }
+ });
+ }
}
}
@@ -6275,6 +6406,52 @@
}
}
+ /**
+ * asynchronously notify relevant listeners their notification is hidden
+ * NotificationListenerServices that target P+:
+ * NotificationListenerService#notifyRankingUpdateLocked()
+ * NotificationListenerServices that target <= P:
+ * NotificationListenerService#notifyRemovedLocked() with REASON_PACKAGE_SUSPENDED.
+ */
+ @GuardedBy("mNotificationLock")
+ public void notifyHiddenLocked(List<NotificationRecord> changedNotifications) {
+ if (changedNotifications == null || changedNotifications.size() == 0) {
+ return;
+ }
+
+ notifyRankingUpdateLocked(changedNotifications);
+
+ // for listeners that target < P, notifyRemoveLocked
+ int numChangedNotifications = changedNotifications.size();
+ for (int i = 0; i < numChangedNotifications; i++) {
+ NotificationRecord rec = changedNotifications.get(i);
+ mListeners.notifyRemovedLocked(rec, REASON_PACKAGE_SUSPENDED, rec.getStats());
+ }
+ }
+
+ /**
+ * asynchronously notify relevant listeners their notification is unhidden
+ * NotificationListenerServices that target P+:
+ * NotificationListenerService#notifyRankingUpdateLocked()
+ * NotificationListenerServices that target <= P:
+ * NotificationListeners#notifyPostedLocked()
+ */
+ @GuardedBy("mNotificationLock")
+ public void notifyUnhiddenLocked(List<NotificationRecord> changedNotifications) {
+ if (changedNotifications == null || changedNotifications.size() == 0) {
+ return;
+ }
+
+ notifyRankingUpdateLocked(changedNotifications);
+
+ // for listeners that target < P, notifyPostedLocked
+ int numChangedNotifications = changedNotifications.size();
+ for (int i = 0; i < numChangedNotifications; i++) {
+ NotificationRecord rec = changedNotifications.get(i);
+ mListeners.notifyPostedLocked(rec, rec.sbn, false);
+ }
+ }
+
public void notifyInterruptionFilterChanged(final int interruptionFilter) {
for (final ManagedServiceInfo serviceInfo : getServices()) {
if (!serviceInfo.isEnabledForCurrentProfiles()) {
@@ -6503,6 +6680,22 @@
}
}
+ @VisibleForTesting
+ protected void simulatePackageSuspendBroadcast(boolean suspend, String pkg) {
+ // only use for testing: mimic receive broadcast that package is (un)suspended
+ // but does not actually (un)suspend the package
+ final Bundle extras = new Bundle();
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
+ new String[]{pkg});
+
+ final String action = suspend ? Intent.ACTION_PACKAGES_SUSPENDED
+ : Intent.ACTION_PACKAGES_UNSUSPENDED;
+ final Intent intent = new Intent(action);
+ intent.putExtras(extras);
+
+ mPackageIntentReceiver.onReceive(getContext(), intent);
+ }
+
/**
* Wrapper for a StatusBarNotification object that allows transfer across a oneway
* binder without sending large amounts of data over a oneway transaction.
@@ -6531,7 +6724,9 @@
+ "allow_assistant COMPONENT\n"
+ "remove_assistant COMPONENT\n"
+ "allow_dnd PACKAGE\n"
- + "disallow_dnd PACKAGE";
+ + "disallow_dnd PACKAGE\n"
+ + "suspend_package PACKAGE\n"
+ + "unsuspend_package PACKAGE";
@Override
public int onCommand(String cmd) {
@@ -6600,7 +6795,16 @@
getBinderService().setNotificationAssistantAccessGranted(cn, false);
}
break;
-
+ case "suspend_package": {
+ // only use for testing
+ simulatePackageSuspendBroadcast(true, getNextArgRequired());
+ }
+ break;
+ case "unsuspend_package": {
+ // only use for testing
+ simulatePackageSuspendBroadcast(false, getNextArgRequired());
+ }
+ break;
default:
return handleDefaultCommands(cmd);
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index f1908bf..c887085 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -103,6 +103,9 @@
// is this notification currently being intercepted by Zen Mode?
private boolean mIntercept;
+ // is this notification hidden since the app pkg is suspended?
+ private boolean mHidden;
+
// The timestamp used for ranking.
private long mRankingTimeMs;
@@ -353,6 +356,7 @@
mPackagePriority = previous.mPackagePriority;
mPackageVisibility = previous.mPackageVisibility;
mIntercept = previous.mIntercept;
+ mHidden = previous.mHidden;
mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
mCreationTimeMs = previous.mCreationTimeMs;
mVisibleSinceMs = previous.mVisibleSinceMs;
@@ -498,6 +502,7 @@
+ NotificationListenerService.Ranking.importanceToString(mImportance));
pw.println(prefix + "mImportanceExplanation=" + mImportanceExplanation);
pw.println(prefix + "mIntercept=" + mIntercept);
+ pw.println(prefix + "mHidden==" + mHidden);
pw.println(prefix + "mGlobalSortKey=" + mGlobalSortKey);
pw.println(prefix + "mRankingTimeMs=" + mRankingTimeMs);
pw.println(prefix + "mCreationTimeMs=" + mCreationTimeMs);
@@ -702,6 +707,15 @@
return mIntercept;
}
+ public void setHidden(boolean hidden) {
+ mHidden = hidden;
+ }
+
+ public boolean isHidden() {
+ return mHidden;
+ }
+
+
public void setSuppressedVisualEffects(int effects) {
mSuppressedVisualEffects = effects;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index f4313b8..181fceb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -70,6 +70,7 @@
assertEquals(getSnoozeCriteria(key, i), ranking.getSnoozeCriteria());
assertEquals(getShowBadge(i), ranking.canShowBadge());
assertEquals(getUserSentiment(i), ranking.getUserSentiment());
+ assertEquals(getHidden(i), ranking.isSuspended());
}
}
@@ -85,6 +86,7 @@
Bundle showBadge = new Bundle();
int[] importance = new int[mKeys.length];
Bundle userSentiment = new Bundle();
+ Bundle mHidden = new Bundle();
for (int i = 0; i < mKeys.length; i++) {
String key = mKeys[i];
@@ -101,11 +103,12 @@
snoozeCriteria.putParcelableArrayList(key, getSnoozeCriteria(key, i));
showBadge.putBoolean(key, getShowBadge(i));
userSentiment.putInt(key, getUserSentiment(i));
+ mHidden.putBoolean(key, getHidden(i));
}
NotificationRankingUpdate update = new NotificationRankingUpdate(mKeys,
interceptedKeys.toArray(new String[0]), visibilityOverrides,
suppressedVisualEffects, importance, explanation, overrideGroupKeys,
- channels, overridePeople, snoozeCriteria, showBadge, userSentiment);
+ channels, overridePeople, snoozeCriteria, showBadge, userSentiment, mHidden);
return update;
}
@@ -153,6 +156,10 @@
return USER_SENTIMENT_NEUTRAL;
}
+ private boolean getHidden(int index) {
+ return index % 2 == 0;
+ }
+
private ArrayList<String> getPeople(String key, int index) {
ArrayList<String> people = new ArrayList<>();
for (int i = 0; i < index; i++) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 9afaa24..b82becd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -2739,4 +2739,44 @@
assertFalse(mService.isVisuallyInterruptive(r1, r2));
}
+
+ @Test
+ public void testHideAndUnhideNotificationsOnSuspendedPackageBroadcast() {
+ // post 2 notification from this package
+ final NotificationRecord notif1 = generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true);
+ final NotificationRecord notif2 = generateNotificationRecord(
+ mTestNotificationChannel, 2, null, false);
+ mService.addNotification(notif1);
+ mService.addNotification(notif2);
+
+ // on broadcast, hide the 2 notifications
+ mService.simulatePackageSuspendBroadcast(true, PKG);
+ ArgumentCaptor<List> captorHide = ArgumentCaptor.forClass(List.class);
+ verify(mListeners, times(1)).notifyHiddenLocked(captorHide.capture());
+ assertEquals(2, captorHide.getValue().size());
+
+ // on broadcast, unhide the 2 notifications
+ mService.simulatePackageSuspendBroadcast(false, PKG);
+ ArgumentCaptor<List> captorUnhide = ArgumentCaptor.forClass(List.class);
+ verify(mListeners, times(1)).notifyUnhiddenLocked(captorUnhide.capture());
+ assertEquals(2, captorUnhide.getValue().size());
+ }
+
+ @Test
+ public void testNoNotificationsHiddenOnSuspendedPackageBroadcast() {
+ // post 2 notification from this package
+ final NotificationRecord notif1 = generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true);
+ final NotificationRecord notif2 = generateNotificationRecord(
+ mTestNotificationChannel, 2, null, false);
+ mService.addNotification(notif1);
+ mService.addNotification(notif2);
+
+ // on broadcast, nothing is hidden since no notifications are of package "test_package"
+ mService.simulatePackageSuspendBroadcast(true, "test_package");
+ ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
+ verify(mListeners, times(1)).notifyHiddenLocked(captor.capture());
+ assertEquals(0, captor.getValue().size());
+ }
}