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