Loading services/core/java/com/android/server/notification/NotificationManagerService.java +26 −17 Original line number Diff line number Diff line Loading @@ -2068,20 +2068,17 @@ public class NotificationManagerService extends SystemService { @Override public void onStart() { SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), new SnoozeHelper.Callback() { @Override public void repost(int userId, NotificationRecord r) { SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), (userId, r, muteOnReturn) -> { try { if (DBG) { Slog.d(TAG, "Reposting " + r.getKey()); } enqueueNotificationInternal(r.getSbn().getPackageName(), r.getSbn().getOpPkg(), r.getSbn().getUid(), r.getSbn().getInitialPid(), r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getNotification(), userId); r.getSbn().getId(), r.getSbn().getNotification(), userId, true); } catch (Exception e) { Slog.e(TAG, "Cannot un-snooze notification", e); } } }, mUserProfiles); final File systemDir = new File(Environment.getDataDirectory(), "system"); Loading Loading @@ -3983,7 +3980,7 @@ public class NotificationManagerService extends SystemService { synchronized (mNotificationLock) { final ManagedServiceInfo info = mAssistants.checkServiceTokenLocked(token); unsnoozeNotificationInt(key, info); unsnoozeNotificationInt(key, info, false); } } finally { Binder.restoreCallingIdentity(identity); Loading @@ -4006,7 +4003,7 @@ public class NotificationManagerService extends SystemService { if (!info.isSystem) { throw new SecurityException("Not allowed to unsnooze before deadline"); } unsnoozeNotificationInt(key, info); unsnoozeNotificationInt(key, info, true); } } finally { Binder.restoreCallingIdentity(identity); Loading Loading @@ -5525,6 +5522,13 @@ public class NotificationManagerService extends SystemService { void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int incomingUserId) { enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification, incomingUserId, false); } void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int incomingUserId, boolean postSilently) { if (DBG) { Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification); Loading Loading @@ -5605,6 +5609,7 @@ public class NotificationManagerService extends SystemService { user, null, System.currentTimeMillis()); final NotificationRecord r = new NotificationRecord(getContext(), n, channel); r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid)); r.setPostSilently(postSilently); if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { final boolean fgServiceShown = channel.isFgServiceShown(); Loading Loading @@ -7040,6 +7045,11 @@ public class NotificationManagerService extends SystemService { return true; } // Suppressed because a user manually unsnoozed something (or similar) if (record.shouldPostSilently()) { return true; } // muted by listener final String disableEffects = disableNotificationEffects(record); if (disableEffects != null) { Loading Loading @@ -8054,13 +8064,12 @@ public class NotificationManagerService extends SystemService { mHandler.post(new SnoozeNotificationRunnable(key, duration, snoozeCriterionId)); } void unsnoozeNotificationInt(String key, ManagedServiceInfo listener) { void unsnoozeNotificationInt(String key, ManagedServiceInfo listener, boolean muteOnReturn) { String listenerName = listener == null ? null : listener.component.toShortString(); if (DBG) { Slog.d(TAG, String.format("unsnooze event(%s, %s)", key, listenerName)); } mSnoozeHelper.cleanupPersistedContext(key); mSnoozeHelper.repost(key); mSnoozeHelper.repost(key, muteOnReturn); handleSavePolicyFile(); } Loading services/core/java/com/android/server/notification/NotificationRecord.java +12 −0 Original line number Diff line number Diff line Loading @@ -187,6 +187,7 @@ public final class NotificationRecord { private boolean mSuggestionsGeneratedByAssistant; private boolean mEditChoicesBeforeSending; private boolean mHasSeenSmartReplies; private boolean mPostSilently; /** * Whether this notification (and its channels) should be considered user locked. Used in * conjunction with user sentiment calculation. Loading Loading @@ -856,6 +857,17 @@ public final class NotificationRecord { return mHidden; } /** * Override of all alerting information on the channel and notification. Used when notifications * are reposted in response to direct user action and thus don't need to alert. */ public void setPostSilently(boolean postSilently) { mPostSilently = postSilently; } public boolean shouldPostSilently() { return mPostSilently; } public void setSuppressedVisualEffects(int effects) { mSuppressedVisualEffects = effects; Loading services/core/java/com/android/server/notification/SnoozeHelper.java +298 −238 Original line number Diff line number Diff line Loading @@ -106,6 +106,8 @@ public class SnoozeHelper { private ArrayMap<String, Integer> mUsers = new ArrayMap<>(); private Callback mCallback; private final Object mLock = new Object(); public SnoozeHelper(Context context, Callback callback, ManagedServices.UserProfiles userProfiles) { mContext = context; Loading @@ -122,42 +124,53 @@ public class SnoozeHelper { } void cleanupPersistedContext(String key){ synchronized (mLock) { int userId = mUsers.get(key); String pkg = mPackages.get(key); synchronized (mPersistedSnoozedNotificationsWithContext) { removeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext); removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotificationsWithContext); } } //This function has a side effect of removing the time from the list of persisted notifications. //IT IS NOT IDEMPOTENT! @NonNull protected Long getSnoozeTimeForUnpostedNotification(int userId, String pkg, String key) { Long time; synchronized (mPersistedSnoozedNotifications) { time = removeRecord(pkg, key, userId, mPersistedSnoozedNotifications); Long time = null; synchronized (mLock) { ArrayMap<String, Long> snoozed = mPersistedSnoozedNotifications.get(getPkgKey(userId, pkg)); if (snoozed != null) { time = snoozed.get(key); } } if (time == null) { return 0L; time = 0L; } return time; } protected String getSnoozeContextForUnpostedNotification(int userId, String pkg, String key) { synchronized (mPersistedSnoozedNotificationsWithContext) { return removeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext); synchronized (mLock) { ArrayMap<String, String> snoozed = mPersistedSnoozedNotificationsWithContext.get(getPkgKey(userId, pkg)); if (snoozed != null) { return snoozed.get(key); } } return null; } protected boolean isSnoozed(int userId, String pkg, String key) { synchronized (mLock) { return mSnoozedNotifications.containsKey(getPkgKey(userId, pkg)) && mSnoozedNotifications.get(getPkgKey(userId, pkg)).containsKey(key); } } protected Collection<NotificationRecord> getSnoozed(int userId, String pkg) { synchronized (mLock) { if (mSnoozedNotifications.containsKey(getPkgKey(userId, pkg))) { return mSnoozedNotifications.get(getPkgKey(userId, pkg)).values(); } } return Collections.EMPTY_LIST; } Loading @@ -165,6 +178,7 @@ public class SnoozeHelper { ArrayList<NotificationRecord> getNotifications(String pkg, String groupKey, Integer userId) { ArrayList<NotificationRecord> records = new ArrayList<>(); synchronized (mLock) { ArrayMap<String, NotificationRecord> allRecords = mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (allRecords != null) { Loading @@ -176,10 +190,12 @@ public class SnoozeHelper { } } } } return records; } protected NotificationRecord getNotification(String key) { synchronized (mLock) { if (!mUsers.containsKey(key) || !mPackages.containsKey(key)) { Slog.w(TAG, "Snoozed data sets no longer agree for " + key); return null; Loading @@ -193,8 +209,10 @@ public class SnoozeHelper { } return snoozed.get(key); } } protected @NonNull List<NotificationRecord> getSnoozed() { synchronized (mLock) { // caller filters records based on the current user profiles and listener access, so just // return everything List<NotificationRecord> snoozed = new ArrayList<>(); Loading @@ -205,6 +223,7 @@ public class SnoozeHelper { } return snoozed; } } /** * Snoozes a notification and schedules an alarm to repost at that time. Loading @@ -216,9 +235,9 @@ public class SnoozeHelper { snooze(record); scheduleRepost(pkg, key, userId, duration); Long activateAt = SystemClock.elapsedRealtime() + duration; synchronized (mPersistedSnoozedNotifications) { storeRecord(pkg, key, userId, mPersistedSnoozedNotifications, activateAt); Long activateAt = System.currentTimeMillis() + duration; synchronized (mLock) { storeRecordLocked(pkg, key, userId, mPersistedSnoozedNotifications, activateAt); } } Loading @@ -228,8 +247,8 @@ public class SnoozeHelper { protected void snooze(NotificationRecord record, String contextId) { int userId = record.getUser().getIdentifier(); if (contextId != null) { synchronized (mPersistedSnoozedNotificationsWithContext) { storeRecord(record.getSbn().getPackageName(), record.getKey(), synchronized (mLock) { storeRecordLocked(record.getSbn().getPackageName(), record.getKey(), userId, mPersistedSnoozedNotificationsWithContext, contextId); } } Loading @@ -241,25 +260,26 @@ public class SnoozeHelper { if (DEBUG) { Slog.d(TAG, "Snoozing " + record.getKey()); } storeRecord(record.getSbn().getPackageName(), record.getKey(), synchronized (mLock) { storeRecordLocked(record.getSbn().getPackageName(), record.getKey(), userId, mSnoozedNotifications, record); } } private <T> void storeRecord(String pkg, String key, Integer userId, private <T> void storeRecordLocked(String pkg, String key, Integer userId, ArrayMap<String, ArrayMap<String, T>> targets, T object) { mPackages.put(key, pkg); mUsers.put(key, userId); ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg)); if (keyToValue == null) { keyToValue = new ArrayMap<>(); } keyToValue.put(key, object); targets.put(getPkgKey(userId, pkg), keyToValue); mPackages.put(key, pkg); mUsers.put(key, userId); } private <T> T removeRecord(String pkg, String key, Integer userId, private <T> T removeRecordLocked(String pkg, String key, Integer userId, ArrayMap<String, ArrayMap<String, T>> targets) { T object = null; ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg)); Loading @@ -274,6 +294,7 @@ public class SnoozeHelper { } protected boolean cancel(int userId, String pkg, String tag, int id) { synchronized (mLock) { ArrayMap<String, NotificationRecord> recordsForPkg = mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (recordsForPkg != null) { Loading @@ -286,10 +307,12 @@ public class SnoozeHelper { } } } } return false; } protected void cancel(int userId, boolean includeCurrentProfiles) { synchronized (mLock) { if (mSnoozedNotifications.size() == 0) { return; } Loading @@ -306,8 +329,10 @@ public class SnoozeHelper { } } } } protected boolean cancel(int userId, String pkg) { synchronized (mLock) { ArrayMap<String, NotificationRecord> records = mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (records == null) { Loading @@ -319,11 +344,13 @@ public class SnoozeHelper { } return true; } } /** * Updates the notification record so the most up to date information is shown on re-post. */ protected void update(int userId, NotificationRecord record) { synchronized (mLock) { ArrayMap<String, NotificationRecord> records = mSnoozedNotifications.get(getPkgKey(userId, record.getSbn().getPackageName())); if (records == null) { Loading @@ -331,36 +358,46 @@ public class SnoozeHelper { } records.put(record.getKey(), record); } } protected void repost(String key) { protected void repost(String key, boolean muteOnReturn) { synchronized (mLock) { Integer userId = mUsers.get(key); if (userId != null) { repost(key, userId); repost(key, userId, muteOnReturn); } } } protected void repost(String key, int userId) { protected void repost(String key, int userId, boolean muteOnReturn) { NotificationRecord record; synchronized (mLock) { final String pkg = mPackages.remove(key); mUsers.remove(key); removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotifications); removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotificationsWithContext); ArrayMap<String, NotificationRecord> records = mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (records == null) { return; } final NotificationRecord record = records.remove(key); mPackages.remove(key); mUsers.remove(key); record = records.remove(key); } if (record != null && !record.isCanceled) { final PendingIntent pi = createPendingIntent(pkg, record.getKey(), userId); final PendingIntent pi = createPendingIntent( record.getSbn().getPackageName(), record.getKey(), userId); mAm.cancel(pi); MetricsLogger.action(record.getLogMaker() .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) .setType(MetricsProto.MetricsEvent.TYPE_OPEN)); mCallback.repost(userId, record); mCallback.repost(userId, record, muteOnReturn); } } protected void repostGroupSummary(String pkg, int userId, String groupKey) { synchronized (mLock) { ArrayMap<String, NotificationRecord> recordsByKey = mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (recordsByKey == null) { Loading @@ -385,15 +422,20 @@ public class SnoozeHelper { mUsers.remove(groupSummaryKey); if (record != null && !record.isCanceled) { Runnable runnable = () -> { MetricsLogger.action(record.getLogMaker() .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) .setType(MetricsProto.MetricsEvent.TYPE_OPEN)); mCallback.repost(userId, record); mCallback.repost(userId, record, false); }; runnable.run(); } } } } protected void clearData(int userId, String pkg) { synchronized (mLock) { ArrayMap<String, NotificationRecord> records = mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (records == null) { Loading @@ -404,11 +446,15 @@ public class SnoozeHelper { if (r != null) { mPackages.remove(r.getKey()); mUsers.remove(r.getKey()); Runnable runnable = () -> { final PendingIntent pi = createPendingIntent(pkg, r.getKey(), userId); mAm.cancel(pi); MetricsLogger.action(r.getLogMaker() .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) .setType(MetricsProto.MetricsEvent.TYPE_DISMISS)); }; runnable.run(); } } } } Loading @@ -425,6 +471,7 @@ public class SnoozeHelper { } public void scheduleRepostsForPersistedNotifications(long currentTime) { synchronized (mLock) { for (ArrayMap<String, Long> snoozed : mPersistedSnoozedNotifications.values()) { for (int i = 0; i < snoozed.size(); i++) { String key = snoozed.keyAt(i); Loading @@ -439,27 +486,31 @@ public class SnoozeHelper { scheduleRepostAtTime(pkg, key, userId, time); } } } } } private void scheduleRepost(String pkg, String key, int userId, long duration) { scheduleRepostAtTime(pkg, key, userId, SystemClock.elapsedRealtime() + duration); scheduleRepostAtTime(pkg, key, userId, System.currentTimeMillis() + duration); } private void scheduleRepostAtTime(String pkg, String key, int userId, long time) { Runnable runnable = () -> { long identity = Binder.clearCallingIdentity(); try { final PendingIntent pi = createPendingIntent(pkg, key, userId); mAm.cancel(pi); if (DEBUG) Slog.d(TAG, "Scheduling evaluate for " + new Date(time)); mAm.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, time, pi); mAm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pi); } finally { Binder.restoreCallingIdentity(identity); } }; runnable.run(); } public void dump(PrintWriter pw, NotificationManagerService.DumpFilter filter) { synchronized (mLock) { pw.println("\n Snoozed notifications:"); for (String userPkgKey : mSnoozedNotifications.keySet()) { pw.print(INDENT); Loading Loading @@ -494,8 +545,10 @@ public class SnoozeHelper { } } } } protected void writeXml(XmlSerializer out) throws IOException { synchronized (mLock) { final long currentTime = System.currentTimeMillis(); out.startTag(null, XML_TAG_NAME); writeXml(out, mPersistedSnoozedNotifications, XML_SNOOZED_NOTIFICATION, Loading @@ -506,13 +559,15 @@ public class SnoozeHelper { out.attribute(null, XML_SNOOZED_NOTIFICATION_TIME, value.toString()); }); writeXml(out, mPersistedSnoozedNotificationsWithContext, XML_SNOOZED_NOTIFICATION_CONTEXT, writeXml(out, mPersistedSnoozedNotificationsWithContext, XML_SNOOZED_NOTIFICATION_CONTEXT, value -> { out.attribute(null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID, value); }); out.endTag(null, XML_TAG_NAME); } } private interface Inserter<T> { void insert(T t) throws IOException; Loading @@ -522,7 +577,6 @@ public class SnoozeHelper { ArrayMap<String, ArrayMap<String, T>> targets, String tag, Inserter<T> attributeInserter) throws IOException { synchronized (targets) { final int M = targets.size(); for (int i = 0; i < M; i++) { // T is a String (snoozed until context) or Long (snoozed until time) Loading @@ -533,6 +587,11 @@ public class SnoozeHelper { String pkg = mPackages.get(key); Integer userId = mUsers.get(key); if (pkg == null || userId == null) { Slog.w(TAG, "pkg " + pkg + " or user " + userId + " missing for " + key); continue; } out.startTag(null, tag); attributeInserter.insert(value); Loading @@ -550,7 +609,6 @@ public class SnoozeHelper { } } } } protected void readXml(XmlPullParser parser, long currentTime) throws XmlPullParserException, IOException { Loading @@ -575,16 +633,18 @@ public class SnoozeHelper { final Long time = XmlUtils.readLongAttribute( parser, XML_SNOOZED_NOTIFICATION_TIME, 0); if (time > currentTime) { //only read new stuff synchronized (mPersistedSnoozedNotifications) { storeRecord(pkg, key, userId, mPersistedSnoozedNotifications, time); synchronized (mLock) { storeRecordLocked( pkg, key, userId, mPersistedSnoozedNotifications, time); } } } if (tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT)) { final String creationId = parser.getAttributeValue( null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID); synchronized (mPersistedSnoozedNotificationsWithContext) { storeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext, synchronized (mLock) { storeRecordLocked( pkg, key, userId, mPersistedSnoozedNotificationsWithContext, creationId); } } Loading @@ -601,7 +661,7 @@ public class SnoozeHelper { } protected interface Callback { void repost(int userId, NotificationRecord r); void repost(int userId, NotificationRecord r, boolean muteOnReturn); } private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { Loading @@ -612,7 +672,7 @@ public class SnoozeHelper { } if (REPOST_ACTION.equals(intent.getAction())) { repost(intent.getStringExtra(EXTRA_KEY), intent.getIntExtra(EXTRA_USER_ID, UserHandle.USER_SYSTEM)); UserHandle.USER_SYSTEM), false); } } }; Loading services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +12 −0 Original line number Diff line number Diff line Loading @@ -845,6 +845,18 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @Test public void testPostSilently() throws Exception { NotificationRecord r = getBuzzyNotification(); r.setPostSilently(true); mService.buzzBeepBlinkLocked(r); verifyNeverBeep(); assertFalse(r.isInterruptive()); assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test public void testGroupAlertSummarySilenceChild() throws Exception { NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY); Loading services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java +27 −16 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +26 −17 Original line number Diff line number Diff line Loading @@ -2068,20 +2068,17 @@ public class NotificationManagerService extends SystemService { @Override public void onStart() { SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), new SnoozeHelper.Callback() { @Override public void repost(int userId, NotificationRecord r) { SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), (userId, r, muteOnReturn) -> { try { if (DBG) { Slog.d(TAG, "Reposting " + r.getKey()); } enqueueNotificationInternal(r.getSbn().getPackageName(), r.getSbn().getOpPkg(), r.getSbn().getUid(), r.getSbn().getInitialPid(), r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getNotification(), userId); r.getSbn().getId(), r.getSbn().getNotification(), userId, true); } catch (Exception e) { Slog.e(TAG, "Cannot un-snooze notification", e); } } }, mUserProfiles); final File systemDir = new File(Environment.getDataDirectory(), "system"); Loading Loading @@ -3983,7 +3980,7 @@ public class NotificationManagerService extends SystemService { synchronized (mNotificationLock) { final ManagedServiceInfo info = mAssistants.checkServiceTokenLocked(token); unsnoozeNotificationInt(key, info); unsnoozeNotificationInt(key, info, false); } } finally { Binder.restoreCallingIdentity(identity); Loading @@ -4006,7 +4003,7 @@ public class NotificationManagerService extends SystemService { if (!info.isSystem) { throw new SecurityException("Not allowed to unsnooze before deadline"); } unsnoozeNotificationInt(key, info); unsnoozeNotificationInt(key, info, true); } } finally { Binder.restoreCallingIdentity(identity); Loading Loading @@ -5525,6 +5522,13 @@ public class NotificationManagerService extends SystemService { void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int incomingUserId) { enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification, incomingUserId, false); } void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int incomingUserId, boolean postSilently) { if (DBG) { Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification); Loading Loading @@ -5605,6 +5609,7 @@ public class NotificationManagerService extends SystemService { user, null, System.currentTimeMillis()); final NotificationRecord r = new NotificationRecord(getContext(), n, channel); r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid)); r.setPostSilently(postSilently); if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { final boolean fgServiceShown = channel.isFgServiceShown(); Loading Loading @@ -7040,6 +7045,11 @@ public class NotificationManagerService extends SystemService { return true; } // Suppressed because a user manually unsnoozed something (or similar) if (record.shouldPostSilently()) { return true; } // muted by listener final String disableEffects = disableNotificationEffects(record); if (disableEffects != null) { Loading Loading @@ -8054,13 +8064,12 @@ public class NotificationManagerService extends SystemService { mHandler.post(new SnoozeNotificationRunnable(key, duration, snoozeCriterionId)); } void unsnoozeNotificationInt(String key, ManagedServiceInfo listener) { void unsnoozeNotificationInt(String key, ManagedServiceInfo listener, boolean muteOnReturn) { String listenerName = listener == null ? null : listener.component.toShortString(); if (DBG) { Slog.d(TAG, String.format("unsnooze event(%s, %s)", key, listenerName)); } mSnoozeHelper.cleanupPersistedContext(key); mSnoozeHelper.repost(key); mSnoozeHelper.repost(key, muteOnReturn); handleSavePolicyFile(); } Loading
services/core/java/com/android/server/notification/NotificationRecord.java +12 −0 Original line number Diff line number Diff line Loading @@ -187,6 +187,7 @@ public final class NotificationRecord { private boolean mSuggestionsGeneratedByAssistant; private boolean mEditChoicesBeforeSending; private boolean mHasSeenSmartReplies; private boolean mPostSilently; /** * Whether this notification (and its channels) should be considered user locked. Used in * conjunction with user sentiment calculation. Loading Loading @@ -856,6 +857,17 @@ public final class NotificationRecord { return mHidden; } /** * Override of all alerting information on the channel and notification. Used when notifications * are reposted in response to direct user action and thus don't need to alert. */ public void setPostSilently(boolean postSilently) { mPostSilently = postSilently; } public boolean shouldPostSilently() { return mPostSilently; } public void setSuppressedVisualEffects(int effects) { mSuppressedVisualEffects = effects; Loading
services/core/java/com/android/server/notification/SnoozeHelper.java +298 −238 Original line number Diff line number Diff line Loading @@ -106,6 +106,8 @@ public class SnoozeHelper { private ArrayMap<String, Integer> mUsers = new ArrayMap<>(); private Callback mCallback; private final Object mLock = new Object(); public SnoozeHelper(Context context, Callback callback, ManagedServices.UserProfiles userProfiles) { mContext = context; Loading @@ -122,42 +124,53 @@ public class SnoozeHelper { } void cleanupPersistedContext(String key){ synchronized (mLock) { int userId = mUsers.get(key); String pkg = mPackages.get(key); synchronized (mPersistedSnoozedNotificationsWithContext) { removeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext); removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotificationsWithContext); } } //This function has a side effect of removing the time from the list of persisted notifications. //IT IS NOT IDEMPOTENT! @NonNull protected Long getSnoozeTimeForUnpostedNotification(int userId, String pkg, String key) { Long time; synchronized (mPersistedSnoozedNotifications) { time = removeRecord(pkg, key, userId, mPersistedSnoozedNotifications); Long time = null; synchronized (mLock) { ArrayMap<String, Long> snoozed = mPersistedSnoozedNotifications.get(getPkgKey(userId, pkg)); if (snoozed != null) { time = snoozed.get(key); } } if (time == null) { return 0L; time = 0L; } return time; } protected String getSnoozeContextForUnpostedNotification(int userId, String pkg, String key) { synchronized (mPersistedSnoozedNotificationsWithContext) { return removeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext); synchronized (mLock) { ArrayMap<String, String> snoozed = mPersistedSnoozedNotificationsWithContext.get(getPkgKey(userId, pkg)); if (snoozed != null) { return snoozed.get(key); } } return null; } protected boolean isSnoozed(int userId, String pkg, String key) { synchronized (mLock) { return mSnoozedNotifications.containsKey(getPkgKey(userId, pkg)) && mSnoozedNotifications.get(getPkgKey(userId, pkg)).containsKey(key); } } protected Collection<NotificationRecord> getSnoozed(int userId, String pkg) { synchronized (mLock) { if (mSnoozedNotifications.containsKey(getPkgKey(userId, pkg))) { return mSnoozedNotifications.get(getPkgKey(userId, pkg)).values(); } } return Collections.EMPTY_LIST; } Loading @@ -165,6 +178,7 @@ public class SnoozeHelper { ArrayList<NotificationRecord> getNotifications(String pkg, String groupKey, Integer userId) { ArrayList<NotificationRecord> records = new ArrayList<>(); synchronized (mLock) { ArrayMap<String, NotificationRecord> allRecords = mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (allRecords != null) { Loading @@ -176,10 +190,12 @@ public class SnoozeHelper { } } } } return records; } protected NotificationRecord getNotification(String key) { synchronized (mLock) { if (!mUsers.containsKey(key) || !mPackages.containsKey(key)) { Slog.w(TAG, "Snoozed data sets no longer agree for " + key); return null; Loading @@ -193,8 +209,10 @@ public class SnoozeHelper { } return snoozed.get(key); } } protected @NonNull List<NotificationRecord> getSnoozed() { synchronized (mLock) { // caller filters records based on the current user profiles and listener access, so just // return everything List<NotificationRecord> snoozed = new ArrayList<>(); Loading @@ -205,6 +223,7 @@ public class SnoozeHelper { } return snoozed; } } /** * Snoozes a notification and schedules an alarm to repost at that time. Loading @@ -216,9 +235,9 @@ public class SnoozeHelper { snooze(record); scheduleRepost(pkg, key, userId, duration); Long activateAt = SystemClock.elapsedRealtime() + duration; synchronized (mPersistedSnoozedNotifications) { storeRecord(pkg, key, userId, mPersistedSnoozedNotifications, activateAt); Long activateAt = System.currentTimeMillis() + duration; synchronized (mLock) { storeRecordLocked(pkg, key, userId, mPersistedSnoozedNotifications, activateAt); } } Loading @@ -228,8 +247,8 @@ public class SnoozeHelper { protected void snooze(NotificationRecord record, String contextId) { int userId = record.getUser().getIdentifier(); if (contextId != null) { synchronized (mPersistedSnoozedNotificationsWithContext) { storeRecord(record.getSbn().getPackageName(), record.getKey(), synchronized (mLock) { storeRecordLocked(record.getSbn().getPackageName(), record.getKey(), userId, mPersistedSnoozedNotificationsWithContext, contextId); } } Loading @@ -241,25 +260,26 @@ public class SnoozeHelper { if (DEBUG) { Slog.d(TAG, "Snoozing " + record.getKey()); } storeRecord(record.getSbn().getPackageName(), record.getKey(), synchronized (mLock) { storeRecordLocked(record.getSbn().getPackageName(), record.getKey(), userId, mSnoozedNotifications, record); } } private <T> void storeRecord(String pkg, String key, Integer userId, private <T> void storeRecordLocked(String pkg, String key, Integer userId, ArrayMap<String, ArrayMap<String, T>> targets, T object) { mPackages.put(key, pkg); mUsers.put(key, userId); ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg)); if (keyToValue == null) { keyToValue = new ArrayMap<>(); } keyToValue.put(key, object); targets.put(getPkgKey(userId, pkg), keyToValue); mPackages.put(key, pkg); mUsers.put(key, userId); } private <T> T removeRecord(String pkg, String key, Integer userId, private <T> T removeRecordLocked(String pkg, String key, Integer userId, ArrayMap<String, ArrayMap<String, T>> targets) { T object = null; ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg)); Loading @@ -274,6 +294,7 @@ public class SnoozeHelper { } protected boolean cancel(int userId, String pkg, String tag, int id) { synchronized (mLock) { ArrayMap<String, NotificationRecord> recordsForPkg = mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (recordsForPkg != null) { Loading @@ -286,10 +307,12 @@ public class SnoozeHelper { } } } } return false; } protected void cancel(int userId, boolean includeCurrentProfiles) { synchronized (mLock) { if (mSnoozedNotifications.size() == 0) { return; } Loading @@ -306,8 +329,10 @@ public class SnoozeHelper { } } } } protected boolean cancel(int userId, String pkg) { synchronized (mLock) { ArrayMap<String, NotificationRecord> records = mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (records == null) { Loading @@ -319,11 +344,13 @@ public class SnoozeHelper { } return true; } } /** * Updates the notification record so the most up to date information is shown on re-post. */ protected void update(int userId, NotificationRecord record) { synchronized (mLock) { ArrayMap<String, NotificationRecord> records = mSnoozedNotifications.get(getPkgKey(userId, record.getSbn().getPackageName())); if (records == null) { Loading @@ -331,36 +358,46 @@ public class SnoozeHelper { } records.put(record.getKey(), record); } } protected void repost(String key) { protected void repost(String key, boolean muteOnReturn) { synchronized (mLock) { Integer userId = mUsers.get(key); if (userId != null) { repost(key, userId); repost(key, userId, muteOnReturn); } } } protected void repost(String key, int userId) { protected void repost(String key, int userId, boolean muteOnReturn) { NotificationRecord record; synchronized (mLock) { final String pkg = mPackages.remove(key); mUsers.remove(key); removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotifications); removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotificationsWithContext); ArrayMap<String, NotificationRecord> records = mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (records == null) { return; } final NotificationRecord record = records.remove(key); mPackages.remove(key); mUsers.remove(key); record = records.remove(key); } if (record != null && !record.isCanceled) { final PendingIntent pi = createPendingIntent(pkg, record.getKey(), userId); final PendingIntent pi = createPendingIntent( record.getSbn().getPackageName(), record.getKey(), userId); mAm.cancel(pi); MetricsLogger.action(record.getLogMaker() .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) .setType(MetricsProto.MetricsEvent.TYPE_OPEN)); mCallback.repost(userId, record); mCallback.repost(userId, record, muteOnReturn); } } protected void repostGroupSummary(String pkg, int userId, String groupKey) { synchronized (mLock) { ArrayMap<String, NotificationRecord> recordsByKey = mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (recordsByKey == null) { Loading @@ -385,15 +422,20 @@ public class SnoozeHelper { mUsers.remove(groupSummaryKey); if (record != null && !record.isCanceled) { Runnable runnable = () -> { MetricsLogger.action(record.getLogMaker() .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) .setType(MetricsProto.MetricsEvent.TYPE_OPEN)); mCallback.repost(userId, record); mCallback.repost(userId, record, false); }; runnable.run(); } } } } protected void clearData(int userId, String pkg) { synchronized (mLock) { ArrayMap<String, NotificationRecord> records = mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (records == null) { Loading @@ -404,11 +446,15 @@ public class SnoozeHelper { if (r != null) { mPackages.remove(r.getKey()); mUsers.remove(r.getKey()); Runnable runnable = () -> { final PendingIntent pi = createPendingIntent(pkg, r.getKey(), userId); mAm.cancel(pi); MetricsLogger.action(r.getLogMaker() .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) .setType(MetricsProto.MetricsEvent.TYPE_DISMISS)); }; runnable.run(); } } } } Loading @@ -425,6 +471,7 @@ public class SnoozeHelper { } public void scheduleRepostsForPersistedNotifications(long currentTime) { synchronized (mLock) { for (ArrayMap<String, Long> snoozed : mPersistedSnoozedNotifications.values()) { for (int i = 0; i < snoozed.size(); i++) { String key = snoozed.keyAt(i); Loading @@ -439,27 +486,31 @@ public class SnoozeHelper { scheduleRepostAtTime(pkg, key, userId, time); } } } } } private void scheduleRepost(String pkg, String key, int userId, long duration) { scheduleRepostAtTime(pkg, key, userId, SystemClock.elapsedRealtime() + duration); scheduleRepostAtTime(pkg, key, userId, System.currentTimeMillis() + duration); } private void scheduleRepostAtTime(String pkg, String key, int userId, long time) { Runnable runnable = () -> { long identity = Binder.clearCallingIdentity(); try { final PendingIntent pi = createPendingIntent(pkg, key, userId); mAm.cancel(pi); if (DEBUG) Slog.d(TAG, "Scheduling evaluate for " + new Date(time)); mAm.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, time, pi); mAm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pi); } finally { Binder.restoreCallingIdentity(identity); } }; runnable.run(); } public void dump(PrintWriter pw, NotificationManagerService.DumpFilter filter) { synchronized (mLock) { pw.println("\n Snoozed notifications:"); for (String userPkgKey : mSnoozedNotifications.keySet()) { pw.print(INDENT); Loading Loading @@ -494,8 +545,10 @@ public class SnoozeHelper { } } } } protected void writeXml(XmlSerializer out) throws IOException { synchronized (mLock) { final long currentTime = System.currentTimeMillis(); out.startTag(null, XML_TAG_NAME); writeXml(out, mPersistedSnoozedNotifications, XML_SNOOZED_NOTIFICATION, Loading @@ -506,13 +559,15 @@ public class SnoozeHelper { out.attribute(null, XML_SNOOZED_NOTIFICATION_TIME, value.toString()); }); writeXml(out, mPersistedSnoozedNotificationsWithContext, XML_SNOOZED_NOTIFICATION_CONTEXT, writeXml(out, mPersistedSnoozedNotificationsWithContext, XML_SNOOZED_NOTIFICATION_CONTEXT, value -> { out.attribute(null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID, value); }); out.endTag(null, XML_TAG_NAME); } } private interface Inserter<T> { void insert(T t) throws IOException; Loading @@ -522,7 +577,6 @@ public class SnoozeHelper { ArrayMap<String, ArrayMap<String, T>> targets, String tag, Inserter<T> attributeInserter) throws IOException { synchronized (targets) { final int M = targets.size(); for (int i = 0; i < M; i++) { // T is a String (snoozed until context) or Long (snoozed until time) Loading @@ -533,6 +587,11 @@ public class SnoozeHelper { String pkg = mPackages.get(key); Integer userId = mUsers.get(key); if (pkg == null || userId == null) { Slog.w(TAG, "pkg " + pkg + " or user " + userId + " missing for " + key); continue; } out.startTag(null, tag); attributeInserter.insert(value); Loading @@ -550,7 +609,6 @@ public class SnoozeHelper { } } } } protected void readXml(XmlPullParser parser, long currentTime) throws XmlPullParserException, IOException { Loading @@ -575,16 +633,18 @@ public class SnoozeHelper { final Long time = XmlUtils.readLongAttribute( parser, XML_SNOOZED_NOTIFICATION_TIME, 0); if (time > currentTime) { //only read new stuff synchronized (mPersistedSnoozedNotifications) { storeRecord(pkg, key, userId, mPersistedSnoozedNotifications, time); synchronized (mLock) { storeRecordLocked( pkg, key, userId, mPersistedSnoozedNotifications, time); } } } if (tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT)) { final String creationId = parser.getAttributeValue( null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID); synchronized (mPersistedSnoozedNotificationsWithContext) { storeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext, synchronized (mLock) { storeRecordLocked( pkg, key, userId, mPersistedSnoozedNotificationsWithContext, creationId); } } Loading @@ -601,7 +661,7 @@ public class SnoozeHelper { } protected interface Callback { void repost(int userId, NotificationRecord r); void repost(int userId, NotificationRecord r, boolean muteOnReturn); } private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { Loading @@ -612,7 +672,7 @@ public class SnoozeHelper { } if (REPOST_ACTION.equals(intent.getAction())) { repost(intent.getStringExtra(EXTRA_KEY), intent.getIntExtra(EXTRA_USER_ID, UserHandle.USER_SYSTEM)); UserHandle.USER_SYSTEM), false); } } }; Loading
services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +12 −0 Original line number Diff line number Diff line Loading @@ -845,6 +845,18 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @Test public void testPostSilently() throws Exception { NotificationRecord r = getBuzzyNotification(); r.setPostSilently(true); mService.buzzBeepBlinkLocked(r); verifyNeverBeep(); assertFalse(r.isInterruptive()); assertEquals(-1, r.getLastAudiblyAlertedMs()); } @Test public void testGroupAlertSummarySilenceChild() throws Exception { NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY); Loading
services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java +27 −16 File changed.Preview size limit exceeded, changes collapsed. Show changes