• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.server.notification;
17 
18 import android.annotation.NonNull;
19 import android.annotation.UserIdInt;
20 import android.app.AlarmManager;
21 import android.app.PendingIntent;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.net.Uri;
27 import android.os.Binder;
28 import android.os.SystemClock;
29 import android.os.UserHandle;
30 import android.service.notification.StatusBarNotification;
31 import android.util.ArrayMap;
32 import android.util.IntArray;
33 import android.util.Log;
34 import android.util.Slog;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.internal.logging.MetricsLogger;
38 import com.android.internal.logging.nano.MetricsProto;
39 import com.android.internal.util.XmlUtils;
40 import com.android.server.pm.PackageManagerService;
41 
42 import org.xmlpull.v1.XmlPullParser;
43 import org.xmlpull.v1.XmlPullParserException;
44 import org.xmlpull.v1.XmlSerializer;
45 
46 import java.io.IOException;
47 import java.io.PrintWriter;
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.Collections;
51 import java.util.Date;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.Objects;
55 import java.util.Set;
56 
57 /**
58  * NotificationManagerService helper for handling snoozed notifications.
59  */
60 public class SnoozeHelper {
61     public static final String XML_SNOOZED_NOTIFICATION_VERSION = "1";
62 
63     protected static final String XML_TAG_NAME = "snoozed-notifications";
64 
65     private static final String XML_SNOOZED_NOTIFICATION = "notification";
66     private static final String XML_SNOOZED_NOTIFICATION_CONTEXT = "context";
67     private static final String XML_SNOOZED_NOTIFICATION_PKG = "pkg";
68     private static final String XML_SNOOZED_NOTIFICATION_USER_ID = "user-id";
69     private static final String XML_SNOOZED_NOTIFICATION_KEY = "key";
70     //the time the snoozed notification should be reposted
71     private static final String XML_SNOOZED_NOTIFICATION_TIME = "time";
72     private static final String XML_SNOOZED_NOTIFICATION_CONTEXT_ID = "id";
73     private static final String XML_SNOOZED_NOTIFICATION_VERSION_LABEL = "version";
74 
75 
76     private static final String TAG = "SnoozeHelper";
77     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
78     private static final String INDENT = "    ";
79 
80     private static final String REPOST_ACTION = SnoozeHelper.class.getSimpleName() + ".EVALUATE";
81     private static final int REQUEST_CODE_REPOST = 1;
82     private static final String REPOST_SCHEME = "repost";
83     static final String EXTRA_KEY = "key";
84     private static final String EXTRA_USER_ID = "userId";
85 
86     private final Context mContext;
87     private AlarmManager mAm;
88     private final ManagedServices.UserProfiles mUserProfiles;
89 
90     // User id | package name : notification key : record.
91     private ArrayMap<String, ArrayMap<String, NotificationRecord>>
92             mSnoozedNotifications = new ArrayMap<>();
93     // User id | package name : notification key : time-milliseconds .
94     // This member stores persisted snoozed notification trigger times. it persists through reboots
95     // It should have the notifications that haven't expired or re-posted yet
96     private final ArrayMap<String, ArrayMap<String, Long>>
97             mPersistedSnoozedNotifications = new ArrayMap<>();
98     // User id | package name : notification key : creation ID .
99     // This member stores persisted snoozed notification trigger context for the assistant
100     // it persists through reboots.
101     // It should have the notifications that haven't expired or re-posted yet
102     private final ArrayMap<String, ArrayMap<String, String>>
103             mPersistedSnoozedNotificationsWithContext = new ArrayMap<>();
104     // notification key : package.
105     private ArrayMap<String, String> mPackages = new ArrayMap<>();
106     // key : userId
107     private ArrayMap<String, Integer> mUsers = new ArrayMap<>();
108     private Callback mCallback;
109 
110     private final Object mLock = new Object();
111 
SnoozeHelper(Context context, Callback callback, ManagedServices.UserProfiles userProfiles)112     public SnoozeHelper(Context context, Callback callback,
113             ManagedServices.UserProfiles userProfiles) {
114         mContext = context;
115         IntentFilter filter = new IntentFilter(REPOST_ACTION);
116         filter.addDataScheme(REPOST_SCHEME);
117         mContext.registerReceiver(mBroadcastReceiver, filter);
118         mAm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
119         mCallback = callback;
120         mUserProfiles = userProfiles;
121     }
122 
getPkgKey(@serIdInt int userId, String pkg)123     private String getPkgKey(@UserIdInt int userId, String pkg) {
124         return userId + "|" + pkg;
125     }
126 
cleanupPersistedContext(String key)127     void cleanupPersistedContext(String key){
128         synchronized (mLock) {
129             int userId = mUsers.get(key);
130             String pkg = mPackages.get(key);
131             removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotificationsWithContext);
132         }
133     }
134 
135     @NonNull
getSnoozeTimeForUnpostedNotification(int userId, String pkg, String key)136     protected Long getSnoozeTimeForUnpostedNotification(int userId, String pkg, String key) {
137         Long time = null;
138         synchronized (mLock) {
139            ArrayMap<String, Long> snoozed =
140                    mPersistedSnoozedNotifications.get(getPkgKey(userId, pkg));
141            if (snoozed != null) {
142                time = snoozed.get(key);
143            }
144         }
145         if (time == null) {
146             time = 0L;
147         }
148         return time;
149     }
150 
getSnoozeContextForUnpostedNotification(int userId, String pkg, String key)151     protected String getSnoozeContextForUnpostedNotification(int userId, String pkg, String key) {
152         synchronized (mLock) {
153             ArrayMap<String, String> snoozed =
154                     mPersistedSnoozedNotificationsWithContext.get(getPkgKey(userId, pkg));
155             if (snoozed != null) {
156                 return snoozed.get(key);
157             }
158         }
159         return null;
160     }
161 
isSnoozed(int userId, String pkg, String key)162     protected boolean isSnoozed(int userId, String pkg, String key) {
163         synchronized (mLock) {
164             return mSnoozedNotifications.containsKey(getPkgKey(userId, pkg))
165                     && mSnoozedNotifications.get(getPkgKey(userId, pkg)).containsKey(key);
166         }
167     }
168 
getSnoozed(int userId, String pkg)169     protected Collection<NotificationRecord> getSnoozed(int userId, String pkg) {
170         synchronized (mLock) {
171             if (mSnoozedNotifications.containsKey(getPkgKey(userId, pkg))) {
172                 return mSnoozedNotifications.get(getPkgKey(userId, pkg)).values();
173             }
174         }
175         return Collections.EMPTY_LIST;
176     }
177 
178     @NonNull
getNotifications(String pkg, String groupKey, Integer userId)179     ArrayList<NotificationRecord> getNotifications(String pkg,
180             String groupKey, Integer userId) {
181         ArrayList<NotificationRecord> records =  new ArrayList<>();
182         synchronized (mLock) {
183             ArrayMap<String, NotificationRecord> allRecords =
184                     mSnoozedNotifications.get(getPkgKey(userId, pkg));
185             if (allRecords != null) {
186                 for (int i = 0; i < allRecords.size(); i++) {
187                     NotificationRecord r = allRecords.valueAt(i);
188                     String currentGroupKey = r.getSbn().getGroup();
189                     if (Objects.equals(currentGroupKey, groupKey)) {
190                         records.add(r);
191                     }
192                 }
193             }
194         }
195         return records;
196     }
197 
getNotification(String key)198     protected NotificationRecord getNotification(String key) {
199         synchronized (mLock) {
200             if (!mUsers.containsKey(key) || !mPackages.containsKey(key)) {
201                 Slog.w(TAG, "Snoozed data sets no longer agree for " + key);
202                 return null;
203             }
204             int userId = mUsers.get(key);
205             String pkg = mPackages.get(key);
206             ArrayMap<String, NotificationRecord> snoozed =
207                     mSnoozedNotifications.get(getPkgKey(userId, pkg));
208             if (snoozed == null) {
209                 return null;
210             }
211             return snoozed.get(key);
212         }
213     }
214 
getSnoozed()215     protected @NonNull List<NotificationRecord> getSnoozed() {
216         synchronized (mLock) {
217             // caller filters records based on the current user profiles and listener access, so just
218             // return everything
219             List<NotificationRecord> snoozed = new ArrayList<>();
220             for (String userPkgKey : mSnoozedNotifications.keySet()) {
221                 ArrayMap<String, NotificationRecord> snoozedRecords =
222                         mSnoozedNotifications.get(userPkgKey);
223                 snoozed.addAll(snoozedRecords.values());
224             }
225             return snoozed;
226         }
227     }
228 
229     /**
230      * Snoozes a notification and schedules an alarm to repost at that time.
231      */
snooze(NotificationRecord record, long duration)232     protected void snooze(NotificationRecord record, long duration) {
233         String pkg = record.getSbn().getPackageName();
234         String key = record.getKey();
235         int userId = record.getUser().getIdentifier();
236 
237         snooze(record);
238         scheduleRepost(pkg, key, userId, duration);
239         Long activateAt = System.currentTimeMillis() + duration;
240         synchronized (mLock) {
241             storeRecordLocked(pkg, key, userId, mPersistedSnoozedNotifications, activateAt);
242         }
243     }
244 
245     /**
246      * Records a snoozed notification.
247      */
snooze(NotificationRecord record, String contextId)248     protected void snooze(NotificationRecord record, String contextId) {
249         int userId = record.getUser().getIdentifier();
250         if (contextId != null) {
251             synchronized (mLock) {
252                 storeRecordLocked(record.getSbn().getPackageName(), record.getKey(),
253                         userId, mPersistedSnoozedNotificationsWithContext, contextId);
254             }
255         }
256         snooze(record);
257     }
258 
snooze(NotificationRecord record)259     private void snooze(NotificationRecord record) {
260         int userId = record.getUser().getIdentifier();
261         if (DEBUG) {
262             Slog.d(TAG, "Snoozing " + record.getKey());
263         }
264         synchronized (mLock) {
265             storeRecordLocked(record.getSbn().getPackageName(), record.getKey(),
266                     userId, mSnoozedNotifications, record);
267         }
268     }
269 
storeRecordLocked(String pkg, String key, Integer userId, ArrayMap<String, ArrayMap<String, T>> targets, T object)270     private <T> void storeRecordLocked(String pkg, String key, Integer userId,
271             ArrayMap<String, ArrayMap<String, T>> targets, T object) {
272 
273         mPackages.put(key, pkg);
274         mUsers.put(key, userId);
275         ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg));
276         if (keyToValue == null) {
277             keyToValue = new ArrayMap<>();
278         }
279         keyToValue.put(key, object);
280         targets.put(getPkgKey(userId, pkg), keyToValue);
281     }
282 
removeRecordLocked(String pkg, String key, Integer userId, ArrayMap<String, ArrayMap<String, T>> targets)283     private <T> T removeRecordLocked(String pkg, String key, Integer userId,
284             ArrayMap<String, ArrayMap<String, T>> targets) {
285         T object = null;
286         ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg));
287         if (keyToValue == null) {
288             return null;
289         }
290         object = keyToValue.remove(key);
291         if (keyToValue.size() == 0) {
292             targets.remove(getPkgKey(userId, pkg));
293         }
294         return object;
295     }
296 
cancel(int userId, String pkg, String tag, int id)297     protected boolean cancel(int userId, String pkg, String tag, int id) {
298         synchronized (mLock) {
299             ArrayMap<String, NotificationRecord> recordsForPkg =
300                     mSnoozedNotifications.get(getPkgKey(userId, pkg));
301             if (recordsForPkg != null) {
302                 final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet();
303                 for (Map.Entry<String, NotificationRecord> record : records) {
304                     final StatusBarNotification sbn = record.getValue().getSbn();
305                     if (Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) {
306                         record.getValue().isCanceled = true;
307                         return true;
308                     }
309                 }
310             }
311         }
312         return false;
313     }
314 
cancel(int userId, boolean includeCurrentProfiles)315     protected void cancel(int userId, boolean includeCurrentProfiles) {
316         synchronized (mLock) {
317             if (mSnoozedNotifications.size() == 0) {
318                 return;
319             }
320             IntArray userIds = new IntArray();
321             userIds.add(userId);
322             if (includeCurrentProfiles) {
323                 userIds = mUserProfiles.getCurrentProfileIds();
324             }
325             for (ArrayMap<String, NotificationRecord> snoozedRecords : mSnoozedNotifications.values()) {
326                 for (NotificationRecord r : snoozedRecords.values()) {
327                     if (userIds.binarySearch(r.getUserId()) >= 0) {
328                         r.isCanceled = true;
329                     }
330                 }
331             }
332         }
333     }
334 
cancel(int userId, String pkg)335     protected boolean cancel(int userId, String pkg) {
336         synchronized (mLock) {
337             ArrayMap<String, NotificationRecord> records =
338                     mSnoozedNotifications.get(getPkgKey(userId, pkg));
339             if (records == null) {
340                 return false;
341             }
342             int N = records.size();
343             for (int i = 0; i < N; i++) {
344                 records.valueAt(i).isCanceled = true;
345             }
346             return true;
347         }
348     }
349 
350     /**
351      * Updates the notification record so the most up to date information is shown on re-post.
352      */
update(int userId, NotificationRecord record)353     protected void update(int userId, NotificationRecord record) {
354         synchronized (mLock) {
355             ArrayMap<String, NotificationRecord> records =
356                     mSnoozedNotifications.get(getPkgKey(userId, record.getSbn().getPackageName()));
357             if (records == null) {
358                 return;
359             }
360             records.put(record.getKey(), record);
361         }
362     }
363 
repost(String key, boolean muteOnReturn)364     protected void repost(String key, boolean muteOnReturn) {
365         synchronized (mLock) {
366             Integer userId = mUsers.get(key);
367             if (userId != null) {
368                 repost(key, userId, muteOnReturn);
369             }
370         }
371     }
372 
repost(String key, int userId, boolean muteOnReturn)373     protected void repost(String key, int userId, boolean muteOnReturn) {
374         NotificationRecord record;
375         synchronized (mLock) {
376             final String pkg = mPackages.remove(key);
377             mUsers.remove(key);
378             removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotifications);
379             removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotificationsWithContext);
380             ArrayMap<String, NotificationRecord> records =
381                     mSnoozedNotifications.get(getPkgKey(userId, pkg));
382             if (records == null) {
383                 return;
384             }
385             record = records.remove(key);
386 
387         }
388 
389         if (record != null && !record.isCanceled) {
390             final PendingIntent pi = createPendingIntent(
391                     record.getSbn().getPackageName(), record.getKey(), userId);
392             mAm.cancel(pi);
393             MetricsLogger.action(record.getLogMaker()
394                     .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
395                     .setType(MetricsProto.MetricsEvent.TYPE_OPEN));
396             mCallback.repost(userId, record, muteOnReturn);
397         }
398     }
399 
repostGroupSummary(String pkg, int userId, String groupKey)400     protected void repostGroupSummary(String pkg, int userId, String groupKey) {
401         synchronized (mLock) {
402             ArrayMap<String, NotificationRecord> recordsByKey
403                     = mSnoozedNotifications.get(getPkgKey(userId, pkg));
404             if (recordsByKey == null) {
405                 return;
406             }
407 
408             String groupSummaryKey = null;
409             int N = recordsByKey.size();
410             for (int i = 0; i < N; i++) {
411                 final NotificationRecord potentialGroupSummary = recordsByKey.valueAt(i);
412                 if (potentialGroupSummary.getSbn().isGroup()
413                         && potentialGroupSummary.getNotification().isGroupSummary()
414                         && groupKey.equals(potentialGroupSummary.getGroupKey())) {
415                     groupSummaryKey = potentialGroupSummary.getKey();
416                     break;
417                 }
418             }
419 
420             if (groupSummaryKey != null) {
421                 NotificationRecord record = recordsByKey.remove(groupSummaryKey);
422                 mPackages.remove(groupSummaryKey);
423                 mUsers.remove(groupSummaryKey);
424 
425                 if (record != null && !record.isCanceled) {
426                     Runnable runnable = () -> {
427                         MetricsLogger.action(record.getLogMaker()
428                                 .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
429                                 .setType(MetricsProto.MetricsEvent.TYPE_OPEN));
430                         mCallback.repost(userId, record, false);
431                     };
432                     runnable.run();
433                 }
434             }
435         }
436     }
437 
clearData(int userId, String pkg)438     protected void clearData(int userId, String pkg) {
439         synchronized (mLock) {
440             ArrayMap<String, NotificationRecord> records =
441                     mSnoozedNotifications.get(getPkgKey(userId, pkg));
442             if (records == null) {
443                 return;
444             }
445             for (int i = records.size() - 1; i >= 0; i--) {
446                 final NotificationRecord r = records.removeAt(i);
447                 if (r != null) {
448                     mPackages.remove(r.getKey());
449                     mUsers.remove(r.getKey());
450                     Runnable runnable = () -> {
451                         final PendingIntent pi = createPendingIntent(pkg, r.getKey(), userId);
452                         mAm.cancel(pi);
453                         MetricsLogger.action(r.getLogMaker()
454                                 .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
455                                 .setType(MetricsProto.MetricsEvent.TYPE_DISMISS));
456                     };
457                     runnable.run();
458                 }
459             }
460         }
461     }
462 
createPendingIntent(String pkg, String key, int userId)463     private PendingIntent createPendingIntent(String pkg, String key, int userId) {
464         return PendingIntent.getBroadcast(mContext,
465                 REQUEST_CODE_REPOST,
466                 new Intent(REPOST_ACTION)
467                         .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
468                         .setData(new Uri.Builder().scheme(REPOST_SCHEME).appendPath(key).build())
469                         .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
470                         .putExtra(EXTRA_KEY, key)
471                         .putExtra(EXTRA_USER_ID, userId),
472                 PendingIntent.FLAG_UPDATE_CURRENT);
473     }
474 
scheduleRepostsForPersistedNotifications(long currentTime)475     public void scheduleRepostsForPersistedNotifications(long currentTime) {
476         synchronized (mLock) {
477             for (ArrayMap<String, Long> snoozed : mPersistedSnoozedNotifications.values()) {
478                 for (int i = 0; i < snoozed.size(); i++) {
479                     String key = snoozed.keyAt(i);
480                     Long time = snoozed.valueAt(i);
481                     String pkg = mPackages.get(key);
482                     Integer userId = mUsers.get(key);
483                     if (time == null || pkg == null || userId == null) {
484                         Slog.w(TAG, "data out of sync: " + time + "|" + pkg + "|" + userId);
485                         continue;
486                     }
487                     if (time != null && time > currentTime) {
488                         scheduleRepostAtTime(pkg, key, userId, time);
489                     }
490                 }
491             }
492         }
493     }
494 
scheduleRepost(String pkg, String key, int userId, long duration)495     private void scheduleRepost(String pkg, String key, int userId, long duration) {
496         scheduleRepostAtTime(pkg, key, userId, System.currentTimeMillis() + duration);
497     }
498 
scheduleRepostAtTime(String pkg, String key, int userId, long time)499     private void scheduleRepostAtTime(String pkg, String key, int userId, long time) {
500         Runnable runnable = () -> {
501             long identity = Binder.clearCallingIdentity();
502             try {
503                 final PendingIntent pi = createPendingIntent(pkg, key, userId);
504                 mAm.cancel(pi);
505                 if (DEBUG) Slog.d(TAG, "Scheduling evaluate for " + new Date(time));
506                 mAm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pi);
507             } finally {
508                 Binder.restoreCallingIdentity(identity);
509             }
510         };
511         runnable.run();
512     }
513 
dump(PrintWriter pw, NotificationManagerService.DumpFilter filter)514     public void dump(PrintWriter pw, NotificationManagerService.DumpFilter filter) {
515         synchronized (mLock) {
516             pw.println("\n  Snoozed notifications:");
517             for (String userPkgKey : mSnoozedNotifications.keySet()) {
518                 pw.print(INDENT);
519                 pw.println("key: " + userPkgKey);
520                 ArrayMap<String, NotificationRecord> snoozedRecords =
521                         mSnoozedNotifications.get(userPkgKey);
522                 Set<String> snoozedKeys = snoozedRecords.keySet();
523                 for (String key : snoozedKeys) {
524                     pw.print(INDENT);
525                     pw.print(INDENT);
526                     pw.print(INDENT);
527                     pw.println(key);
528                 }
529             }
530             pw.println("\n Pending snoozed notifications");
531             for (String userPkgKey : mPersistedSnoozedNotifications.keySet()) {
532                 pw.print(INDENT);
533                 pw.println("key: " + userPkgKey);
534                 ArrayMap<String, Long> snoozedRecords =
535                         mPersistedSnoozedNotifications.get(userPkgKey);
536                 if (snoozedRecords == null) {
537                     continue;
538                 }
539                 Set<String> snoozedKeys = snoozedRecords.keySet();
540                 for (String key : snoozedKeys) {
541                     pw.print(INDENT);
542                     pw.print(INDENT);
543                     pw.print(INDENT);
544                     pw.print(key);
545                     pw.print(INDENT);
546                     pw.println(snoozedRecords.get(key));
547                 }
548             }
549         }
550     }
551 
writeXml(XmlSerializer out)552     protected void writeXml(XmlSerializer out) throws IOException {
553         synchronized (mLock) {
554             final long currentTime = System.currentTimeMillis();
555             out.startTag(null, XML_TAG_NAME);
556             writeXml(out, mPersistedSnoozedNotifications, XML_SNOOZED_NOTIFICATION,
557                     value -> {
558                         if (value < currentTime) {
559                             return;
560                         }
561                         out.attribute(null, XML_SNOOZED_NOTIFICATION_TIME,
562                                 value.toString());
563                     });
564             writeXml(out, mPersistedSnoozedNotificationsWithContext,
565                     XML_SNOOZED_NOTIFICATION_CONTEXT,
566                     value -> {
567                         out.attribute(null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID,
568                                 value);
569                     });
570             out.endTag(null, XML_TAG_NAME);
571         }
572     }
573 
574     private interface Inserter<T> {
insert(T t)575         void insert(T t) throws IOException;
576     }
577 
writeXml(XmlSerializer out, ArrayMap<String, ArrayMap<String, T>> targets, String tag, Inserter<T> attributeInserter)578     private <T> void writeXml(XmlSerializer out,
579             ArrayMap<String, ArrayMap<String, T>> targets, String tag,
580             Inserter<T> attributeInserter)
581             throws IOException {
582         final int M = targets.size();
583         for (int i = 0; i < M; i++) {
584             // T is a String (snoozed until context) or Long (snoozed until time)
585             ArrayMap<String, T> keyToValue = targets.valueAt(i);
586             for (int j = 0; j < keyToValue.size(); j++) {
587                 String key = keyToValue.keyAt(j);
588                 T value = keyToValue.valueAt(j);
589                 String pkg = mPackages.get(key);
590                 Integer userId = mUsers.get(key);
591 
592                 if (pkg == null || userId == null) {
593                     Slog.w(TAG, "pkg " + pkg + " or user " + userId + " missing for " + key);
594                     continue;
595                 }
596 
597                 out.startTag(null, tag);
598 
599                 attributeInserter.insert(value);
600 
601                 out.attribute(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL,
602                         XML_SNOOZED_NOTIFICATION_VERSION);
603                 out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, key);
604 
605 
606                 out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, pkg);
607                 out.attribute(null, XML_SNOOZED_NOTIFICATION_USER_ID,
608                         String.valueOf(userId));
609 
610                 out.endTag(null, tag);
611             }
612         }
613     }
614 
readXml(XmlPullParser parser, long currentTime)615     protected void readXml(XmlPullParser parser, long currentTime)
616             throws XmlPullParserException, IOException {
617         int type;
618         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
619             String tag = parser.getName();
620             if (type == XmlPullParser.END_TAG
621                     && XML_TAG_NAME.equals(tag)) {
622                 break;
623             }
624             if (type == XmlPullParser.START_TAG
625                     && (XML_SNOOZED_NOTIFICATION.equals(tag)
626                         || tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT))
627                     && parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL)
628                         .equals(XML_SNOOZED_NOTIFICATION_VERSION)) {
629                 try {
630                     final String key = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_KEY);
631                     final String pkg = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_PKG);
632                     final int userId = XmlUtils.readIntAttribute(
633                             parser, XML_SNOOZED_NOTIFICATION_USER_ID, UserHandle.USER_ALL);
634                     if (tag.equals(XML_SNOOZED_NOTIFICATION)) {
635                         final Long time = XmlUtils.readLongAttribute(
636                                 parser, XML_SNOOZED_NOTIFICATION_TIME, 0);
637                         if (time > currentTime) { //only read new stuff
638                             synchronized (mLock) {
639                                 storeRecordLocked(
640                                         pkg, key, userId, mPersistedSnoozedNotifications, time);
641                             }
642                         }
643                     }
644                     if (tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT)) {
645                         final String creationId = parser.getAttributeValue(
646                                 null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID);
647                         synchronized (mLock) {
648                             storeRecordLocked(
649                                     pkg, key, userId, mPersistedSnoozedNotificationsWithContext,
650                                     creationId);
651                         }
652                     }
653                 } catch (Exception e) {
654                     Slog.e(TAG,  "Exception in reading snooze data from policy xml", e);
655                 }
656             }
657         }
658     }
659 
660     @VisibleForTesting
setAlarmManager(AlarmManager am)661     void setAlarmManager(AlarmManager am) {
662         mAm = am;
663     }
664 
665     protected interface Callback {
repost(int userId, NotificationRecord r, boolean muteOnReturn)666         void repost(int userId, NotificationRecord r, boolean muteOnReturn);
667     }
668 
669     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
670         @Override
671         public void onReceive(Context context, Intent intent) {
672             if (DEBUG) {
673                 Slog.d(TAG, "Reposting notification");
674             }
675             if (REPOST_ACTION.equals(intent.getAction())) {
676                 repost(intent.getStringExtra(EXTRA_KEY), intent.getIntExtra(EXTRA_USER_ID,
677                         UserHandle.USER_SYSTEM), false);
678             }
679         }
680     };
681 }
682