From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Valentin Iftime Date: Mon, 16 Oct 2023 09:29:17 +0200 Subject: [PATCH] Prioritize system toasts Insert toasts from system packages at the front of the queue to ensure that apps can't spam with toast to delay system toasts from showing. Test: atest NotificationManagerServiceTest Bug: 293301736 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:67721fcfb3198f220c90c976f870407a0bb8d6c6) Merged-In: I13547f853476bc88d12026c545aba9f857ce8724 Change-Id: I13547f853476bc88d12026c545aba9f857ce8724 --- .../NotificationManagerService.java | 40 +++++++++-- .../NotificationManagerServiceTest.java | 68 +++++++++++++++++++ 2 files changed, 104 insertions(+), 4 deletions(-) diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index e09491867f91..d769ff8e7505 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -744,17 +744,21 @@ public class NotificationManagerService extends SystemService { private static final class ToastRecord { + final int uid; final int pid; final String pkg; + final boolean isSystemToast; final ITransientNotification callback; int duration; int displayId; Binder token; - ToastRecord(int pid, String pkg, ITransientNotification callback, int duration, + ToastRecord(int uid, int pid, String pkg, boolean isSystemToast, ITransientNotification callback, int duration, Binder token, int displayId) { + this.uid = uid; this.pid = pid; this.pkg = pkg; + this.isSystemToast = isSystemToast; this.callback = callback; this.duration = duration; this.token = token; @@ -2481,10 +2485,21 @@ public class NotificationManagerService extends SystemService { Binder token = new Binder(); mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, displayId); - record = new ToastRecord(callingPid, pkg, callback, duration, token, + record = new ToastRecord(callingUid, callingPid, pkg, isSystemToast, callback, duration, token, displayId); - mToastQueue.add(record); - index = mToastQueue.size() - 1; + + // Insert system toasts at the front of the queue + int systemToastInsertIdx = mToastQueue.size(); + if (isSystemToast) { + systemToastInsertIdx = getInsertIndexForSystemToastLocked(); + } + if (systemToastInsertIdx < mToastQueue.size()) { + index = systemToastInsertIdx; + mToastQueue.add(index, record); + } else { + mToastQueue.add(record); + index = mToastQueue.size() - 1; + } keepProcessAliveIfNeededLocked(callingPid); } // If it's at index 0, it's the current toast. It doesn't matter if it's @@ -2500,6 +2515,23 @@ public class NotificationManagerService extends SystemService { } } + @GuardedBy("mToastQueue") + private int getInsertIndexForSystemToastLocked() { + // If there are other system toasts: insert after the last one + int idx = 0; + for (ToastRecord r : mToastQueue) { + if (idx == 0 && mIsCurrentToastShown) { + idx++; + continue; + } + if (!r.isSystemToast) { + return idx; + } + idx++; + } + return idx; + } + @Override public void cancelToast(String pkg, ITransientNotification callback) { Slog.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index bcb4c49336cf..4a0aa7c9e8e7 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4480,6 +4480,74 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(1, mService.mToastQueue.size()); } + @Test + public void testPrioritizeSystemToasts() throws Exception { + // Insert non-system toasts + final String testPackage = "testPackageName"; + assertEquals(0, mService.mToastQueue.size()); + mService.isSystemUid = false; + mService.isSystemAppId = false; + setToastRateIsWithinQuota(true); + setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false); + + // package is not suspended + when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) + .thenReturn(false); + + INotificationManager nmService = (INotificationManager) mService.mService; + + // Enqueue maximum number of toasts for test package + for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_TOASTS; i++) { + nmService.enqueueTextToast(testPackage, new Binder(), "Text", 2000, 0, null); + } + + // Enqueue system toast + final String testPackageSystem = "testPackageNameSystem"; + mService.isSystemUid = true; + setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem, false); + when(mPackageManager.isPackageSuspendedForUser(testPackageSystem, UserHandle.getUserId(mUid))) + .thenReturn(false); + + nmService.enqueueToast(testPackageSystem, new Binder(), new TestableToastCallback(), 2000, 0); + + // System toast is inserted at the front of the queue, behind current showing toast + assertEquals(testPackageSystem, mService.mToastQueue.get(1).pkg); + } + + @Test + public void testPrioritizeSystemToasts_enqueueAfterExistingSystemToast() throws Exception { + // Insert system toasts + final String testPackageSystem1 = "testPackageNameSystem1"; + assertEquals(0, mService.mToastQueue.size()); + mService.isSystemUid = true; + setToastRateIsWithinQuota(true); + setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem1, false); + + // package is not suspended + when(mPackageManager.isPackageSuspendedForUser(testPackageSystem1, UserHandle.getUserId(mUid))) + .thenReturn(false); + + INotificationManager nmService = (INotificationManager) mService.mService; + + // Enqueue maximum number of toasts for test package + for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_TOASTS; i++) { + nmService.enqueueTextToast(testPackageSystem1, new Binder(), "Text", 2000, 0, null); + } + + // Enqueue another system toast + final String testPackageSystem2 = "testPackageNameSystem2"; + mService.isSystemUid = true; + setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem2, false); + when(mPackageManager.isPackageSuspendedForUser(testPackageSystem2, UserHandle.getUserId(mUid))) + .thenReturn(false); + + nmService.enqueueToast(testPackageSystem2, new Binder(), new TestableToastCallback(), 2000, 0); + + // System toast is inserted at the back of the queue, after the other system toasts + assertEquals(testPackageSystem2, + mService.mToastQueue.get(mService.mToastQueue.size() - 1).pkg); + } + @Test public void testOnNotificationSmartReplySent() { final int replyIndex = 2;