Loading services/core/java/com/android/server/notification/InjectableSystemClock.java 0 → 100644 +44 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.notification; /** * Testable wrapper around {@link android.os.SystemClock}. * * The default implementation at InjectableSystemClockImpl just proxies calls to the real * SystemClock * * In tests, pass an instance of FakeSystemClock, which allows you to control the values returned by * the various getters below. */ public interface InjectableSystemClock { /** @see android.os.SystemClock#uptimeMillis() */ long uptimeMillis(); /** @see android.os.SystemClock#elapsedRealtime() */ long elapsedRealtime(); /** @see android.os.SystemClock#elapsedRealtimeNanos() */ long elapsedRealtimeNanos(); /** @see android.os.SystemClock#currentThreadTimeMillis() */ long currentThreadTimeMillis(); /** @see System#currentTimeMillis() */ long currentTimeMillis(); } services/core/java/com/android/server/notification/InjectableSystemClockImpl.java 0 → 100644 +51 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.notification; /** * Default implementation of {@link InjectableSystemClock}. * * @hide */ public class InjectableSystemClockImpl implements InjectableSystemClock { public InjectableSystemClockImpl() {} @Override public long uptimeMillis() { return android.os.SystemClock.uptimeMillis(); } @Override public long elapsedRealtime() { return android.os.SystemClock.elapsedRealtime(); } @Override public long elapsedRealtimeNanos() { return android.os.SystemClock.elapsedRealtimeNanos(); } @Override public long currentThreadTimeMillis() { return android.os.SystemClock.currentThreadTimeMillis(); } @Override public long currentTimeMillis() { return System.currentTimeMillis(); } } services/core/java/com/android/server/notification/NotificationManagerService.java +197 −103 Original line number Diff line number Diff line Loading @@ -187,7 +187,6 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; Loading Loading @@ -473,6 +472,11 @@ public class NotificationManagerService extends SystemService { final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>(); final ArrayList<ToastRecord> mToastQueue = new ArrayList<>(); final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>(); // Keep track of `CancelNotificationRunnable`s which have been delayed due to awaiting // enqueued notifications to post @GuardedBy("mNotificationLock") final ArrayMap<NotificationRecord, ArrayList<CancelNotificationRunnable>> mDelayedCancelations = new ArrayMap<>(); // The last key in this list owns the hardware. ArrayList<String> mLights = new ArrayList<>(); Loading Loading @@ -535,6 +539,7 @@ public class NotificationManagerService extends SystemService { private NotificationRecordLogger mNotificationRecordLogger; private InstanceIdSequence mNotificationInstanceIdSequence; private Set<String> mMsgPkgsAllowedAsConvos = new HashSet(); private final InjectableSystemClock mSystemClock; static class Archive { final SparseArray<Boolean> mEnabled; Loading Loading @@ -748,7 +753,7 @@ public class NotificationManagerService extends SystemService { parser, mAllowedManagedServicePackages, forRestore, userId); migratedManagedServices = true; } else if (mSnoozeHelper.XML_TAG_NAME.equals(parser.getName())) { mSnoozeHelper.readXml(parser, System.currentTimeMillis()); mSnoozeHelper.readXml(parser, mSystemClock.currentTimeMillis()); } if (LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG.equals(parser.getName())) { if (forRestore && userId != UserHandle.USER_SYSTEM) { Loading Loading @@ -901,7 +906,7 @@ public class NotificationManagerService extends SystemService { Slog.w(TAG, "No notification with key: " + key); return; } final long now = System.currentTimeMillis(); final long now = mSystemClock.currentTimeMillis(); MetricsLogger.action(r.getItemLogMaker() .setType(MetricsEvent.TYPE_ACTION) .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, nv.rank) Loading Loading @@ -933,7 +938,7 @@ public class NotificationManagerService extends SystemService { Slog.w(TAG, "No notification with key: " + key); return; } final long now = System.currentTimeMillis(); final long now = mSystemClock.currentTimeMillis(); MetricsLogger.action(r.getLogMaker(now) .setCategory(MetricsEvent.NOTIFICATION_ITEM_ACTION) .setType(MetricsEvent.TYPE_ACTION) Loading Loading @@ -1697,15 +1702,18 @@ public class NotificationManagerService extends SystemService { public NotificationManagerService(Context context) { this(context, new NotificationRecordLoggerImpl(), new InjectableSystemClockImpl(), new InstanceIdSequence(NOTIFICATION_INSTANCE_ID_MAX)); } @VisibleForTesting public NotificationManagerService(Context context, NotificationRecordLogger notificationRecordLogger, InjectableSystemClock systemClock, InstanceIdSequence notificationInstanceIdSequence) { super(context); mNotificationRecordLogger = notificationRecordLogger; mSystemClock = systemClock; mNotificationInstanceIdSequence = notificationInstanceIdSequence; Notification.processWhitelistToken = WHITELIST_TOKEN; } Loading Loading @@ -2064,6 +2072,11 @@ public class NotificationManagerService extends SystemService { return getContext().getResources().getStringArray(key); } @VisibleForTesting protected Handler getWorkHandler() { return mHandler; } @Override public void onStart() { SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), (userId, r, muteOnReturn) -> { Loading Loading @@ -2337,7 +2350,8 @@ public class NotificationManagerService extends SystemService { mHistoryManager.onBootPhaseAppsCanStart(); registerDeviceConfigChange(); } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis()); mSnoozeHelper.scheduleRepostsForPersistedNotifications( mSystemClock.currentTimeMillis()); } } Loading Loading @@ -2697,7 +2711,7 @@ public class NotificationManagerService extends SystemService { .setUserId(r.getSbn().getNormalizedUserId()) .setChannelId(r.getChannel().getId()) .setChannelName(r.getChannel().getName().toString()) .setPostedTimeMs(System.currentTimeMillis()) .setPostedTimeMs(mSystemClock.currentTimeMillis()) .setTitle(getHistoryTitle(r.getNotification())) .setText(getHistoryText( r.getSbn().getPackageContext(getContext()), r.getNotification())) Loading Loading @@ -5225,7 +5239,7 @@ public class NotificationManagerService extends SystemService { GroupHelper.AUTOGROUP_KEY, adjustedSbn.getUid(), adjustedSbn.getInitialPid(), summaryNotification, adjustedSbn.getUser(), GroupHelper.AUTOGROUP_KEY, System.currentTimeMillis()); mSystemClock.currentTimeMillis()); summaryRecord = new NotificationRecord(getContext(), summarySbn, notificationRecord.getChannel()); summaryRecord.setIsAppImportanceLocked( Loading Loading @@ -5460,6 +5474,22 @@ public class NotificationManagerService extends SystemService { mSnoozeHelper.dump(pw, filter); } // Log delayed notification cancels pw.println(); pw.println(" Delayed notification cancels:"); if (mDelayedCancelations.isEmpty()) { pw.println(" None"); } else { Set<NotificationRecord> delayedKeys = mDelayedCancelations.keySet(); for (NotificationRecord record : delayedKeys) { ArrayList<CancelNotificationRunnable> queuedCancels = mDelayedCancelations.get(record); pw.println(" (" + queuedCancels.size() + ") cancels enqueued for" + record.getKey()); } } pw.println(); } if (!zenOnly) { Loading Loading @@ -5678,7 +5708,7 @@ public class NotificationManagerService extends SystemService { final StatusBarNotification n = new StatusBarNotification( pkg, opPkg, id, tag, notificationUid, callingPid, notification, user, null, System.currentTimeMillis()); user, null, mSystemClock.currentTimeMillis()); // setup local book-keeping String channelId = notification.getChannelId(); Loading Loading @@ -6011,7 +6041,7 @@ public class NotificationManagerService extends SystemService { final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg); if (appEnqueueRate > mMaxPackageEnqueueRate) { mUsageStats.registerOverRateQuota(pkg); final long now = SystemClock.elapsedRealtime(); final long now = mSystemClock.elapsedRealtime(); if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) { Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg); Loading Loading @@ -6223,43 +6253,13 @@ public class NotificationManagerService extends SystemService { this.mRank = rank; this.mCount = count; this.mListener = listener; this.mWhen = System.currentTimeMillis(); } @Override public void run() { String listenerName = mListener == null ? null : mListener.component.toShortString(); if (DBG) { EventLogTags.writeNotificationCancel(mCallingUid, mCallingPid, mPkg, mId, mTag, mUserId, mMustHaveFlags, mMustNotHaveFlags, mReason, listenerName); } synchronized (mNotificationLock) { // Check to see if there is a notification in the enqueued list that hasn't had a // chance to post yet. List<NotificationRecord> enqueued = findEnqueuedNotificationsForCriteria( mPkg, mTag, mId, mUserId); boolean repost = false; if (enqueued.size() > 0) { // Found something, let's see what it was repost = true; // If all enqueues happened before this cancel then wait for them to happen, // otherwise we should let this cancel through so the next enqueue happens for (NotificationRecord r : enqueued) { if (r.mUpdateTimeMs > mWhen) { // At least one enqueue was posted after the cancel, so we're invalid Slog.i(TAG, "notification cancel ignored due to newer enqueued entry" + "key=" + r.getSbn().getKey()); return; } } } if (repost) { mHandler.post(this); return; this.mWhen = mSystemClock.currentTimeMillis(); } // Move the work to this function so it can be called from PostNotificationRunnable private void doNotificationCancelLocked() { // Look for the notification in the posted list, since we already checked enqueued. String listenerName = mListener == null ? null : mListener.component.toShortString(); NotificationRecord r = findNotificationByListLocked(mNotificationList, mPkg, mTag, mId, mUserId); if (r != null) { Loading @@ -6284,10 +6284,6 @@ public class NotificationManagerService extends SystemService { if ((r.getNotification().flags & mMustNotHaveFlags) != 0) { return; } if (r.getUpdateTimeMs() > mWhen) { // In this case, a post must have slipped by when this runnable reposted return; } // Bubbled children get to stick around if the summary was manually cancelled // (user removed) from systemui. Loading @@ -6304,7 +6300,7 @@ public class NotificationManagerService extends SystemService { } // Cancel the notification. boolean wasPosted = removeFromNotificationListsLocked(r); boolean wasPosted = removePreviousFromNotificationListsLocked(r, mWhen); cancelNotificationLocked( r, mSendDelete, mReason, mRank, mCount, wasPosted, listenerName); cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName, Loading @@ -6325,6 +6321,39 @@ public class NotificationManagerService extends SystemService { } } } @Override public void run() { String listenerName = mListener == null ? null : mListener.component.toShortString(); if (DBG) { EventLogTags.writeNotificationCancel(mCallingUid, mCallingPid, mPkg, mId, mTag, mUserId, mMustHaveFlags, mMustNotHaveFlags, mReason, listenerName); } synchronized (mNotificationLock) { // Check to see if there is a notification in the enqueued list that hasn't had a // chance to post yet. List<NotificationRecord> enqueued = findEnqueuedNotificationsForCriteria( mPkg, mTag, mId, mUserId); if (enqueued.size() > 0) { // We have found notifications that were enqueued before this cancel, but not // yet posted. Attach this cancel to the last enqueue (the most recent), and // we will be executed in that notification's PostNotificationRunnable NotificationRecord enqueuedToAttach = enqueued.get(enqueued.size() - 1); ArrayList<CancelNotificationRunnable> delayed = mDelayedCancelations.get(enqueuedToAttach); if (delayed == null) { delayed = new ArrayList<>(); } delayed.add(this); mDelayedCancelations.put(enqueuedToAttach, delayed); return; } doNotificationCancelLocked(); } } } Loading @@ -6346,7 +6375,7 @@ public class NotificationManagerService extends SystemService { mSnoozeHelper.getSnoozeTimeForUnpostedNotification( r.getUser().getIdentifier(), r.getSbn().getPackageName(), r.getSbn().getKey()); final long currentTime = System.currentTimeMillis(); final long currentTime = mSystemClock.currentTimeMillis(); if (snoozeAt.longValue() > currentTime) { (new SnoozeNotificationRunnable(r.getSbn().getKey(), snoozeAt.longValue() - currentTime, null)).snoozeLocked(r); Loading Loading @@ -6406,15 +6435,26 @@ public class NotificationManagerService extends SystemService { enqueueStatus); } postPostNotificationRunnableMaybeDelayedLocked( r, new PostNotificationRunnable(r.getKey())); } } } /** * Mainly needed as a hook for tests which require setting up enqueued-but-not-posted * notification records */ @GuardedBy("mNotificationLock") protected void postPostNotificationRunnableMaybeDelayedLocked( NotificationRecord r, PostNotificationRunnable runnable) { // tell the assistant service about the notification if (mAssistants.isEnabled()) { mAssistants.onNotificationEnqueuedLocked(r); mHandler.postDelayed(new PostNotificationRunnable(r.getKey()), DELAY_FOR_ASSISTANT_TIME); mHandler.postDelayed(runnable, DELAY_FOR_ASSISTANT_TIME); } else { mHandler.post(new PostNotificationRunnable(r.getKey())); } } mHandler.post(runnable); } } Loading Loading @@ -6566,13 +6606,23 @@ public class NotificationManagerService extends SystemService { buzzBeepBlinkLoggingCode, getGroupInstanceId(n.getGroupKey())); } finally { int N = mEnqueuedNotifications.size(); NotificationRecord enqueued = null; for (int i = 0; i < N; i++) { final NotificationRecord enqueued = mEnqueuedNotifications.get(i); enqueued = mEnqueuedNotifications.get(i); if (Objects.equals(key, enqueued.getKey())) { mEnqueuedNotifications.remove(i); break; } } // If the enqueued notification record had a cancel attached after it, execute // it right now if (enqueued != null && mDelayedCancelations.get(enqueued) != null) { for (CancelNotificationRunnable r : mDelayedCancelations.get(enqueued)) { r.doNotificationCancelLocked(); } mDelayedCancelations.remove(enqueued); } } } } Loading Loading @@ -6801,7 +6851,8 @@ public class NotificationManagerService extends SystemService { .putExtra(EXTRA_KEY, record.getKey()), PendingIntent.FLAG_UPDATE_CURRENT); mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + record.getNotification().getTimeoutAfter(), pi); mSystemClock.elapsedRealtime() + record.getNotification().getTimeoutAfter(), pi); } } Loading Loading @@ -7329,7 +7380,7 @@ public class NotificationManagerService extends SystemService { || visibilityChanged || interruptiveChanged; if (interceptBefore && !record.isIntercepted() && record.isNewEnoughForAlerting(System.currentTimeMillis())) { && record.isNewEnoughForAlerting(mSystemClock.currentTimeMillis())) { buzzBeepBlinkLocked(record); } } Loading Loading @@ -7616,6 +7667,34 @@ public class NotificationManagerService extends SystemService { return wasPosted; } /** * Similar to the above method, removes all NotificationRecords with the same key as the given * NotificationRecord, but skips any records which are newer than the given one. */ private boolean removePreviousFromNotificationListsLocked(NotificationRecord r, long removeBefore) { // Remove notification records that occurred before the given record from both lists, // specifically allowing newer ones to respect ordering boolean wasPosted = false; List<NotificationRecord> matching = findNotificationsByListLocked(mNotificationList, r.getKey()); for (NotificationRecord record : matching) { // We don't need to check against update time for posted notifs mNotificationList.remove(record); mNotificationsByKey.remove(record.getSbn().getKey()); wasPosted = true; } matching = findNotificationsByListLocked(mEnqueuedNotifications, r.getKey()); for (NotificationRecord record : matching) { if (record.getUpdateTimeMs() <= removeBefore) { mNotificationList.remove(record); } } return wasPosted; } @GuardedBy("mNotificationLock") private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, @NotificationListenerService.NotificationCancelReason int reason, Loading Loading @@ -7731,7 +7810,7 @@ public class NotificationManagerService extends SystemService { // Save it for users of getHistoricalNotifications() mArchive.record(r.getSbn(), reason); final long now = System.currentTimeMillis(); final long now = mSystemClock.currentTimeMillis(); final LogMaker logMaker = r.getItemLogMaker() .setType(MetricsEvent.TYPE_DISMISS) .setSubtype(reason); Loading Loading @@ -8287,6 +8366,21 @@ public class NotificationManagerService extends SystemService { return null; } @GuardedBy("mNotificationLock") private List<NotificationRecord> findNotificationsByListLocked( ArrayList<NotificationRecord> list, String key) { List<NotificationRecord> matching = new ArrayList<>(); final int n = list.size(); for (int i = 0; i < n; i++) { NotificationRecord r = list.get(i); if (key.equals(r.getKey())) { matching.add(r); } } return matching; } /** * There may be multiple records that match your criteria. For instance if there have been * multiple notifications posted which are enqueued for the same pkg, tag, id, userId. This Loading services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -103,6 +103,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake( 1 << 30); private InjectableSystemClock mSystemClock = new FakeSystemClock(); private NotificationManagerService mService; private String mPkg = "com.android.server.notification"; Loading Loading @@ -154,7 +155,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { assertTrue(accessibilityManager.isEnabled()); mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger, mNotificationInstanceIdSequence)); mSystemClock, mNotificationInstanceIdSequence)); mService.setAudioManager(mAudioManager); mService.setVibrator(mVibrator); mService.setSystemReady(true); Loading Loading
services/core/java/com/android/server/notification/InjectableSystemClock.java 0 → 100644 +44 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.notification; /** * Testable wrapper around {@link android.os.SystemClock}. * * The default implementation at InjectableSystemClockImpl just proxies calls to the real * SystemClock * * In tests, pass an instance of FakeSystemClock, which allows you to control the values returned by * the various getters below. */ public interface InjectableSystemClock { /** @see android.os.SystemClock#uptimeMillis() */ long uptimeMillis(); /** @see android.os.SystemClock#elapsedRealtime() */ long elapsedRealtime(); /** @see android.os.SystemClock#elapsedRealtimeNanos() */ long elapsedRealtimeNanos(); /** @see android.os.SystemClock#currentThreadTimeMillis() */ long currentThreadTimeMillis(); /** @see System#currentTimeMillis() */ long currentTimeMillis(); }
services/core/java/com/android/server/notification/InjectableSystemClockImpl.java 0 → 100644 +51 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.notification; /** * Default implementation of {@link InjectableSystemClock}. * * @hide */ public class InjectableSystemClockImpl implements InjectableSystemClock { public InjectableSystemClockImpl() {} @Override public long uptimeMillis() { return android.os.SystemClock.uptimeMillis(); } @Override public long elapsedRealtime() { return android.os.SystemClock.elapsedRealtime(); } @Override public long elapsedRealtimeNanos() { return android.os.SystemClock.elapsedRealtimeNanos(); } @Override public long currentThreadTimeMillis() { return android.os.SystemClock.currentThreadTimeMillis(); } @Override public long currentTimeMillis() { return System.currentTimeMillis(); } }
services/core/java/com/android/server/notification/NotificationManagerService.java +197 −103 Original line number Diff line number Diff line Loading @@ -187,7 +187,6 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; Loading Loading @@ -473,6 +472,11 @@ public class NotificationManagerService extends SystemService { final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>(); final ArrayList<ToastRecord> mToastQueue = new ArrayList<>(); final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>(); // Keep track of `CancelNotificationRunnable`s which have been delayed due to awaiting // enqueued notifications to post @GuardedBy("mNotificationLock") final ArrayMap<NotificationRecord, ArrayList<CancelNotificationRunnable>> mDelayedCancelations = new ArrayMap<>(); // The last key in this list owns the hardware. ArrayList<String> mLights = new ArrayList<>(); Loading Loading @@ -535,6 +539,7 @@ public class NotificationManagerService extends SystemService { private NotificationRecordLogger mNotificationRecordLogger; private InstanceIdSequence mNotificationInstanceIdSequence; private Set<String> mMsgPkgsAllowedAsConvos = new HashSet(); private final InjectableSystemClock mSystemClock; static class Archive { final SparseArray<Boolean> mEnabled; Loading Loading @@ -748,7 +753,7 @@ public class NotificationManagerService extends SystemService { parser, mAllowedManagedServicePackages, forRestore, userId); migratedManagedServices = true; } else if (mSnoozeHelper.XML_TAG_NAME.equals(parser.getName())) { mSnoozeHelper.readXml(parser, System.currentTimeMillis()); mSnoozeHelper.readXml(parser, mSystemClock.currentTimeMillis()); } if (LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG.equals(parser.getName())) { if (forRestore && userId != UserHandle.USER_SYSTEM) { Loading Loading @@ -901,7 +906,7 @@ public class NotificationManagerService extends SystemService { Slog.w(TAG, "No notification with key: " + key); return; } final long now = System.currentTimeMillis(); final long now = mSystemClock.currentTimeMillis(); MetricsLogger.action(r.getItemLogMaker() .setType(MetricsEvent.TYPE_ACTION) .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, nv.rank) Loading Loading @@ -933,7 +938,7 @@ public class NotificationManagerService extends SystemService { Slog.w(TAG, "No notification with key: " + key); return; } final long now = System.currentTimeMillis(); final long now = mSystemClock.currentTimeMillis(); MetricsLogger.action(r.getLogMaker(now) .setCategory(MetricsEvent.NOTIFICATION_ITEM_ACTION) .setType(MetricsEvent.TYPE_ACTION) Loading Loading @@ -1697,15 +1702,18 @@ public class NotificationManagerService extends SystemService { public NotificationManagerService(Context context) { this(context, new NotificationRecordLoggerImpl(), new InjectableSystemClockImpl(), new InstanceIdSequence(NOTIFICATION_INSTANCE_ID_MAX)); } @VisibleForTesting public NotificationManagerService(Context context, NotificationRecordLogger notificationRecordLogger, InjectableSystemClock systemClock, InstanceIdSequence notificationInstanceIdSequence) { super(context); mNotificationRecordLogger = notificationRecordLogger; mSystemClock = systemClock; mNotificationInstanceIdSequence = notificationInstanceIdSequence; Notification.processWhitelistToken = WHITELIST_TOKEN; } Loading Loading @@ -2064,6 +2072,11 @@ public class NotificationManagerService extends SystemService { return getContext().getResources().getStringArray(key); } @VisibleForTesting protected Handler getWorkHandler() { return mHandler; } @Override public void onStart() { SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), (userId, r, muteOnReturn) -> { Loading Loading @@ -2337,7 +2350,8 @@ public class NotificationManagerService extends SystemService { mHistoryManager.onBootPhaseAppsCanStart(); registerDeviceConfigChange(); } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis()); mSnoozeHelper.scheduleRepostsForPersistedNotifications( mSystemClock.currentTimeMillis()); } } Loading Loading @@ -2697,7 +2711,7 @@ public class NotificationManagerService extends SystemService { .setUserId(r.getSbn().getNormalizedUserId()) .setChannelId(r.getChannel().getId()) .setChannelName(r.getChannel().getName().toString()) .setPostedTimeMs(System.currentTimeMillis()) .setPostedTimeMs(mSystemClock.currentTimeMillis()) .setTitle(getHistoryTitle(r.getNotification())) .setText(getHistoryText( r.getSbn().getPackageContext(getContext()), r.getNotification())) Loading Loading @@ -5225,7 +5239,7 @@ public class NotificationManagerService extends SystemService { GroupHelper.AUTOGROUP_KEY, adjustedSbn.getUid(), adjustedSbn.getInitialPid(), summaryNotification, adjustedSbn.getUser(), GroupHelper.AUTOGROUP_KEY, System.currentTimeMillis()); mSystemClock.currentTimeMillis()); summaryRecord = new NotificationRecord(getContext(), summarySbn, notificationRecord.getChannel()); summaryRecord.setIsAppImportanceLocked( Loading Loading @@ -5460,6 +5474,22 @@ public class NotificationManagerService extends SystemService { mSnoozeHelper.dump(pw, filter); } // Log delayed notification cancels pw.println(); pw.println(" Delayed notification cancels:"); if (mDelayedCancelations.isEmpty()) { pw.println(" None"); } else { Set<NotificationRecord> delayedKeys = mDelayedCancelations.keySet(); for (NotificationRecord record : delayedKeys) { ArrayList<CancelNotificationRunnable> queuedCancels = mDelayedCancelations.get(record); pw.println(" (" + queuedCancels.size() + ") cancels enqueued for" + record.getKey()); } } pw.println(); } if (!zenOnly) { Loading Loading @@ -5678,7 +5708,7 @@ public class NotificationManagerService extends SystemService { final StatusBarNotification n = new StatusBarNotification( pkg, opPkg, id, tag, notificationUid, callingPid, notification, user, null, System.currentTimeMillis()); user, null, mSystemClock.currentTimeMillis()); // setup local book-keeping String channelId = notification.getChannelId(); Loading Loading @@ -6011,7 +6041,7 @@ public class NotificationManagerService extends SystemService { final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg); if (appEnqueueRate > mMaxPackageEnqueueRate) { mUsageStats.registerOverRateQuota(pkg); final long now = SystemClock.elapsedRealtime(); final long now = mSystemClock.elapsedRealtime(); if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) { Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg); Loading Loading @@ -6223,43 +6253,13 @@ public class NotificationManagerService extends SystemService { this.mRank = rank; this.mCount = count; this.mListener = listener; this.mWhen = System.currentTimeMillis(); } @Override public void run() { String listenerName = mListener == null ? null : mListener.component.toShortString(); if (DBG) { EventLogTags.writeNotificationCancel(mCallingUid, mCallingPid, mPkg, mId, mTag, mUserId, mMustHaveFlags, mMustNotHaveFlags, mReason, listenerName); } synchronized (mNotificationLock) { // Check to see if there is a notification in the enqueued list that hasn't had a // chance to post yet. List<NotificationRecord> enqueued = findEnqueuedNotificationsForCriteria( mPkg, mTag, mId, mUserId); boolean repost = false; if (enqueued.size() > 0) { // Found something, let's see what it was repost = true; // If all enqueues happened before this cancel then wait for them to happen, // otherwise we should let this cancel through so the next enqueue happens for (NotificationRecord r : enqueued) { if (r.mUpdateTimeMs > mWhen) { // At least one enqueue was posted after the cancel, so we're invalid Slog.i(TAG, "notification cancel ignored due to newer enqueued entry" + "key=" + r.getSbn().getKey()); return; } } } if (repost) { mHandler.post(this); return; this.mWhen = mSystemClock.currentTimeMillis(); } // Move the work to this function so it can be called from PostNotificationRunnable private void doNotificationCancelLocked() { // Look for the notification in the posted list, since we already checked enqueued. String listenerName = mListener == null ? null : mListener.component.toShortString(); NotificationRecord r = findNotificationByListLocked(mNotificationList, mPkg, mTag, mId, mUserId); if (r != null) { Loading @@ -6284,10 +6284,6 @@ public class NotificationManagerService extends SystemService { if ((r.getNotification().flags & mMustNotHaveFlags) != 0) { return; } if (r.getUpdateTimeMs() > mWhen) { // In this case, a post must have slipped by when this runnable reposted return; } // Bubbled children get to stick around if the summary was manually cancelled // (user removed) from systemui. Loading @@ -6304,7 +6300,7 @@ public class NotificationManagerService extends SystemService { } // Cancel the notification. boolean wasPosted = removeFromNotificationListsLocked(r); boolean wasPosted = removePreviousFromNotificationListsLocked(r, mWhen); cancelNotificationLocked( r, mSendDelete, mReason, mRank, mCount, wasPosted, listenerName); cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName, Loading @@ -6325,6 +6321,39 @@ public class NotificationManagerService extends SystemService { } } } @Override public void run() { String listenerName = mListener == null ? null : mListener.component.toShortString(); if (DBG) { EventLogTags.writeNotificationCancel(mCallingUid, mCallingPid, mPkg, mId, mTag, mUserId, mMustHaveFlags, mMustNotHaveFlags, mReason, listenerName); } synchronized (mNotificationLock) { // Check to see if there is a notification in the enqueued list that hasn't had a // chance to post yet. List<NotificationRecord> enqueued = findEnqueuedNotificationsForCriteria( mPkg, mTag, mId, mUserId); if (enqueued.size() > 0) { // We have found notifications that were enqueued before this cancel, but not // yet posted. Attach this cancel to the last enqueue (the most recent), and // we will be executed in that notification's PostNotificationRunnable NotificationRecord enqueuedToAttach = enqueued.get(enqueued.size() - 1); ArrayList<CancelNotificationRunnable> delayed = mDelayedCancelations.get(enqueuedToAttach); if (delayed == null) { delayed = new ArrayList<>(); } delayed.add(this); mDelayedCancelations.put(enqueuedToAttach, delayed); return; } doNotificationCancelLocked(); } } } Loading @@ -6346,7 +6375,7 @@ public class NotificationManagerService extends SystemService { mSnoozeHelper.getSnoozeTimeForUnpostedNotification( r.getUser().getIdentifier(), r.getSbn().getPackageName(), r.getSbn().getKey()); final long currentTime = System.currentTimeMillis(); final long currentTime = mSystemClock.currentTimeMillis(); if (snoozeAt.longValue() > currentTime) { (new SnoozeNotificationRunnable(r.getSbn().getKey(), snoozeAt.longValue() - currentTime, null)).snoozeLocked(r); Loading Loading @@ -6406,15 +6435,26 @@ public class NotificationManagerService extends SystemService { enqueueStatus); } postPostNotificationRunnableMaybeDelayedLocked( r, new PostNotificationRunnable(r.getKey())); } } } /** * Mainly needed as a hook for tests which require setting up enqueued-but-not-posted * notification records */ @GuardedBy("mNotificationLock") protected void postPostNotificationRunnableMaybeDelayedLocked( NotificationRecord r, PostNotificationRunnable runnable) { // tell the assistant service about the notification if (mAssistants.isEnabled()) { mAssistants.onNotificationEnqueuedLocked(r); mHandler.postDelayed(new PostNotificationRunnable(r.getKey()), DELAY_FOR_ASSISTANT_TIME); mHandler.postDelayed(runnable, DELAY_FOR_ASSISTANT_TIME); } else { mHandler.post(new PostNotificationRunnable(r.getKey())); } } mHandler.post(runnable); } } Loading Loading @@ -6566,13 +6606,23 @@ public class NotificationManagerService extends SystemService { buzzBeepBlinkLoggingCode, getGroupInstanceId(n.getGroupKey())); } finally { int N = mEnqueuedNotifications.size(); NotificationRecord enqueued = null; for (int i = 0; i < N; i++) { final NotificationRecord enqueued = mEnqueuedNotifications.get(i); enqueued = mEnqueuedNotifications.get(i); if (Objects.equals(key, enqueued.getKey())) { mEnqueuedNotifications.remove(i); break; } } // If the enqueued notification record had a cancel attached after it, execute // it right now if (enqueued != null && mDelayedCancelations.get(enqueued) != null) { for (CancelNotificationRunnable r : mDelayedCancelations.get(enqueued)) { r.doNotificationCancelLocked(); } mDelayedCancelations.remove(enqueued); } } } } Loading Loading @@ -6801,7 +6851,8 @@ public class NotificationManagerService extends SystemService { .putExtra(EXTRA_KEY, record.getKey()), PendingIntent.FLAG_UPDATE_CURRENT); mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + record.getNotification().getTimeoutAfter(), pi); mSystemClock.elapsedRealtime() + record.getNotification().getTimeoutAfter(), pi); } } Loading Loading @@ -7329,7 +7380,7 @@ public class NotificationManagerService extends SystemService { || visibilityChanged || interruptiveChanged; if (interceptBefore && !record.isIntercepted() && record.isNewEnoughForAlerting(System.currentTimeMillis())) { && record.isNewEnoughForAlerting(mSystemClock.currentTimeMillis())) { buzzBeepBlinkLocked(record); } } Loading Loading @@ -7616,6 +7667,34 @@ public class NotificationManagerService extends SystemService { return wasPosted; } /** * Similar to the above method, removes all NotificationRecords with the same key as the given * NotificationRecord, but skips any records which are newer than the given one. */ private boolean removePreviousFromNotificationListsLocked(NotificationRecord r, long removeBefore) { // Remove notification records that occurred before the given record from both lists, // specifically allowing newer ones to respect ordering boolean wasPosted = false; List<NotificationRecord> matching = findNotificationsByListLocked(mNotificationList, r.getKey()); for (NotificationRecord record : matching) { // We don't need to check against update time for posted notifs mNotificationList.remove(record); mNotificationsByKey.remove(record.getSbn().getKey()); wasPosted = true; } matching = findNotificationsByListLocked(mEnqueuedNotifications, r.getKey()); for (NotificationRecord record : matching) { if (record.getUpdateTimeMs() <= removeBefore) { mNotificationList.remove(record); } } return wasPosted; } @GuardedBy("mNotificationLock") private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, @NotificationListenerService.NotificationCancelReason int reason, Loading Loading @@ -7731,7 +7810,7 @@ public class NotificationManagerService extends SystemService { // Save it for users of getHistoricalNotifications() mArchive.record(r.getSbn(), reason); final long now = System.currentTimeMillis(); final long now = mSystemClock.currentTimeMillis(); final LogMaker logMaker = r.getItemLogMaker() .setType(MetricsEvent.TYPE_DISMISS) .setSubtype(reason); Loading Loading @@ -8287,6 +8366,21 @@ public class NotificationManagerService extends SystemService { return null; } @GuardedBy("mNotificationLock") private List<NotificationRecord> findNotificationsByListLocked( ArrayList<NotificationRecord> list, String key) { List<NotificationRecord> matching = new ArrayList<>(); final int n = list.size(); for (int i = 0; i < n; i++) { NotificationRecord r = list.get(i); if (key.equals(r.getKey())) { matching.add(r); } } return matching; } /** * There may be multiple records that match your criteria. For instance if there have been * multiple notifications posted which are enqueued for the same pkg, tag, id, userId. This Loading
services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -103,6 +103,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake( 1 << 30); private InjectableSystemClock mSystemClock = new FakeSystemClock(); private NotificationManagerService mService; private String mPkg = "com.android.server.notification"; Loading Loading @@ -154,7 +155,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { assertTrue(accessibilityManager.isEnabled()); mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger, mNotificationInstanceIdSequence)); mSystemClock, mNotificationInstanceIdSequence)); mService.setAudioManager(mAudioManager); mService.setVibrator(mVibrator); mService.setSystemReady(true); Loading