Merge "When a profile is stopped, remove pending snoozes" into tm-qpr-dev am: bd945c1115

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19213106

Change-Id: I8f7d4a38c55bbdda380376ab02ed438e787e749e
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Julia Reynolds
2022-07-14 20:46:13 +00:00
committed by Automerger Merge Worker
3 changed files with 198 additions and 286 deletions

View File

@@ -1794,6 +1794,7 @@ public class NotificationManagerService extends SystemService {
if (userHandle >= 0) { if (userHandle >= 0) {
cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle, cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
REASON_PROFILE_TURNED_OFF, null); REASON_PROFILE_TURNED_OFF, null);
mSnoozeHelper.clearData(userHandle);
} }
} else if (action.equals(Intent.ACTION_USER_PRESENT)) { } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
// turn off LED when user passes through lock screen // turn off LED when user passes through lock screen

View File

@@ -16,7 +16,6 @@
package com.android.server.notification; package com.android.server.notification;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
@@ -25,7 +24,6 @@ import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.net.Uri; import android.net.Uri;
import android.os.Binder; import android.os.Binder;
import android.os.SystemClock;
import android.os.UserHandle; import android.os.UserHandle;
import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification;
import android.util.ArrayMap; import android.util.ArrayMap;
@@ -38,18 +36,15 @@ import android.util.TypedXmlSerializer;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.util.XmlUtils;
import com.android.server.pm.PackageManagerService; import com.android.server.pm.PackageManagerService;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -59,7 +54,7 @@ import java.util.Set;
/** /**
* NotificationManagerService helper for handling snoozed notifications. * NotificationManagerService helper for handling snoozed notifications.
*/ */
public class SnoozeHelper { public final class SnoozeHelper {
public static final int XML_SNOOZED_NOTIFICATION_VERSION = 1; public static final int XML_SNOOZED_NOTIFICATION_VERSION = 1;
static final int CONCURRENT_SNOOZE_LIMIT = 500; static final int CONCURRENT_SNOOZE_LIMIT = 500;
@@ -68,8 +63,6 @@ public class SnoozeHelper {
private static final String XML_SNOOZED_NOTIFICATION = "notification"; private static final String XML_SNOOZED_NOTIFICATION = "notification";
private static final String XML_SNOOZED_NOTIFICATION_CONTEXT = "context"; private static final String XML_SNOOZED_NOTIFICATION_CONTEXT = "context";
private static final String XML_SNOOZED_NOTIFICATION_PKG = "pkg";
private static final String XML_SNOOZED_NOTIFICATION_USER_ID = "user-id";
private static final String XML_SNOOZED_NOTIFICATION_KEY = "key"; private static final String XML_SNOOZED_NOTIFICATION_KEY = "key";
//the time the snoozed notification should be reposted //the time the snoozed notification should be reposted
private static final String XML_SNOOZED_NOTIFICATION_TIME = "time"; private static final String XML_SNOOZED_NOTIFICATION_TIME = "time";
@@ -91,24 +84,19 @@ public class SnoozeHelper {
private AlarmManager mAm; private AlarmManager mAm;
private final ManagedServices.UserProfiles mUserProfiles; private final ManagedServices.UserProfiles mUserProfiles;
// User id | package name : notification key : record. // notification key : record.
private ArrayMap<String, ArrayMap<String, NotificationRecord>> private ArrayMap<String, NotificationRecord> mSnoozedNotifications = new ArrayMap<>();
mSnoozedNotifications = new ArrayMap<>(); // notification key : time-milliseconds .
// User id | package name : notification key : time-milliseconds .
// This member stores persisted snoozed notification trigger times. it persists through reboots // This member stores persisted snoozed notification trigger times. it persists through reboots
// It should have the notifications that haven't expired or re-posted yet // It should have the notifications that haven't expired or re-posted yet
private final ArrayMap<String, ArrayMap<String, Long>> private final ArrayMap<String, Long> mPersistedSnoozedNotifications = new ArrayMap<>();
mPersistedSnoozedNotifications = new ArrayMap<>(); // notification key : creation ID.
// User id | package name : notification key : creation ID .
// This member stores persisted snoozed notification trigger context for the assistant // This member stores persisted snoozed notification trigger context for the assistant
// it persists through reboots. // it persists through reboots.
// It should have the notifications that haven't expired or re-posted yet // It should have the notifications that haven't expired or re-posted yet
private final ArrayMap<String, ArrayMap<String, String>> private final ArrayMap<String, String>
mPersistedSnoozedNotificationsWithContext = new ArrayMap<>(); mPersistedSnoozedNotificationsWithContext = new ArrayMap<>();
// notification key : package.
private ArrayMap<String, String> mPackages = new ArrayMap<>();
// key : userId
private ArrayMap<String, Integer> mUsers = new ArrayMap<>();
private Callback mCallback; private Callback mCallback;
private final Object mLock = new Object(); private final Object mLock = new Object();
@@ -125,21 +113,9 @@ public class SnoozeHelper {
mUserProfiles = userProfiles; mUserProfiles = userProfiles;
} }
private String getPkgKey(@UserIdInt int userId, String pkg) {
return userId + "|" + pkg;
}
void cleanupPersistedContext(String key){
synchronized (mLock) {
int userId = mUsers.get(key);
String pkg = mPackages.get(key);
removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotificationsWithContext);
}
}
protected boolean canSnooze(int numberToSnooze) { protected boolean canSnooze(int numberToSnooze) {
synchronized (mLock) { synchronized (mLock) {
if ((mPackages.size() + numberToSnooze) > CONCURRENT_SNOOZE_LIMIT) { if ((mSnoozedNotifications.size() + numberToSnooze) > CONCURRENT_SNOOZE_LIMIT) {
return false; return false;
} }
} }
@@ -150,11 +126,7 @@ public class SnoozeHelper {
protected Long getSnoozeTimeForUnpostedNotification(int userId, String pkg, String key) { protected Long getSnoozeTimeForUnpostedNotification(int userId, String pkg, String key) {
Long time = null; Long time = null;
synchronized (mLock) { synchronized (mLock) {
ArrayMap<String, Long> snoozed = time = mPersistedSnoozedNotifications.get(key);
mPersistedSnoozedNotifications.get(getPkgKey(userId, pkg));
if (snoozed != null) {
time = snoozed.get(key);
}
} }
if (time == null) { if (time == null) {
time = 0L; time = 0L;
@@ -164,29 +136,26 @@ public class SnoozeHelper {
protected String getSnoozeContextForUnpostedNotification(int userId, String pkg, String key) { protected String getSnoozeContextForUnpostedNotification(int userId, String pkg, String key) {
synchronized (mLock) { synchronized (mLock) {
ArrayMap<String, String> snoozed = return mPersistedSnoozedNotificationsWithContext.get(key);
mPersistedSnoozedNotificationsWithContext.get(getPkgKey(userId, pkg));
if (snoozed != null) {
return snoozed.get(key);
}
} }
return null;
} }
protected boolean isSnoozed(int userId, String pkg, String key) { protected boolean isSnoozed(int userId, String pkg, String key) {
synchronized (mLock) { synchronized (mLock) {
return mSnoozedNotifications.containsKey(getPkgKey(userId, pkg)) return mSnoozedNotifications.containsKey(key);
&& mSnoozedNotifications.get(getPkgKey(userId, pkg)).containsKey(key);
} }
} }
protected Collection<NotificationRecord> getSnoozed(int userId, String pkg) { protected Collection<NotificationRecord> getSnoozed(int userId, String pkg) {
synchronized (mLock) { synchronized (mLock) {
if (mSnoozedNotifications.containsKey(getPkgKey(userId, pkg))) { ArrayList snoozed = new ArrayList();
return mSnoozedNotifications.get(getPkgKey(userId, pkg)).values(); for (NotificationRecord r : mSnoozedNotifications.values()) {
if (r.getUserId() == userId && r.getSbn().getPackageName().equals(pkg)) {
snoozed.add(r);
}
} }
return snoozed;
} }
return Collections.EMPTY_LIST;
} }
@NonNull @NonNull
@@ -194,15 +163,11 @@ public class SnoozeHelper {
String groupKey, Integer userId) { String groupKey, Integer userId) {
ArrayList<NotificationRecord> records = new ArrayList<>(); ArrayList<NotificationRecord> records = new ArrayList<>();
synchronized (mLock) { synchronized (mLock) {
ArrayMap<String, NotificationRecord> allRecords = for (int i = 0; i < mSnoozedNotifications.size(); i++) {
mSnoozedNotifications.get(getPkgKey(userId, pkg)); NotificationRecord r = mSnoozedNotifications.valueAt(i);
if (allRecords != null) { if (r.getSbn().getPackageName().equals(pkg) && r.getUserId() == userId
for (int i = 0; i < allRecords.size(); i++) { && Objects.equals(r.getSbn().getGroup(), groupKey)) {
NotificationRecord r = allRecords.valueAt(i); records.add(r);
String currentGroupKey = r.getSbn().getGroup();
if (Objects.equals(currentGroupKey, groupKey)) {
records.add(r);
}
} }
} }
} }
@@ -211,31 +176,16 @@ public class SnoozeHelper {
protected NotificationRecord getNotification(String key) { protected NotificationRecord getNotification(String key) {
synchronized (mLock) { synchronized (mLock) {
if (!mUsers.containsKey(key) || !mPackages.containsKey(key)) { return mSnoozedNotifications.get(key);
Slog.w(TAG, "Snoozed data sets no longer agree for " + key);
return null;
}
int userId = mUsers.get(key);
String pkg = mPackages.get(key);
ArrayMap<String, NotificationRecord> snoozed =
mSnoozedNotifications.get(getPkgKey(userId, pkg));
if (snoozed == null) {
return null;
}
return snoozed.get(key);
} }
} }
protected @NonNull List<NotificationRecord> getSnoozed() { protected @NonNull List<NotificationRecord> getSnoozed() {
synchronized (mLock) { synchronized (mLock) {
// caller filters records based on the current user profiles and listener access, so just // caller filters records based on the current user profiles and listener access,
// return everything // so just return everything
List<NotificationRecord> snoozed = new ArrayList<>(); List<NotificationRecord> snoozed = new ArrayList<>();
for (String userPkgKey : mSnoozedNotifications.keySet()) { snoozed.addAll(mSnoozedNotifications.values());
ArrayMap<String, NotificationRecord> snoozedRecords =
mSnoozedNotifications.get(userPkgKey);
snoozed.addAll(snoozedRecords.values());
}
return snoozed; return snoozed;
} }
} }
@@ -244,15 +194,13 @@ public class SnoozeHelper {
* Snoozes a notification and schedules an alarm to repost at that time. * Snoozes a notification and schedules an alarm to repost at that time.
*/ */
protected void snooze(NotificationRecord record, long duration) { protected void snooze(NotificationRecord record, long duration) {
String pkg = record.getSbn().getPackageName();
String key = record.getKey(); String key = record.getKey();
int userId = record.getUser().getIdentifier();
snooze(record); snooze(record);
scheduleRepost(pkg, key, userId, duration); scheduleRepost(key, duration);
Long activateAt = System.currentTimeMillis() + duration; Long activateAt = System.currentTimeMillis() + duration;
synchronized (mLock) { synchronized (mLock) {
storeRecordLocked(pkg, key, userId, mPersistedSnoozedNotifications, activateAt); mPersistedSnoozedNotifications.put(key, activateAt);
} }
} }
@@ -260,66 +208,33 @@ public class SnoozeHelper {
* Records a snoozed notification. * Records a snoozed notification.
*/ */
protected void snooze(NotificationRecord record, String contextId) { protected void snooze(NotificationRecord record, String contextId) {
int userId = record.getUser().getIdentifier();
if (contextId != null) { if (contextId != null) {
synchronized (mLock) { synchronized (mLock) {
storeRecordLocked(record.getSbn().getPackageName(), record.getKey(), mPersistedSnoozedNotificationsWithContext.put(record.getKey(), contextId);
userId, mPersistedSnoozedNotificationsWithContext, contextId);
} }
} }
snooze(record); snooze(record);
} }
private void snooze(NotificationRecord record) { private void snooze(NotificationRecord record) {
int userId = record.getUser().getIdentifier();
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "Snoozing " + record.getKey()); Slog.d(TAG, "Snoozing " + record.getKey());
} }
synchronized (mLock) { synchronized (mLock) {
storeRecordLocked(record.getSbn().getPackageName(), record.getKey(), mSnoozedNotifications.put(record.getKey(), record);
userId, mSnoozedNotifications, record);
} }
} }
private <T> void storeRecordLocked(String pkg, String key, Integer userId,
ArrayMap<String, ArrayMap<String, T>> targets, T object) {
mPackages.put(key, pkg);
mUsers.put(key, userId);
ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg));
if (keyToValue == null) {
keyToValue = new ArrayMap<>();
}
keyToValue.put(key, object);
targets.put(getPkgKey(userId, pkg), keyToValue);
}
private <T> T removeRecordLocked(String pkg, String key, Integer userId,
ArrayMap<String, ArrayMap<String, T>> targets) {
T object = null;
ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg));
if (keyToValue == null) {
return null;
}
object = keyToValue.remove(key);
if (keyToValue.size() == 0) {
targets.remove(getPkgKey(userId, pkg));
}
return object;
}
protected boolean cancel(int userId, String pkg, String tag, int id) { protected boolean cancel(int userId, String pkg, String tag, int id) {
synchronized (mLock) { synchronized (mLock) {
ArrayMap<String, NotificationRecord> recordsForPkg = final Set<Map.Entry<String, NotificationRecord>> records =
mSnoozedNotifications.get(getPkgKey(userId, pkg)); mSnoozedNotifications.entrySet();
if (recordsForPkg != null) { for (Map.Entry<String, NotificationRecord> record : records) {
final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet(); final StatusBarNotification sbn = record.getValue().getSbn();
for (Map.Entry<String, NotificationRecord> record : records) { if (sbn.getPackageName().equals(pkg) && sbn.getUserId() == userId
final StatusBarNotification sbn = record.getValue().getSbn(); && Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) {
if (Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) { record.getValue().isCanceled = true;
record.getValue().isCanceled = true; return true;
return true;
}
} }
} }
} }
@@ -336,11 +251,9 @@ public class SnoozeHelper {
if (includeCurrentProfiles) { if (includeCurrentProfiles) {
userIds = mUserProfiles.getCurrentProfileIds(); userIds = mUserProfiles.getCurrentProfileIds();
} }
for (ArrayMap<String, NotificationRecord> snoozedRecords : mSnoozedNotifications.values()) { for (NotificationRecord r : mSnoozedNotifications.values()) {
for (NotificationRecord r : snoozedRecords.values()) { if (userIds.binarySearch(r.getUserId()) >= 0) {
if (userIds.binarySearch(r.getUserId()) >= 0) { r.isCanceled = true;
r.isCanceled = true;
}
} }
} }
} }
@@ -348,14 +261,12 @@ public class SnoozeHelper {
protected boolean cancel(int userId, String pkg) { protected boolean cancel(int userId, String pkg) {
synchronized (mLock) { synchronized (mLock) {
ArrayMap<String, NotificationRecord> records = int n = mSnoozedNotifications.size();
mSnoozedNotifications.get(getPkgKey(userId, pkg)); for (int i = 0; i < n; i++) {
if (records == null) { final NotificationRecord r = mSnoozedNotifications.valueAt(i);
return false; if (r.getSbn().getPackageName().equals(pkg) && r.getUserId() == userId) {
} r.isCanceled = true;
int N = records.size(); }
for (int i = 0; i < N; i++) {
records.valueAt(i).isCanceled = true;
} }
return true; return true;
} }
@@ -366,20 +277,17 @@ public class SnoozeHelper {
*/ */
protected void update(int userId, NotificationRecord record) { protected void update(int userId, NotificationRecord record) {
synchronized (mLock) { synchronized (mLock) {
ArrayMap<String, NotificationRecord> records = if (mSnoozedNotifications.containsKey(record.getKey())) {
mSnoozedNotifications.get(getPkgKey(userId, record.getSbn().getPackageName())); mSnoozedNotifications.put(record.getKey(), record);
if (records == null) {
return;
} }
records.put(record.getKey(), record);
} }
} }
protected void repost(String key, boolean muteOnReturn) { protected void repost(String key, boolean muteOnReturn) {
synchronized (mLock) { synchronized (mLock) {
Integer userId = mUsers.get(key); final NotificationRecord r = mSnoozedNotifications.get(key);
if (userId != null) { if (r != null) {
repost(key, userId, muteOnReturn); repost(key, r.getUserId(), muteOnReturn);
} }
} }
} }
@@ -387,43 +295,30 @@ public class SnoozeHelper {
protected void repost(String key, int userId, boolean muteOnReturn) { protected void repost(String key, int userId, boolean muteOnReturn) {
NotificationRecord record; NotificationRecord record;
synchronized (mLock) { synchronized (mLock) {
final String pkg = mPackages.remove(key); mPersistedSnoozedNotifications.remove(key);
mUsers.remove(key); mPersistedSnoozedNotificationsWithContext.remove(key);
removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotifications); record = mSnoozedNotifications.remove(key);
removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotificationsWithContext);
ArrayMap<String, NotificationRecord> records =
mSnoozedNotifications.get(getPkgKey(userId, pkg));
if (records == null) {
return;
}
record = records.remove(key);
} }
if (record != null && !record.isCanceled) { if (record != null && !record.isCanceled) {
final PendingIntent pi = createPendingIntent( final PendingIntent pi = createPendingIntent(record.getKey());
record.getSbn().getPackageName(), record.getKey(), userId);
mAm.cancel(pi); mAm.cancel(pi);
MetricsLogger.action(record.getLogMaker() MetricsLogger.action(record.getLogMaker()
.setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
.setType(MetricsProto.MetricsEvent.TYPE_OPEN)); .setType(MetricsProto.MetricsEvent.TYPE_OPEN));
mCallback.repost(userId, record, muteOnReturn); mCallback.repost(record.getUserId(), record, muteOnReturn);
} }
} }
protected void repostGroupSummary(String pkg, int userId, String groupKey) { protected void repostGroupSummary(String pkg, int userId, String groupKey) {
synchronized (mLock) { synchronized (mLock) {
ArrayMap<String, NotificationRecord> recordsByKey
= mSnoozedNotifications.get(getPkgKey(userId, pkg));
if (recordsByKey == null) {
return;
}
String groupSummaryKey = null; String groupSummaryKey = null;
int N = recordsByKey.size(); int n = mSnoozedNotifications.size();
for (int i = 0; i < N; i++) { for (int i = 0; i < n; i++) {
final NotificationRecord potentialGroupSummary = recordsByKey.valueAt(i); final NotificationRecord potentialGroupSummary = mSnoozedNotifications.valueAt(i);
if (potentialGroupSummary.getSbn().isGroup() if (potentialGroupSummary.getSbn().getPackageName().equals(pkg)
&& potentialGroupSummary.getUserId() == userId
&& potentialGroupSummary.getSbn().isGroup()
&& potentialGroupSummary.getNotification().isGroupSummary() && potentialGroupSummary.getNotification().isGroupSummary()
&& groupKey.equals(potentialGroupSummary.getGroupKey())) { && groupKey.equals(potentialGroupSummary.getGroupKey())) {
groupSummaryKey = potentialGroupSummary.getKey(); groupSummaryKey = potentialGroupSummary.getKey();
@@ -432,16 +327,14 @@ public class SnoozeHelper {
} }
if (groupSummaryKey != null) { if (groupSummaryKey != null) {
NotificationRecord record = recordsByKey.remove(groupSummaryKey); NotificationRecord record = mSnoozedNotifications.remove(groupSummaryKey);
mPackages.remove(groupSummaryKey);
mUsers.remove(groupSummaryKey);
if (record != null && !record.isCanceled) { if (record != null && !record.isCanceled) {
Runnable runnable = () -> { Runnable runnable = () -> {
MetricsLogger.action(record.getLogMaker() MetricsLogger.action(record.getLogMaker()
.setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
.setType(MetricsProto.MetricsEvent.TYPE_OPEN)); .setType(MetricsProto.MetricsEvent.TYPE_OPEN));
mCallback.repost(userId, record, false); mCallback.repost(record.getUserId(), record, false);
}; };
runnable.run(); runnable.run();
} }
@@ -451,20 +344,17 @@ public class SnoozeHelper {
protected void clearData(int userId, String pkg) { protected void clearData(int userId, String pkg) {
synchronized (mLock) { synchronized (mLock) {
ArrayMap<String, NotificationRecord> records = int n = mSnoozedNotifications.size();
mSnoozedNotifications.get(getPkgKey(userId, pkg)); for (int i = n - 1; i >= 0; i--) {
if (records == null) { final NotificationRecord record = mSnoozedNotifications.valueAt(i);
return; if (record.getUserId() == userId && record.getSbn().getPackageName().equals(pkg)) {
} mSnoozedNotifications.removeAt(i);
for (int i = records.size() - 1; i >= 0; i--) { mPersistedSnoozedNotificationsWithContext.remove(record.getKey());
final NotificationRecord r = records.removeAt(i); mPersistedSnoozedNotifications.remove(record.getKey());
if (r != null) {
mPackages.remove(r.getKey());
mUsers.remove(r.getKey());
Runnable runnable = () -> { Runnable runnable = () -> {
final PendingIntent pi = createPendingIntent(pkg, r.getKey(), userId); final PendingIntent pi = createPendingIntent(record.getKey());
mAm.cancel(pi); mAm.cancel(pi);
MetricsLogger.action(r.getLogMaker() MetricsLogger.action(record.getLogMaker()
.setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
.setType(MetricsProto.MetricsEvent.TYPE_DISMISS)); .setType(MetricsProto.MetricsEvent.TYPE_DISMISS));
}; };
@@ -474,47 +364,61 @@ public class SnoozeHelper {
} }
} }
private PendingIntent createPendingIntent(String pkg, String key, int userId) { protected void clearData(int userId) {
synchronized (mLock) {
int n = mSnoozedNotifications.size();
for (int i = n - 1; i >= 0; i--) {
final NotificationRecord record = mSnoozedNotifications.valueAt(i);
if (record.getUserId() == userId) {
mSnoozedNotifications.removeAt(i);
mPersistedSnoozedNotificationsWithContext.remove(record.getKey());
mPersistedSnoozedNotifications.remove(record.getKey());
Runnable runnable = () -> {
final PendingIntent pi = createPendingIntent(record.getKey());
mAm.cancel(pi);
MetricsLogger.action(record.getLogMaker()
.setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
.setType(MetricsProto.MetricsEvent.TYPE_DISMISS));
};
runnable.run();
}
}
}
}
private PendingIntent createPendingIntent(String key) {
return PendingIntent.getBroadcast(mContext, return PendingIntent.getBroadcast(mContext,
REQUEST_CODE_REPOST, REQUEST_CODE_REPOST,
new Intent(REPOST_ACTION) new Intent(REPOST_ACTION)
.setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME) .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
.setData(new Uri.Builder().scheme(REPOST_SCHEME).appendPath(key).build()) .setData(new Uri.Builder().scheme(REPOST_SCHEME).appendPath(key).build())
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
.putExtra(EXTRA_KEY, key) .putExtra(EXTRA_KEY, key),
.putExtra(EXTRA_USER_ID, userId),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} }
public void scheduleRepostsForPersistedNotifications(long currentTime) { public void scheduleRepostsForPersistedNotifications(long currentTime) {
synchronized (mLock) { synchronized (mLock) {
for (ArrayMap<String, Long> snoozed : mPersistedSnoozedNotifications.values()) { for (int i = 0; i < mPersistedSnoozedNotifications.size(); i++) {
for (int i = 0; i < snoozed.size(); i++) { String key = mPersistedSnoozedNotifications.keyAt(i);
String key = snoozed.keyAt(i); Long time = mPersistedSnoozedNotifications.valueAt(i);
Long time = snoozed.valueAt(i); if (time != null && time > currentTime) {
String pkg = mPackages.get(key); scheduleRepostAtTime(key, time);
Integer userId = mUsers.get(key);
if (time == null || pkg == null || userId == null) {
Slog.w(TAG, "data out of sync: " + time + "|" + pkg + "|" + userId);
continue;
}
if (time != null && time > currentTime) {
scheduleRepostAtTime(pkg, key, userId, time);
}
} }
} }
} }
} }
private void scheduleRepost(String pkg, String key, int userId, long duration) { private void scheduleRepost(String key, long duration) {
scheduleRepostAtTime(pkg, key, userId, System.currentTimeMillis() + duration); scheduleRepostAtTime(key, System.currentTimeMillis() + duration);
} }
private void scheduleRepostAtTime(String pkg, String key, int userId, long time) { private void scheduleRepostAtTime(String key, long time) {
Runnable runnable = () -> { Runnable runnable = () -> {
final long identity = Binder.clearCallingIdentity(); final long identity = Binder.clearCallingIdentity();
try { try {
final PendingIntent pi = createPendingIntent(pkg, key, userId); final PendingIntent pi = createPendingIntent(key);
mAm.cancel(pi); mAm.cancel(pi);
if (DEBUG) Slog.d(TAG, "Scheduling evaluate for " + new Date(time)); if (DEBUG) Slog.d(TAG, "Scheduling evaluate for " + new Date(time));
mAm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pi); mAm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pi);
@@ -528,37 +432,14 @@ public class SnoozeHelper {
public void dump(PrintWriter pw, NotificationManagerService.DumpFilter filter) { public void dump(PrintWriter pw, NotificationManagerService.DumpFilter filter) {
synchronized (mLock) { synchronized (mLock) {
pw.println("\n Snoozed notifications:"); pw.println("\n Snoozed notifications:");
for (String userPkgKey : mSnoozedNotifications.keySet()) { for (String key : mSnoozedNotifications.keySet()) {
pw.print(INDENT); pw.print(INDENT);
pw.println("key: " + userPkgKey); pw.println("key: " + key);
ArrayMap<String, NotificationRecord> snoozedRecords =
mSnoozedNotifications.get(userPkgKey);
Set<String> snoozedKeys = snoozedRecords.keySet();
for (String key : snoozedKeys) {
pw.print(INDENT);
pw.print(INDENT);
pw.print(INDENT);
pw.println(key);
}
} }
pw.println("\n Pending snoozed notifications"); pw.println("\n Pending snoozed notifications");
for (String userPkgKey : mPersistedSnoozedNotifications.keySet()) { for (String key : mPersistedSnoozedNotifications.keySet()) {
pw.print(INDENT); pw.print(INDENT);
pw.println("key: " + userPkgKey); pw.println("key: " + key + " until: " + mPersistedSnoozedNotifications.get(key));
ArrayMap<String, Long> snoozedRecords =
mPersistedSnoozedNotifications.get(userPkgKey);
if (snoozedRecords == null) {
continue;
}
Set<String> snoozedKeys = snoozedRecords.keySet();
for (String key : snoozedKeys) {
pw.print(INDENT);
pw.print(INDENT);
pw.print(INDENT);
pw.print(key);
pw.print(INDENT);
pw.println(snoozedRecords.get(key));
}
} }
} }
} }
@@ -589,37 +470,22 @@ public class SnoozeHelper {
void insert(T t) throws IOException; void insert(T t) throws IOException;
} }
private <T> void writeXml(TypedXmlSerializer out, private <T> void writeXml(TypedXmlSerializer out, ArrayMap<String, T> targets, String tag,
ArrayMap<String, ArrayMap<String, T>> targets, String tag, Inserter<T> attributeInserter) throws IOException {
Inserter<T> attributeInserter) for (int j = 0; j < targets.size(); j++) {
throws IOException { String key = targets.keyAt(j);
final int M = targets.size();
for (int i = 0; i < M; i++) {
// T is a String (snoozed until context) or Long (snoozed until time) // T is a String (snoozed until context) or Long (snoozed until time)
ArrayMap<String, T> keyToValue = targets.valueAt(i); T value = targets.valueAt(j);
for (int j = 0; j < keyToValue.size(); j++) {
String key = keyToValue.keyAt(j);
T value = keyToValue.valueAt(j);
String pkg = mPackages.get(key);
Integer userId = mUsers.get(key);
if (pkg == null || userId == null) { out.startTag(null, tag);
Slog.w(TAG, "pkg " + pkg + " or user " + userId + " missing for " + key);
continue;
}
out.startTag(null, tag); attributeInserter.insert(value);
attributeInserter.insert(value); out.attributeInt(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL,
XML_SNOOZED_NOTIFICATION_VERSION);
out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, key);
out.attributeInt(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL, out.endTag(null, tag);
XML_SNOOZED_NOTIFICATION_VERSION);
out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, key);
out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, pkg);
out.attributeInt(null, XML_SNOOZED_NOTIFICATION_USER_ID, userId);
out.endTag(null, tag);
}
} }
} }
@@ -639,16 +505,12 @@ public class SnoozeHelper {
== XML_SNOOZED_NOTIFICATION_VERSION) { == XML_SNOOZED_NOTIFICATION_VERSION) {
try { try {
final String key = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_KEY); final String key = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_KEY);
final String pkg = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_PKG);
final int userId = parser.getAttributeInt(
null, XML_SNOOZED_NOTIFICATION_USER_ID, UserHandle.USER_ALL);
if (tag.equals(XML_SNOOZED_NOTIFICATION)) { if (tag.equals(XML_SNOOZED_NOTIFICATION)) {
final Long time = parser.getAttributeLong( final Long time = parser.getAttributeLong(
null, XML_SNOOZED_NOTIFICATION_TIME, 0); null, XML_SNOOZED_NOTIFICATION_TIME, 0);
if (time > currentTime) { //only read new stuff if (time > currentTime) { //only read new stuff
synchronized (mLock) { synchronized (mLock) {
storeRecordLocked( mPersistedSnoozedNotifications.put(key, time);
pkg, key, userId, mPersistedSnoozedNotifications, time);
} }
} }
} }
@@ -656,9 +518,7 @@ public class SnoozeHelper {
final String creationId = parser.getAttributeValue( final String creationId = parser.getAttributeValue(
null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID); null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID);
synchronized (mLock) { synchronized (mLock) {
storeRecordLocked( mPersistedSnoozedNotificationsWithContext.put(key, creationId);
pkg, key, userId, mPersistedSnoozedNotificationsWithContext,
creationId);
} }
} }
} catch (Exception e) { } catch (Exception e) {

View File

@@ -20,6 +20,7 @@ import static com.android.server.notification.SnoozeHelper.EXTRA_KEY;
import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
@@ -39,7 +40,6 @@ import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.os.SystemClock;
import android.os.UserHandle; import android.os.UserHandle;
import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.SmallTest;
@@ -50,7 +50,6 @@ import android.util.Xml;
import androidx.test.runner.AndroidJUnit4; import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.FastXmlSerializer;
import com.android.server.UiServiceTestCase; import com.android.server.UiServiceTestCase;
import com.android.server.pm.PackageManagerService; import com.android.server.pm.PackageManagerService;
@@ -60,9 +59,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
@@ -197,18 +194,6 @@ public class SnoozeHelperTest extends UiServiceTestCase {
systemUser, "pkg", r.getKey()).longValue()); systemUser, "pkg", r.getKey()).longValue());
} }
@Test
public void testCleanupContextShouldRemovePersistedRecord() {
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
mSnoozeHelper.snooze(r, "context");
mSnoozeHelper.cleanupPersistedContext(r.getSbn().getKey());
assertNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification(
r.getUser().getIdentifier(),
r.getSbn().getPackageName(),
r.getSbn().getKey()
));
}
@Test @Test
public void testReadNoneSnoozedNotification() throws XmlPullParserException, public void testReadNoneSnoozedNotification() throws XmlPullParserException,
IOException, InterruptedException { IOException, InterruptedException {
@@ -219,8 +204,9 @@ public class SnoozeHelperTest extends UiServiceTestCase {
assertEquals("should see a zero value for unsnoozed notification", assertEquals("should see a zero value for unsnoozed notification",
0L, 0L,
mSnoozeHelper.getSnoozeTimeForUnpostedNotification( mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
UserHandle.SYSTEM.getIdentifier(), UserHandle.SYSTEM.getIdentifier(), "not_my_package",
"not_my_package", r.getKey()).longValue()); getNotificationRecord("not_my_package", 1, "one",
UserHandle.SYSTEM).getKey()).longValue());
} }
@Test @Test
@@ -592,7 +578,7 @@ public class SnoozeHelperTest extends UiServiceTestCase {
} }
@Test @Test
public void testClearData() { public void testClearData_userPackage() {
// snooze 2 from same package // snooze 2 from same package
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM); NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
@@ -615,17 +601,72 @@ public class SnoozeHelperTest extends UiServiceTestCase {
verify(mAm, times(4)).cancel(any(PendingIntent.class)); verify(mAm, times(4)).cancel(any(PendingIntent.class));
} }
@Test
public void testClearData_user() {
// snooze 2 from same package
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
NotificationRecord r2 = getNotificationRecord("pkg2", 2, "two", UserHandle.SYSTEM);
NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
NotificationRecord r4 = getNotificationRecord("pkg", 2, "two", UserHandle.ALL);
mSnoozeHelper.snooze(r, 1000);
mSnoozeHelper.snooze(r2, 1000);
mSnoozeHelper.snooze(r3, "until");
mSnoozeHelper.snooze(r4, "until");
assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_ALL, r4.getSbn().getPackageName(), r4.getKey()));
// clear data
mSnoozeHelper.clearData(UserHandle.USER_SYSTEM);
// nothing in USER_SYSTEM snoozed; alarms canceled
assertFalse(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
assertFalse(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
assertFalse(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, r4.getSbn().getPackageName(), r4.getKey()));
assertNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification(
r3.getUser().getIdentifier(), r3.getSbn().getPackageName(),
r3.getSbn().getKey()));
assertNotNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification(
r4.getUser().getIdentifier(), r4.getSbn().getPackageName(),
r4.getSbn().getKey()));
assertEquals(0L, mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
r.getUser().getIdentifier(), r.getSbn().getPackageName(),
r.getSbn().getKey()).longValue());
assertEquals(0L, mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
r2.getUser().getIdentifier(), r2.getSbn().getPackageName(),
r2.getSbn().getKey()).longValue());
// 2 for initial timed-snoozes, once each for canceling the USER_SYSTEM snoozes
verify(mAm, times(5)).cancel(any(PendingIntent.class));
}
@Test @Test
public void testClearData_otherRecordsUntouched() { public void testClearData_otherRecordsUntouched() {
// 2 packages, 2 users // 2 packages, 2 users
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
NotificationRecord rb = getNotificationRecord("pkg", 1, "oneb", UserHandle.SYSTEM);
NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.ALL); NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.ALL);
NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM); NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
mSnoozeHelper.snooze(r, 1000); mSnoozeHelper.snooze(r, 1000);
mSnoozeHelper.snooze(rb, "until");
mSnoozeHelper.snooze(r2, 1000); mSnoozeHelper.snooze(r2, 1000);
mSnoozeHelper.snooze(r3, 1000); mSnoozeHelper.snooze(r3, 1000);
assertTrue(mSnoozeHelper.isSnoozed( assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, rb.getSbn().getPackageName(), rb.getKey()));
assertTrue(mSnoozeHelper.isSnoozed( assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_ALL, r2.getSbn().getPackageName(), r2.getKey())); UserHandle.USER_ALL, r2.getSbn().getPackageName(), r2.getKey()));
assertTrue(mSnoozeHelper.isSnoozed( assertTrue(mSnoozeHelper.isSnoozed(
@@ -636,12 +677,22 @@ public class SnoozeHelperTest extends UiServiceTestCase {
assertFalse(mSnoozeHelper.isSnoozed( assertFalse(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
assertFalse(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, rb.getSbn().getPackageName(), rb.getKey()));
assertTrue(mSnoozeHelper.isSnoozed( assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_ALL, r2.getSbn().getPackageName(), r2.getKey())); UserHandle.USER_ALL, r2.getSbn().getPackageName(), r2.getKey()));
assertTrue(mSnoozeHelper.isSnoozed( assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey())); UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
assertNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification(
rb.getUser().getIdentifier(), rb.getSbn().getPackageName(),
rb.getSbn().getKey()));
assertEquals(0L, mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
r.getUser().getIdentifier(), r.getSbn().getPackageName(),
r.getSbn().getKey()).longValue());
// once for each initial snooze, once for canceling one snooze // once for each initial snooze, once for canceling one snooze
verify(mAm, times(4)).cancel(any(PendingIntent.class)); verify(mAm, times(5)).cancel(any(PendingIntent.class));
} }
private NotificationRecord getNotificationRecord(String pkg, int id, String tag, private NotificationRecord getNotificationRecord(String pkg, int id, String tag,