From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jan Tomljanovic <jtomljanovic@google.com>
Date: Fri, 6 Nov 2020 11:28:09 +0000
Subject: [PATCH] Don't try to show the current toast again while it's showing.

By doing this we avoid a few bad things:
 - mechanism that hides the current toast by trying to show it again
 - delaying the call to hide and remove the current toast from the queue
   when it's duration expires (which in the case of repeated calls can
   delay this indefinitely)

Test: atest NotificationManagerServiceTest
Test: atest android.widget.cts.ToastTest
Bug: 167672740
Change-Id: Ie4953109314113efae49fa0c5e0c236e6e0dbb23
---
 .../NotificationManagerService.java           | 13 +++++
 .../NotificationManagerServiceTest.java       | 53 +++++++++++++++++++
 2 files changed, 66 insertions(+)

diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 32873445b5e6..11c89bbdf4d9 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -493,6 +493,10 @@ public class NotificationManagerService extends SystemService {
 
     private KeyguardManager mKeyguardManager;
 
+    // True if the toast that's on top of the queue is being shown at the moment.
+    @GuardedBy("mToastQueue")
+    private boolean mIsCurrentToastShown = false;
+
     // The last key in this list owns the hardware.
     ArrayList<String> mLights = new ArrayList<>();
 
@@ -7444,10 +7448,15 @@ public class NotificationManagerService extends SystemService {
 
     @GuardedBy("mToastQueue")
     void showNextToastLocked() {
+        if (mIsCurrentToastShown) {
+            return; // Don't show the same toast twice.
+        }
+
         ToastRecord record = mToastQueue.get(0);
         while (record != null) {
             if (record.show()) {
                 scheduleDurationReachedLocked(record);
+                mIsCurrentToastShown = true;
                 return;
             }
             int index = mToastQueue.indexOf(record);
@@ -7463,6 +7472,10 @@ public class NotificationManagerService extends SystemService {
         ToastRecord record = mToastQueue.get(index);
         record.hide();
 
+        if (index == 0) {
+            mIsCurrentToastShown = false;
+        }
+
         ToastRecord lastToast = mToastQueue.remove(index);
 
         mWindowManagerInternal.removeWindowToken(lastToast.windowToken, false /* removeWindows */,
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 9ed2b17de423..3a108144ad66 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5341,6 +5341,32 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
         assertEquals(0, mService.mToastQueue.size());
     }
 
+    @Test
+    public void testDontCallShowToastAgainOnTheSameCustomToast() throws Exception {
+        final String testPackage = "testPackageName";
+        assertEquals(0, mService.mToastQueue.size());
+        mService.isSystemUid = false;
+
+        // package is not suspended
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+                .thenReturn(false);
+
+        setAppInForegroundForToasts(mUid, true);
+
+        Binder token = new Binder();
+        ITransientNotification callback = mock(ITransientNotification.class);
+        INotificationManager nmService = (INotificationManager) mService.mService;
+
+        // first time trying to show the toast, showToast gets called
+        nmService.enqueueToast(testPackage, token, callback, 2000, 0);
+        verify(callback, times(1)).show(any());
+
+        // second time trying to show the same toast, showToast isn't called again (total number of
+        // invocations stays at one)
+        nmService.enqueueToast(testPackage, token, callback, 2000, 0);
+        verify(callback, times(1)).show(any());
+    }
+
     @Test
     public void testAllowForegroundTextToasts() throws Exception {
         final String testPackage = "testPackageName";
@@ -5377,6 +5403,33 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
         assertEquals(1, mService.mToastQueue.size());
     }
 
+    @Test
+    public void testDontCallShowToastAgainOnTheSameTextToast() throws Exception {
+        final String testPackage = "testPackageName";
+        assertEquals(0, mService.mToastQueue.size());
+        mService.isSystemUid = false;
+
+        // package is not suspended
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+                .thenReturn(false);
+
+        setAppInForegroundForToasts(mUid, true);
+
+        Binder token = new Binder();
+        INotificationManager nmService = (INotificationManager) mService.mService;
+
+        // first time trying to show the toast, showToast gets called
+        nmService.enqueueTextToast(testPackage, token, "Text", 2000, 0, null);
+        verify(mStatusBar, times(1))
+                .showToast(anyInt(), any(), any(), any(), any(), anyInt(), any());
+
+        // second time trying to show the same toast, showToast isn't called again (total number of
+        // invocations stays at one)
+        nmService.enqueueTextToast(testPackage, token, "Text", 2000, 0, null);
+        verify(mStatusBar, times(1))
+                .showToast(anyInt(), any(), any(), any(), any(), anyInt(), any());
+    }
+
     @Test
     public void testTextToastsCallStatusBar() throws Exception {
         final String testPackage = "testPackageName";
