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