From d0ac45a71f29a97500db58eec9bbc681c362d600 Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Thu, 24 Mar 2022 22:51:25 +0000 Subject: [PATCH] If something is suppressed from the notification list don't bubble it Also set it to not auto-expand since it's unexpected to have bubbles expand when DND turns off. Test: BubblesTest NewNotifPipelineBubblesTest Bug: 226316876 Change-Id: Ieb77b211f2b11fea83a2ff6e34e6f31b0f96919e --- .../wm/shell/bubbles/BubbleController.java | 33 +++++++++---- .../android/wm/shell/bubbles/BubbleData.java | 16 +++++++ .../android/systemui/wmshell/BubblesTest.java | 48 +++++++++++++++++++ .../wmshell/NewNotifPipelineBubblesTest.java | 48 +++++++++++++++++++ 4 files changed, 135 insertions(+), 10 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 7fed064eaec3e..f407bdcb8852c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1037,7 +1037,15 @@ public class BubbleController { } } else { Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */); - inflateAndAdd(bubble, suppressFlyout, showInShade); + if (notif.shouldSuppressNotificationList()) { + // If we're suppressing notifs for DND, we don't want the bubbles to randomly + // expand when DND turns off so flip the flag. + if (bubble.shouldAutoExpand()) { + bubble.setShouldAutoExpand(false); + } + } else { + inflateAndAdd(bubble, suppressFlyout, showInShade); + } } } @@ -1069,7 +1077,8 @@ public class BubbleController { } } - private void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) { + @VisibleForTesting + public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) { // shouldBubbleUp checks canBubble & for bubble metadata boolean shouldBubble = shouldBubbleUp && canLaunchInTaskView(mContext, entry); if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) { @@ -1095,7 +1104,8 @@ public class BubbleController { } } - private void onRankingUpdated(RankingMap rankingMap, + @VisibleForTesting + public void onRankingUpdated(RankingMap rankingMap, HashMap> entryDataByKey) { if (mTmpRanking == null) { mTmpRanking = new NotificationListenerService.Ranking(); @@ -1106,19 +1116,22 @@ public class BubbleController { Pair entryData = entryDataByKey.get(key); BubbleEntry entry = entryData.first; boolean shouldBubbleUp = entryData.second; - if (entry != null && !isCurrentProfile( entry.getStatusBarNotification().getUser().getIdentifier())) { return; } - + if (entry != null && (entry.shouldSuppressNotificationList() + || entry.getRanking().isSuspended())) { + shouldBubbleUp = false; + } rankingMap.getRanking(key, mTmpRanking); - boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key); - if (isActiveBubble && !mTmpRanking.canBubble()) { + boolean isActiveOrInOverflow = mBubbleData.hasAnyBubbleWithKey(key); + boolean isActive = mBubbleData.hasBubbleInStackWithKey(key); + if (isActiveOrInOverflow && !mTmpRanking.canBubble()) { // If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason. // This means that the app or channel's ability to bubble has been revoked. mBubbleData.dismissBubbleWithKey(key, DISMISS_BLOCKED); - } else if (isActiveBubble && (!shouldBubbleUp || entry.getRanking().isSuspended())) { + } else if (isActiveOrInOverflow && !shouldBubbleUp) { // If this entry is allowed to bubble, but cannot currently bubble up or is // suspended, dismiss it. This happens when DND is enabled and configured to hide // bubbles, or focus mode is enabled and the app is designated as distracting. @@ -1126,9 +1139,9 @@ public class BubbleController { // notification, so that the bubble will be re-created if shouldBubbleUp returns // true. mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP); - } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) { + } else if (entry != null && mTmpRanking.isBubble() && !isActive) { entry.setFlagBubble(true); - onEntryUpdated(entry, shouldBubbleUp && !entry.getRanking().isSuspended()); + onEntryUpdated(entry, shouldBubbleUp); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index 5cff42187fcad..e4a0fd03860c6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -1057,6 +1057,22 @@ public class BubbleData { return null; } + /** + * Get a pending bubble with given notification key + * + * @param key notification key + * @return bubble that matches or null + */ + @VisibleForTesting(visibility = PRIVATE) + public Bubble getPendingBubbleWithKey(String key) { + for (Bubble b : mPendingBubbles.values()) { + if (b.getKey().equals(key)) { + return b; + } + } + return null; + } + @VisibleForTesting(visibility = PRIVATE) void setTimeSource(TimeSource timeSource) { mTimeSource = timeSource; diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index ad7bdd9b5aa03..238a4d37a8727 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -57,6 +57,7 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; @@ -70,6 +71,8 @@ import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.Pair; +import android.util.SparseArray; import android.view.View; import android.view.WindowManager; @@ -147,6 +150,7 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.HashMap; import java.util.List; import java.util.Optional; @@ -1466,6 +1470,50 @@ public class BubblesTest extends SysuiTestCase { verify(mBubbleController).onBubbleMetadataFlagChanged(b); } + @Test + public void testUpdateBubble_skipsDndSuppressListNotifs() { + mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(), + mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */, + mRow.shouldSuppressPeek()); + mBubbleEntry.getBubbleMetadata().setFlags( + Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + + mBubbleController.updateBubble(mBubbleEntry); + + Bubble b = mBubbleData.getPendingBubbleWithKey(mBubbleEntry.getKey()); + assertThat(b.shouldAutoExpand()).isFalse(); + assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey())).isNull(); + } + + @Test + public void testOnRankingUpdate_DndSuppressListNotif() { + // It's in the stack + mBubbleController.updateBubble(mBubbleEntry); + assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isTrue(); + + // Set current user profile + SparseArray userInfos = new SparseArray<>(); + userInfos.put(mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(), + mock(UserInfo.class)); + mBubbleController.onCurrentProfilesChanged(userInfos); + + // Send ranking update that the notif is suppressed from the list. + HashMap> entryDataByKey = new HashMap<>(); + mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(), + mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */, + mRow.shouldSuppressPeek()); + Pair pair = new Pair(mBubbleEntry, true); + entryDataByKey.put(mBubbleEntry.getKey(), pair); + + NotificationListenerService.RankingMap rankingMap = + mock(NotificationListenerService.RankingMap.class); + when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() }); + mBubbleController.onRankingUpdated(rankingMap, entryDataByKey); + + // Should no longer be in the stack + assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse(); + } + /** Creates a bubble using the userId and package. */ private Bubble createBubble(int userId, String pkg) { final UserHandle userHandle = new UserHandle(userId); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java index 55aad9875903b..dff89e0a5558c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java @@ -50,6 +50,7 @@ import android.content.BroadcastReceiver; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.LauncherApps; +import android.content.pm.UserInfo; import android.hardware.display.AmbientDisplayConfiguration; import android.os.Handler; import android.os.PowerManager; @@ -59,6 +60,8 @@ import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.Pair; +import android.util.SparseArray; import android.view.View; import android.view.WindowManager; @@ -128,6 +131,7 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.HashMap; import java.util.List; import java.util.Optional; @@ -1286,6 +1290,50 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { verify(mBubbleController).onBubbleMetadataFlagChanged(b); } + @Test + public void testUpdateBubble_skipsDndSuppressListNotifs() { + mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(), + mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */, + mRow.shouldSuppressPeek()); + mBubbleEntry.getBubbleMetadata().setFlags( + Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + + mBubbleController.updateBubble(mBubbleEntry); + + Bubble b = mBubbleData.getPendingBubbleWithKey(mBubbleEntry.getKey()); + assertThat(b.shouldAutoExpand()).isFalse(); + assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey())).isNull(); + } + + @Test + public void testOnRankingUpdate_DndSuppressListNotif() { + // It's in the stack + mBubbleController.updateBubble(mBubbleEntry); + assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isTrue(); + + // Set current user profile + SparseArray userInfos = new SparseArray<>(); + userInfos.put(mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(), + mock(UserInfo.class)); + mBubbleController.onCurrentProfilesChanged(userInfos); + + // Send ranking update that the notif is suppressed from the list. + HashMap> entryDataByKey = new HashMap<>(); + mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(), + mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */, + mRow.shouldSuppressPeek()); + Pair pair = new Pair(mBubbleEntry, true); + entryDataByKey.put(mBubbleEntry.getKey(), pair); + + NotificationListenerService.RankingMap rankingMap = + mock(NotificationListenerService.RankingMap.class); + when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() }); + mBubbleController.onRankingUpdated(rankingMap, entryDataByKey); + + // Should no longer be in the stack + assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse(); + } + /** * Sets the bubble metadata flags for this entry. These flags are normally set by * NotificationManagerService when the notification is sent, however, these tests do not