Merge "Ability to hide/unhide suspended app notifications" into pi-dev
am: 6c0f42b3b5
Change-Id: Iad46af0da71abd4c879b8a0811ebe1943d064b7a
This commit is contained in:
@@ -40058,6 +40058,7 @@ package android.service.notification {
|
||||
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
|
||||
|
||||
@@ -1418,6 +1418,7 @@ public abstract class NotificationListenerService extends Service {
|
||||
private ArrayList<SnoozeCriterion> mSnoozeCriteria;
|
||||
private boolean mShowBadge;
|
||||
private @UserSentiment int mUserSentiment = USER_SENTIMENT_NEUTRAL;
|
||||
private boolean mHidden;
|
||||
|
||||
public Ranking() {}
|
||||
|
||||
@@ -1556,6 +1557,16 @@ public abstract class NotificationListenerService extends Service {
|
||||
return mShowBadge;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@@ -1565,7 +1576,7 @@ public abstract class NotificationListenerService extends Service {
|
||||
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 @@ public abstract class NotificationListenerService extends Service {
|
||||
mSnoozeCriteria = snoozeCriteria;
|
||||
mShowBadge = showBadge;
|
||||
mUserSentiment = userSentiment;
|
||||
mHidden = hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1628,6 +1640,7 @@ public abstract class NotificationListenerService extends Service {
|
||||
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 @@ public abstract class NotificationListenerService extends Service {
|
||||
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 @@ public abstract class NotificationListenerService extends Service {
|
||||
? 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 @@ public abstract class NotificationListenerService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@@ -36,12 +36,13 @@ public class NotificationRankingUpdate implements Parcelable {
|
||||
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 @@ public class NotificationRankingUpdate implements Parcelable {
|
||||
mSnoozeCriteria = snoozeCriteria;
|
||||
mShowBadge = showBadge;
|
||||
mUserSentiment = userSentiment;
|
||||
mHidden = hidden;
|
||||
}
|
||||
|
||||
public NotificationRankingUpdate(Parcel in) {
|
||||
@@ -70,6 +72,7 @@ public class NotificationRankingUpdate implements Parcelable {
|
||||
mSnoozeCriteria = in.readBundle();
|
||||
mShowBadge = in.readBundle();
|
||||
mUserSentiment = in.readBundle();
|
||||
mHidden = in.readBundle();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,6 +94,7 @@ public class NotificationRankingUpdate implements Parcelable {
|
||||
out.writeBundle(mSnoozeCriteria);
|
||||
out.writeBundle(mShowBadge);
|
||||
out.writeBundle(mUserSentiment);
|
||||
out.writeBundle(mHidden);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
|
||||
@@ -151,4 +155,8 @@ public class NotificationRankingUpdate implements Parcelable {
|
||||
public Bundle getUserSentiment() {
|
||||
return mUserSentiment;
|
||||
}
|
||||
|
||||
public Bundle getHidden() {
|
||||
return mHidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,6 +529,14 @@ public class NotificationData {
|
||||
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 @@ public class NotificationData {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (shouldHide(sbn.getKey())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!StatusBar.ENABLE_CHILD_NOTIFICATIONS
|
||||
&& mGroupManager.isChildInGroupWithSummary(sbn)) {
|
||||
return true;
|
||||
|
||||
@@ -60,6 +60,7 @@ public class NotificationDataTest 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);
|
||||
@@ -247,6 +248,22 @@ public class NotificationDataTest extends SysuiTestCase {
|
||||
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 @@ public class NotificationDataTest extends SysuiTestCase {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -959,6 +959,8 @@ public class NotificationManagerService extends SystemService {
|
||||
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 @@ public class NotificationManagerService extends SystemService {
|
||||
|| (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 @@ public class NotificationManagerService extends SystemService {
|
||||
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 @@ public class NotificationManagerService extends SystemService {
|
||||
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 @@ public class NotificationManagerService extends SystemService {
|
||||
|
||||
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 @@ public class NotificationManagerService extends SystemService {
|
||||
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 @@ public class NotificationManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
@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 @@ public class NotificationManagerService extends SystemService {
|
||||
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 @@ public class NotificationManagerService extends SystemService {
|
||||
} 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 @@ public class NotificationManagerService extends SystemService {
|
||||
+ n.getPackageName());
|
||||
}
|
||||
|
||||
buzzBeepBlinkLocked(r);
|
||||
if (!r.isHidden()) {
|
||||
buzzBeepBlinkLocked(r);
|
||||
}
|
||||
maybeRecordInterruptionLocked(r);
|
||||
} finally {
|
||||
int N = mEnqueuedNotifications.size();
|
||||
@@ -5022,7 +5050,7 @@ public class NotificationManagerService extends SystemService {
|
||||
|
||||
private void handleSendRankingUpdate() {
|
||||
synchronized (mNotificationLock) {
|
||||
mListeners.notifyRankingUpdateLocked();
|
||||
mListeners.notifyRankingUpdateLocked(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5207,7 +5235,7 @@ public class NotificationManagerService extends SystemService {
|
||||
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 @@ public class NotificationManagerService extends SystemService {
|
||||
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 @@ public class NotificationManagerService extends SystemService {
|
||||
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 @@ public class NotificationManagerService extends SystemService {
|
||||
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 @@ public class NotificationManagerService extends SystemService {
|
||||
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 @@ public class NotificationManagerService extends SystemService {
|
||||
}
|
||||
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 @@ public class NotificationManagerService extends SystemService {
|
||||
*/
|
||||
@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 @@ public class NotificationManagerService extends SystemService {
|
||||
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 @@ public class NotificationManagerService extends SystemService {
|
||||
* 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 @@ public class NotificationManagerService extends SystemService {
|
||||
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 @@ public class NotificationManagerService extends SystemService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ public class NotificationManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ public class NotificationManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
@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 @@ public class NotificationManagerService extends SystemService {
|
||||
+ "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 @@ public class NotificationManagerService extends SystemService {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -103,6 +103,9 @@ public final class NotificationRecord {
|
||||
// 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 @@ public final class NotificationRecord {
|
||||
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 @@ public final class NotificationRecord {
|
||||
+ 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 @@ public final class NotificationRecord {
|
||||
return mIntercept;
|
||||
}
|
||||
|
||||
public void setHidden(boolean hidden) {
|
||||
mHidden = hidden;
|
||||
}
|
||||
|
||||
public boolean isHidden() {
|
||||
return mHidden;
|
||||
}
|
||||
|
||||
|
||||
public void setSuppressedVisualEffects(int effects) {
|
||||
mSuppressedVisualEffects = effects;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
|
||||
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 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
|
||||
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 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
|
||||
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 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
|
||||
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++) {
|
||||
|
||||
@@ -2739,4 +2739,44 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user