diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 039fc50aa4f84..aaba00ac26803 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -270,6 +270,7 @@ public class NotificationManagerService extends SystemService { new ArrayList(); final ArrayMap mNotificationsByKey = new ArrayMap(); + final ArrayList mEnqueuedNotifications = new ArrayList<>(); final ArrayMap> mAutobundledSummaries = new ArrayMap<>(); final ArrayList mToastQueue = new ArrayList(); final ArrayMap mSummaryByGroupKey = new ArrayMap<>(); @@ -1614,35 +1615,60 @@ public class NotificationManagerService extends SystemService { int userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), incomingUserId, true, false, "getAppActiveNotifications", pkg); - - final ArrayList list - = new ArrayList(mNotificationList.size()); + final ArrayMap map + = new ArrayMap<>(mNotificationList.size() + mEnqueuedNotifications.size()); synchronized (mNotificationList) { final int N = mNotificationList.size(); for (int i = 0; i < N; i++) { - final StatusBarNotification sbn = mNotificationList.get(i).sbn; - if (sbn.getPackageName().equals(pkg) && sbn.getUserId() == userId - && (sbn.getNotification().flags - & Notification.FLAG_AUTOGROUP_SUMMARY) == 0) { - // We could pass back a cloneLight() but clients might get confused and - // try to send this thing back to notify() again, which would not work - // very well. - final StatusBarNotification sbnOut = new StatusBarNotification( - sbn.getPackageName(), - sbn.getOpPkg(), - sbn.getNotificationChannel(), - sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), - sbn.getNotification().clone(), - sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); - list.add(sbnOut); + StatusBarNotification sbn = sanitizeSbn(pkg, userId, + mNotificationList.get(i).sbn); + if (sbn != null) { + map.put(sbn.getKey(), sbn); + } + } + for(NotificationRecord snoozed: mSnoozeHelper.getSnoozed(userId, pkg)) { + StatusBarNotification sbn = sanitizeSbn(pkg, userId, snoozed.sbn); + if (sbn != null) { + map.put(sbn.getKey(), sbn); + } + } + } + synchronized (mEnqueuedNotifications) { + final int N = mEnqueuedNotifications.size(); + for (int i = 0; i < N; i++) { + StatusBarNotification sbn = sanitizeSbn(pkg, userId, + mEnqueuedNotifications.get(i).sbn); + if (sbn != null) { + map.put(sbn.getKey(), sbn); // pending update overwrites existing post here } } } + final ArrayList list = new ArrayList<>(map.size()); + list.addAll(map.values()); return new ParceledListSlice(list); } + private StatusBarNotification sanitizeSbn(String pkg, int userId, + StatusBarNotification sbn) { + if (sbn.getPackageName().equals(pkg) && sbn.getUserId() == userId + && (sbn.getNotification().flags + & Notification.FLAG_AUTOGROUP_SUMMARY) == 0) { + // We could pass back a cloneLight() but clients might get confused and + // try to send this thing back to notify() again, which would not work + // very well. + return new StatusBarNotification( + sbn.getPackageName(), + sbn.getOpPkg(), + sbn.getNotificationChannel(), + sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), + sbn.getNotification().clone(), + sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); + } + return null; + } + /** * System-only API for getting a list of recent (cleared, no longer shown) notifications. * @@ -2835,6 +2861,9 @@ public class NotificationManagerService extends SystemService { // setup local book-keeping final NotificationRecord r = new NotificationRecord(getContext(), n); + synchronized (mEnqueuedNotifications) { + mEnqueuedNotifications.add(r); + } mHandler.post(new EnqueueNotificationRunnable(userId, r)); idOut[0] = id; @@ -2851,121 +2880,126 @@ public class NotificationManagerService extends SystemService { @Override public void run() { - - synchronized (mNotificationList) { - if (mSnoozeHelper.isSnoozed(userId, r.sbn.getPackageName(), r.getKey())) { - // TODO: log to event log - if (DBG) { - Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey()); - } - mSnoozeHelper.update(userId, r); - savePolicyFile(); - return; - } - - final StatusBarNotification n = r.sbn; - if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey()); - NotificationRecord old = mNotificationsByKey.get(n.getKey()); - if (old != null) { - // Retain ranking information from previous record - r.copyRankingInformation(old); - } - - final int callingUid = n.getUid(); - final int callingPid = n.getInitialPid(); - final Notification notification = n.getNotification(); - final String pkg = n.getPackageName(); - final int id = n.getId(); - final String tag = n.getTag(); - final boolean isSystemNotification = isUidSystem(callingUid) || - ("android".equals(pkg)); - - // Handle grouped notifications and bail out early if we - // can to avoid extracting signals. - handleGroupedNotificationLocked(r, old, callingUid, callingPid); - - // This conditional is a dirty hack to limit the logging done on - // behalf of the download manager without affecting other apps. - if (!pkg.equals("com.android.providers.downloads") - || Log.isLoggable("DownloadManager", Log.VERBOSE)) { - int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW; - if (old != null) { - enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE; - } - EventLogTags.writeNotificationEnqueue(callingUid, callingPid, - pkg, id, tag, userId, notification.toString(), - enqueueStatus); - } - - mRankingHelper.extractSignals(r); - - // blocked apps - if (isBlocked(r, mUsageStats)) { - return; - } - - // tell the assistant service about the notification - if (mNotificationAssistants.isEnabled()) { - mNotificationAssistants.onNotificationEnqueued(r); - // TODO delay the code below here for 100ms or until there is an answer - } - - - int index = indexOfNotificationLocked(n.getKey()); - if (index < 0) { - mNotificationList.add(r); - mUsageStats.registerPostedByApp(r); - } else { - old = mNotificationList.get(index); - mNotificationList.set(index, r); - mUsageStats.registerUpdatedByApp(r, old); - // Make sure we don't lose the foreground service state. - notification.flags |= - old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE; - r.isUpdate = true; - } - - mNotificationsByKey.put(n.getKey(), r); - - // Ensure if this is a foreground service that the proper additional - // flags are set. - if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { - notification.flags |= Notification.FLAG_ONGOING_EVENT - | Notification.FLAG_NO_CLEAR; - } - - applyZenModeLocked(r); - mRankingHelper.sort(mNotificationList); - - if (notification.getSmallIcon() != null) { - StatusBarNotification oldSbn = (old != null) ? old.sbn : null; - mListeners.notifyPostedLocked(n, oldSbn); - mHandler.post(new Runnable() { - @Override - public void run() { - mGroupHelper.onNotificationPosted(n); + try { + synchronized (mNotificationList) { + if (mSnoozeHelper.isSnoozed(userId, r.sbn.getPackageName(), r.getKey())) { + // TODO: log to event log + if (DBG) { + Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey()); } - }); - } else { - Slog.e(TAG, "Not posting notification without small icon: " + notification); - if (old != null && !old.isCanceled) { - mListeners.notifyRemovedLocked(n, - NotificationListenerService.REASON_DELEGATE_ERROR); + mSnoozeHelper.update(userId, r); + savePolicyFile(); + return; + } + + final StatusBarNotification n = r.sbn; + if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey()); + NotificationRecord old = mNotificationsByKey.get(n.getKey()); + if (old != null) { + // Retain ranking information from previous record + r.copyRankingInformation(old); + } + + final int callingUid = n.getUid(); + final int callingPid = n.getInitialPid(); + final Notification notification = n.getNotification(); + final String pkg = n.getPackageName(); + final int id = n.getId(); + final String tag = n.getTag(); + final boolean isSystemNotification = isUidSystem(callingUid) || + ("android".equals(pkg)); + + // Handle grouped notifications and bail out early if we + // can to avoid extracting signals. + handleGroupedNotificationLocked(r, old, callingUid, callingPid); + + // This conditional is a dirty hack to limit the logging done on + // behalf of the download manager without affecting other apps. + if (!pkg.equals("com.android.providers.downloads") + || Log.isLoggable("DownloadManager", Log.VERBOSE)) { + int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW; + if (old != null) { + enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE; + } + EventLogTags.writeNotificationEnqueue(callingUid, callingPid, + pkg, id, tag, userId, notification.toString(), + enqueueStatus); + } + + mRankingHelper.extractSignals(r); + + // blocked apps + if (isBlocked(r, mUsageStats)) { + return; + } + + // tell the assistant service about the notification + if (mNotificationAssistants.isEnabled()) { + mNotificationAssistants.onNotificationEnqueued(r); + // TODO delay the code below here for 100ms or until there is an answer + } + + + int index = indexOfNotificationLocked(n.getKey()); + if (index < 0) { + mNotificationList.add(r); + mUsageStats.registerPostedByApp(r); + } else { + old = mNotificationList.get(index); + mNotificationList.set(index, r); + mUsageStats.registerUpdatedByApp(r, old); + // Make sure we don't lose the foreground service state. + notification.flags |= + old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE; + r.isUpdate = true; + } + + mNotificationsByKey.put(n.getKey(), r); + + // Ensure if this is a foreground service that the proper additional + // flags are set. + if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { + notification.flags |= Notification.FLAG_ONGOING_EVENT + | Notification.FLAG_NO_CLEAR; + } + + applyZenModeLocked(r); + mRankingHelper.sort(mNotificationList); + + if (notification.getSmallIcon() != null) { + StatusBarNotification oldSbn = (old != null) ? old.sbn : null; + mListeners.notifyPostedLocked(n, oldSbn); mHandler.post(new Runnable() { @Override public void run() { - mGroupHelper.onNotificationRemoved(n); + mGroupHelper.onNotificationPosted(n); } }); + } else { + Slog.e(TAG, "Not posting notification without small icon: " + notification); + if (old != null && !old.isCanceled) { + mListeners.notifyRemovedLocked(n, + NotificationListenerService.REASON_DELEGATE_ERROR); + mHandler.post(new Runnable() { + @Override + public void run() { + mGroupHelper.onNotificationRemoved(n); + } + }); + } + // ATTENTION: in a future release we will bail out here + // so that we do not play sounds, show lights, etc. for invalid + // notifications + Slog.e(TAG, "WARNING: In a future release this will crash the app: " + + n.getPackageName()); } - // ATTENTION: in a future release we will bail out here - // so that we do not play sounds, show lights, etc. for invalid - // notifications - Slog.e(TAG, "WARNING: In a future release this will crash the app: " - + n.getPackageName()); - } - buzzBeepBlinkLocked(r); + buzzBeepBlinkLocked(r); + } + } finally { + synchronized (mEnqueuedNotifications) { + mEnqueuedNotifications.remove(r); + } } } diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java index 409eef45b557f..733ff67135c51 100644 --- a/services/core/java/com/android/server/notification/SnoozeHelper.java +++ b/services/core/java/com/android/server/notification/SnoozeHelper.java @@ -36,6 +36,9 @@ import android.util.Slog; 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.Map; import java.util.Objects; @@ -84,6 +87,14 @@ public class SnoozeHelper { && mSnoozedNotifications.get(userId).get(pkg).containsKey(key); } + protected Collection getSnoozed(int userId, String pkg) { + if (mSnoozedNotifications.containsKey(userId) + && mSnoozedNotifications.get(userId).containsKey(pkg)) { + mSnoozedNotifications.get(userId).get(pkg).values(); + } + return Collections.EMPTY_LIST; + } + /** * Snoozes a notification and schedules an alarm to repost at that time. */