| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.server.notification; |
| |
| import android.annotation.NonNull; |
| import android.annotation.UserIdInt; |
| import android.app.AlarmManager; |
| import android.app.PendingIntent; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.net.Uri; |
| import android.os.Binder; |
| import android.os.SystemClock; |
| import android.os.UserHandle; |
| import android.service.notification.StatusBarNotification; |
| import android.util.ArrayMap; |
| import android.util.IntArray; |
| import android.util.Log; |
| import android.util.Slog; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.logging.MetricsLogger; |
| import com.android.internal.logging.nano.MetricsProto; |
| import com.android.internal.util.XmlUtils; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| import org.xmlpull.v1.XmlSerializer; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| /** |
| * NotificationManagerService helper for handling snoozed notifications. |
| */ |
| public class SnoozeHelper { |
| public static final String XML_SNOOZED_NOTIFICATION_VERSION = "1"; |
| |
| protected static final String XML_TAG_NAME = "snoozed-notifications"; |
| |
| private static final String XML_SNOOZED_NOTIFICATION = "notification"; |
| 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"; |
| //the time the snoozed notification should be reposted |
| private static final String XML_SNOOZED_NOTIFICATION_TIME = "time"; |
| private static final String XML_SNOOZED_NOTIFICATION_CONTEXT_ID = "id"; |
| private static final String XML_SNOOZED_NOTIFICATION_VERSION_LABEL = "version"; |
| |
| |
| private static final String TAG = "SnoozeHelper"; |
| private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); |
| private static final String INDENT = " "; |
| |
| private static final String REPOST_ACTION = SnoozeHelper.class.getSimpleName() + ".EVALUATE"; |
| private static final int REQUEST_CODE_REPOST = 1; |
| private static final String REPOST_SCHEME = "repost"; |
| static final String EXTRA_KEY = "key"; |
| private static final String EXTRA_USER_ID = "userId"; |
| |
| private final Context mContext; |
| private AlarmManager mAm; |
| private final ManagedServices.UserProfiles mUserProfiles; |
| |
| // User id | package name : notification key : record. |
| private ArrayMap<String, ArrayMap<String, NotificationRecord>> |
| mSnoozedNotifications = new ArrayMap<>(); |
| // User id | package name : notification key : time-milliseconds . |
| // 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 |
| private final ArrayMap<String, ArrayMap<String, Long>> |
| mPersistedSnoozedNotifications = new ArrayMap<>(); |
| // User id | package name : notification key : creation ID . |
| // This member stores persisted snoozed notification trigger context for the assistant |
| // it persists through reboots. |
| // It should have the notifications that haven't expired or re-posted yet |
| private final ArrayMap<String, ArrayMap<String, String>> |
| 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 final Object mLock = new Object(); |
| |
| public SnoozeHelper(Context context, Callback callback, |
| ManagedServices.UserProfiles userProfiles) { |
| mContext = context; |
| IntentFilter filter = new IntentFilter(REPOST_ACTION); |
| filter.addDataScheme(REPOST_SCHEME); |
| mContext.registerReceiver(mBroadcastReceiver, filter); |
| mAm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); |
| mCallback = callback; |
| 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); |
| } |
| } |
| |
| @NonNull |
| protected Long getSnoozeTimeForUnpostedNotification(int userId, String pkg, String key) { |
| Long time = null; |
| synchronized (mLock) { |
| ArrayMap<String, Long> snoozed = |
| mPersistedSnoozedNotifications.get(getPkgKey(userId, pkg)); |
| if (snoozed != null) { |
| time = snoozed.get(key); |
| } |
| } |
| if (time == null) { |
| time = 0L; |
| } |
| return time; |
| } |
| |
| protected String getSnoozeContextForUnpostedNotification(int userId, String pkg, String key) { |
| synchronized (mLock) { |
| ArrayMap<String, String> snoozed = |
| mPersistedSnoozedNotificationsWithContext.get(getPkgKey(userId, pkg)); |
| if (snoozed != null) { |
| return snoozed.get(key); |
| } |
| } |
| return null; |
| } |
| |
| protected boolean isSnoozed(int userId, String pkg, String key) { |
| synchronized (mLock) { |
| return mSnoozedNotifications.containsKey(getPkgKey(userId, pkg)) |
| && mSnoozedNotifications.get(getPkgKey(userId, pkg)).containsKey(key); |
| } |
| } |
| |
| protected Collection<NotificationRecord> getSnoozed(int userId, String pkg) { |
| synchronized (mLock) { |
| if (mSnoozedNotifications.containsKey(getPkgKey(userId, pkg))) { |
| return mSnoozedNotifications.get(getPkgKey(userId, pkg)).values(); |
| } |
| } |
| return Collections.EMPTY_LIST; |
| } |
| |
| @NonNull |
| ArrayList<NotificationRecord> getNotifications(String pkg, |
| String groupKey, Integer userId) { |
| ArrayList<NotificationRecord> records = new ArrayList<>(); |
| synchronized (mLock) { |
| ArrayMap<String, NotificationRecord> allRecords = |
| mSnoozedNotifications.get(getPkgKey(userId, pkg)); |
| if (allRecords != null) { |
| for (int i = 0; i < allRecords.size(); i++) { |
| NotificationRecord r = allRecords.valueAt(i); |
| String currentGroupKey = r.getSbn().getGroup(); |
| if (Objects.equals(currentGroupKey, groupKey)) { |
| records.add(r); |
| } |
| } |
| } |
| } |
| return records; |
| } |
| |
| protected NotificationRecord getNotification(String key) { |
| synchronized (mLock) { |
| if (!mUsers.containsKey(key) || !mPackages.containsKey(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() { |
| synchronized (mLock) { |
| // caller filters records based on the current user profiles and listener access, so just |
| // return everything |
| List<NotificationRecord> snoozed = new ArrayList<>(); |
| for (String userPkgKey : mSnoozedNotifications.keySet()) { |
| ArrayMap<String, NotificationRecord> snoozedRecords = |
| mSnoozedNotifications.get(userPkgKey); |
| snoozed.addAll(snoozedRecords.values()); |
| } |
| return snoozed; |
| } |
| } |
| |
| /** |
| * Snoozes a notification and schedules an alarm to repost at that time. |
| */ |
| protected void snooze(NotificationRecord record, long duration) { |
| String pkg = record.getSbn().getPackageName(); |
| String key = record.getKey(); |
| int userId = record.getUser().getIdentifier(); |
| |
| snooze(record); |
| scheduleRepost(pkg, key, userId, duration); |
| Long activateAt = System.currentTimeMillis() + duration; |
| synchronized (mLock) { |
| storeRecordLocked(pkg, key, userId, mPersistedSnoozedNotifications, activateAt); |
| } |
| } |
| |
| /** |
| * Records a snoozed notification. |
| */ |
| protected void snooze(NotificationRecord record, String contextId) { |
| int userId = record.getUser().getIdentifier(); |
| if (contextId != null) { |
| synchronized (mLock) { |
| storeRecordLocked(record.getSbn().getPackageName(), record.getKey(), |
| userId, mPersistedSnoozedNotificationsWithContext, contextId); |
| } |
| } |
| snooze(record); |
| } |
| |
| private void snooze(NotificationRecord record) { |
| int userId = record.getUser().getIdentifier(); |
| if (DEBUG) { |
| Slog.d(TAG, "Snoozing " + record.getKey()); |
| } |
| synchronized (mLock) { |
| storeRecordLocked(record.getSbn().getPackageName(), record.getKey(), |
| 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) { |
| synchronized (mLock) { |
| ArrayMap<String, NotificationRecord> recordsForPkg = |
| mSnoozedNotifications.get(getPkgKey(userId, pkg)); |
| if (recordsForPkg != null) { |
| final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet(); |
| for (Map.Entry<String, NotificationRecord> record : records) { |
| final StatusBarNotification sbn = record.getValue().getSbn(); |
| if (Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) { |
| record.getValue().isCanceled = true; |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| protected void cancel(int userId, boolean includeCurrentProfiles) { |
| synchronized (mLock) { |
| if (mSnoozedNotifications.size() == 0) { |
| return; |
| } |
| IntArray userIds = new IntArray(); |
| userIds.add(userId); |
| if (includeCurrentProfiles) { |
| userIds = mUserProfiles.getCurrentProfileIds(); |
| } |
| for (ArrayMap<String, NotificationRecord> snoozedRecords : mSnoozedNotifications.values()) { |
| for (NotificationRecord r : snoozedRecords.values()) { |
| if (userIds.binarySearch(r.getUserId()) >= 0) { |
| r.isCanceled = true; |
| } |
| } |
| } |
| } |
| } |
| |
| protected boolean cancel(int userId, String pkg) { |
| synchronized (mLock) { |
| ArrayMap<String, NotificationRecord> records = |
| mSnoozedNotifications.get(getPkgKey(userId, pkg)); |
| if (records == null) { |
| return false; |
| } |
| int N = records.size(); |
| for (int i = 0; i < N; i++) { |
| records.valueAt(i).isCanceled = true; |
| } |
| return true; |
| } |
| } |
| |
| /** |
| * Updates the notification record so the most up to date information is shown on re-post. |
| */ |
| protected void update(int userId, NotificationRecord record) { |
| synchronized (mLock) { |
| ArrayMap<String, NotificationRecord> records = |
| mSnoozedNotifications.get(getPkgKey(userId, record.getSbn().getPackageName())); |
| if (records == null) { |
| return; |
| } |
| records.put(record.getKey(), record); |
| } |
| } |
| |
| protected void repost(String key, boolean muteOnReturn) { |
| synchronized (mLock) { |
| Integer userId = mUsers.get(key); |
| if (userId != null) { |
| repost(key, userId, muteOnReturn); |
| } |
| } |
| } |
| |
| protected void repost(String key, int userId, boolean muteOnReturn) { |
| NotificationRecord record; |
| synchronized (mLock) { |
| final String pkg = mPackages.remove(key); |
| mUsers.remove(key); |
| removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotifications); |
| 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) { |
| final PendingIntent pi = createPendingIntent( |
| record.getSbn().getPackageName(), record.getKey(), userId); |
| mAm.cancel(pi); |
| MetricsLogger.action(record.getLogMaker() |
| .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) |
| .setType(MetricsProto.MetricsEvent.TYPE_OPEN)); |
| mCallback.repost(userId, record, muteOnReturn); |
| } |
| } |
| |
| protected void repostGroupSummary(String pkg, int userId, String groupKey) { |
| synchronized (mLock) { |
| ArrayMap<String, NotificationRecord> recordsByKey |
| = mSnoozedNotifications.get(getPkgKey(userId, pkg)); |
| if (recordsByKey == null) { |
| return; |
| } |
| |
| String groupSummaryKey = null; |
| int N = recordsByKey.size(); |
| for (int i = 0; i < N; i++) { |
| final NotificationRecord potentialGroupSummary = recordsByKey.valueAt(i); |
| if (potentialGroupSummary.getSbn().isGroup() |
| && potentialGroupSummary.getNotification().isGroupSummary() |
| && groupKey.equals(potentialGroupSummary.getGroupKey())) { |
| groupSummaryKey = potentialGroupSummary.getKey(); |
| break; |
| } |
| } |
| |
| if (groupSummaryKey != null) { |
| NotificationRecord record = recordsByKey.remove(groupSummaryKey); |
| mPackages.remove(groupSummaryKey); |
| mUsers.remove(groupSummaryKey); |
| |
| if (record != null && !record.isCanceled) { |
| Runnable runnable = () -> { |
| MetricsLogger.action(record.getLogMaker() |
| .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) |
| .setType(MetricsProto.MetricsEvent.TYPE_OPEN)); |
| mCallback.repost(userId, record, false); |
| }; |
| runnable.run(); |
| } |
| } |
| } |
| } |
| |
| protected void clearData(int userId, String pkg) { |
| synchronized (mLock) { |
| ArrayMap<String, NotificationRecord> records = |
| mSnoozedNotifications.get(getPkgKey(userId, pkg)); |
| if (records == null) { |
| return; |
| } |
| for (int i = records.size() - 1; i >= 0; i--) { |
| final NotificationRecord r = records.removeAt(i); |
| if (r != null) { |
| mPackages.remove(r.getKey()); |
| mUsers.remove(r.getKey()); |
| Runnable runnable = () -> { |
| final PendingIntent pi = createPendingIntent(pkg, r.getKey(), userId); |
| mAm.cancel(pi); |
| MetricsLogger.action(r.getLogMaker() |
| .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) |
| .setType(MetricsProto.MetricsEvent.TYPE_DISMISS)); |
| }; |
| runnable.run(); |
| } |
| } |
| } |
| } |
| |
| private PendingIntent createPendingIntent(String pkg, String key, int userId) { |
| return PendingIntent.getBroadcast(mContext, |
| REQUEST_CODE_REPOST, |
| new Intent(REPOST_ACTION) |
| .setData(new Uri.Builder().scheme(REPOST_SCHEME).appendPath(key).build()) |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) |
| .putExtra(EXTRA_KEY, key) |
| .putExtra(EXTRA_USER_ID, userId), |
| PendingIntent.FLAG_UPDATE_CURRENT); |
| } |
| |
| public void scheduleRepostsForPersistedNotifications(long currentTime) { |
| synchronized (mLock) { |
| for (ArrayMap<String, Long> snoozed : mPersistedSnoozedNotifications.values()) { |
| for (int i = 0; i < snoozed.size(); i++) { |
| String key = snoozed.keyAt(i); |
| Long time = snoozed.valueAt(i); |
| String pkg = mPackages.get(key); |
| 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) { |
| scheduleRepostAtTime(pkg, key, userId, System.currentTimeMillis() + duration); |
| } |
| |
| private void scheduleRepostAtTime(String pkg, String key, int userId, long time) { |
| Runnable runnable = () -> { |
| long identity = Binder.clearCallingIdentity(); |
| try { |
| final PendingIntent pi = createPendingIntent(pkg, key, userId); |
| mAm.cancel(pi); |
| if (DEBUG) Slog.d(TAG, "Scheduling evaluate for " + new Date(time)); |
| mAm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pi); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| }; |
| runnable.run(); |
| } |
| |
| public void dump(PrintWriter pw, NotificationManagerService.DumpFilter filter) { |
| synchronized (mLock) { |
| pw.println("\n Snoozed notifications:"); |
| for (String userPkgKey : mSnoozedNotifications.keySet()) { |
| pw.print(INDENT); |
| pw.println("key: " + userPkgKey); |
| 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"); |
| for (String userPkgKey : mPersistedSnoozedNotifications.keySet()) { |
| pw.print(INDENT); |
| pw.println("key: " + userPkgKey); |
| 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)); |
| } |
| } |
| } |
| } |
| |
| protected void writeXml(XmlSerializer out) throws IOException { |
| synchronized (mLock) { |
| final long currentTime = System.currentTimeMillis(); |
| out.startTag(null, XML_TAG_NAME); |
| writeXml(out, mPersistedSnoozedNotifications, XML_SNOOZED_NOTIFICATION, |
| value -> { |
| if (value < currentTime) { |
| return; |
| } |
| out.attribute(null, XML_SNOOZED_NOTIFICATION_TIME, |
| value.toString()); |
| }); |
| writeXml(out, mPersistedSnoozedNotificationsWithContext, |
| XML_SNOOZED_NOTIFICATION_CONTEXT, |
| value -> { |
| out.attribute(null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID, |
| value); |
| }); |
| out.endTag(null, XML_TAG_NAME); |
| } |
| } |
| |
| private interface Inserter<T> { |
| void insert(T t) throws IOException; |
| } |
| |
| private <T> void writeXml(XmlSerializer out, |
| ArrayMap<String, ArrayMap<String, T>> targets, String tag, |
| Inserter<T> attributeInserter) |
| throws IOException { |
| final int M = targets.size(); |
| for (int i = 0; i < M; i++) { |
| // T is a String (snoozed until context) or Long (snoozed until time) |
| ArrayMap<String, T> keyToValue = targets.valueAt(i); |
| 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) { |
| Slog.w(TAG, "pkg " + pkg + " or user " + userId + " missing for " + key); |
| continue; |
| } |
| |
| out.startTag(null, tag); |
| |
| attributeInserter.insert(value); |
| |
| out.attribute(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL, |
| XML_SNOOZED_NOTIFICATION_VERSION); |
| out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, key); |
| |
| |
| out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, pkg); |
| out.attribute(null, XML_SNOOZED_NOTIFICATION_USER_ID, |
| String.valueOf(userId)); |
| |
| out.endTag(null, tag); |
| } |
| } |
| } |
| |
| protected void readXml(XmlPullParser parser, long currentTime) |
| throws XmlPullParserException, IOException { |
| int type; |
| while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { |
| String tag = parser.getName(); |
| if (type == XmlPullParser.END_TAG |
| && XML_TAG_NAME.equals(tag)) { |
| break; |
| } |
| if (type == XmlPullParser.START_TAG |
| && (XML_SNOOZED_NOTIFICATION.equals(tag) |
| || tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT)) |
| && parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL) |
| .equals(XML_SNOOZED_NOTIFICATION_VERSION)) { |
| try { |
| final String key = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_KEY); |
| final String pkg = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_PKG); |
| final int userId = XmlUtils.readIntAttribute( |
| parser, XML_SNOOZED_NOTIFICATION_USER_ID, UserHandle.USER_ALL); |
| if (tag.equals(XML_SNOOZED_NOTIFICATION)) { |
| final Long time = XmlUtils.readLongAttribute( |
| parser, XML_SNOOZED_NOTIFICATION_TIME, 0); |
| if (time > currentTime) { //only read new stuff |
| synchronized (mLock) { |
| storeRecordLocked( |
| pkg, key, userId, mPersistedSnoozedNotifications, time); |
| } |
| } |
| } |
| if (tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT)) { |
| final String creationId = parser.getAttributeValue( |
| null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID); |
| synchronized (mLock) { |
| storeRecordLocked( |
| pkg, key, userId, mPersistedSnoozedNotificationsWithContext, |
| creationId); |
| } |
| } |
| } catch (Exception e) { |
| Slog.e(TAG, "Exception in reading snooze data from policy xml", e); |
| } |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| void setAlarmManager(AlarmManager am) { |
| mAm = am; |
| } |
| |
| protected interface Callback { |
| void repost(int userId, NotificationRecord r, boolean muteOnReturn); |
| } |
| |
| private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (DEBUG) { |
| Slog.d(TAG, "Reposting notification"); |
| } |
| if (REPOST_ACTION.equals(intent.getAction())) { |
| repost(intent.getStringExtra(EXTRA_KEY), intent.getIntExtra(EXTRA_USER_ID, |
| UserHandle.USER_SYSTEM), false); |
| } |
| } |
| }; |
| } |