diff options
13 files changed, 280 insertions, 89 deletions
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 46b463074383..ef8f2db5ff57 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -88,7 +88,7 @@ interface IStatusBarService in int notificationLocation, boolean modifiedBeforeSending); void onNotificationSettingsViewed(String key); void onNotificationBubbleChanged(String key, boolean isBubble, int flags); - void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed, boolean isBubbleSuppressed); + void onBubbleMetadataFlagChanged(String key, int flags); void hideCurrentInputMethodForBubbles(); void grantInlineReplyUriPermission(String key, in Uri uri, in UserHandle user, String packageName); oneway void clearInlineReplyUriPermissions(String key); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 227494c04049..31fc6a5be589 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -71,7 +71,7 @@ public class Bubble implements BubbleViewProvider { private long mLastAccessed; @Nullable - private Bubbles.SuppressionChangedListener mSuppressionListener; + private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener; /** Whether the bubble should show a dot for the notification indicating updated content. */ private boolean mShowBubbleUpdateDot = true; @@ -192,13 +192,13 @@ public class Bubble implements BubbleViewProvider { @VisibleForTesting(visibility = PRIVATE) public Bubble(@NonNull final BubbleEntry entry, - @Nullable final Bubbles.SuppressionChangedListener listener, + @Nullable final Bubbles.BubbleMetadataFlagListener listener, final Bubbles.PendingIntentCanceledListener intentCancelListener, Executor mainExecutor) { mKey = entry.getKey(); mGroupKey = entry.getGroupKey(); mLocusId = entry.getLocusId(); - mSuppressionListener = listener; + mBubbleMetadataFlagListener = listener; mIntentCancelListener = intent -> { if (mIntent != null) { mIntent.unregisterCancelListener(mIntentCancelListener); @@ -606,8 +606,8 @@ public class Bubble implements BubbleViewProvider { mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; } - if (showInShade() != prevShowInShade && mSuppressionListener != null) { - mSuppressionListener.onBubbleNotificationSuppressionChange(this); + if (showInShade() != prevShowInShade && mBubbleMetadataFlagListener != null) { + mBubbleMetadataFlagListener.onBubbleMetadataFlagChanged(this); } } @@ -626,8 +626,8 @@ public class Bubble implements BubbleViewProvider { } else { mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; } - if (prevSuppressed != suppressBubble && mSuppressionListener != null) { - mSuppressionListener.onBubbleNotificationSuppressionChange(this); + if (prevSuppressed != suppressBubble && mBubbleMetadataFlagListener != null) { + mBubbleMetadataFlagListener.onBubbleMetadataFlagChanged(this); } } @@ -771,12 +771,17 @@ public class Bubble implements BubbleViewProvider { return isEnabled(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); } - void setShouldAutoExpand(boolean shouldAutoExpand) { + @VisibleForTesting + public void setShouldAutoExpand(boolean shouldAutoExpand) { + boolean prevAutoExpand = shouldAutoExpand(); if (shouldAutoExpand) { enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); } else { disable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); } + if (prevAutoExpand != shouldAutoExpand && mBubbleMetadataFlagListener != null) { + mBubbleMetadataFlagListener.onBubbleMetadataFlagChanged(this); + } } public void setIsBubble(final boolean isBubble) { @@ -799,6 +804,10 @@ public class Bubble implements BubbleViewProvider { return (mFlags & option) != 0; } + public int getFlags() { + return mFlags; + } + @Override public String toString() { return "Bubble{" + mKey + '}'; 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 806c395bf395..f407bdcb8852 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 @@ -323,7 +323,7 @@ public class BubbleController { public void initialize() { mBubbleData.setListener(mBubbleDataListener); - mBubbleData.setSuppressionChangedListener(this::onBubbleNotificationSuppressionChanged); + mBubbleData.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged); mBubbleData.setPendingIntentCancelledListener(bubble -> { if (bubble.getBubbleIntent() == null) { @@ -554,11 +554,10 @@ public class BubbleController { } @VisibleForTesting - public void onBubbleNotificationSuppressionChanged(Bubble bubble) { + public void onBubbleMetadataFlagChanged(Bubble bubble) { // Make sure NoMan knows suppression state so that anyone querying it can tell. try { - mBarService.onBubbleNotificationSuppressionChanged(bubble.getKey(), - !bubble.showInShade(), bubble.isSuppressed()); + mBarService.onBubbleMetadataFlagChanged(bubble.getKey(), bubble.getFlags()); } catch (RemoteException e) { // Bad things have happened } @@ -1038,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); + } } } @@ -1070,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())) { @@ -1096,7 +1104,8 @@ public class BubbleController { } } - private void onRankingUpdated(RankingMap rankingMap, + @VisibleForTesting + public void onRankingUpdated(RankingMap rankingMap, HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) { if (mTmpRanking == null) { mTmpRanking = new NotificationListenerService.Ranking(); @@ -1107,19 +1116,22 @@ public class BubbleController { Pair<BubbleEntry, Boolean> 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. @@ -1127,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 c98c0e69de15..e4a0fd03860c 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 @@ -159,7 +159,7 @@ public class BubbleData { private Listener mListener; @Nullable - private Bubbles.SuppressionChangedListener mSuppressionListener; + private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener; private Bubbles.PendingIntentCanceledListener mCancelledListener; /** @@ -190,9 +190,8 @@ public class BubbleData { mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow); } - public void setSuppressionChangedListener( - Bubbles.SuppressionChangedListener listener) { - mSuppressionListener = listener; + public void setSuppressionChangedListener(Bubbles.BubbleMetadataFlagListener listener) { + mBubbleMetadataFlagListener = listener; } public void setPendingIntentCancelledListener( @@ -311,7 +310,7 @@ public class BubbleData { bubbleToReturn = mPendingBubbles.get(key); } else if (entry != null) { // New bubble - bubbleToReturn = new Bubble(entry, mSuppressionListener, mCancelledListener, + bubbleToReturn = new Bubble(entry, mBubbleMetadataFlagListener, mCancelledListener, mMainExecutor); } else { // Persisted bubble being promoted @@ -1058,6 +1057,22 @@ public class BubbleData { return null; } + /** + * Get a pending bubble with given notification <code>key</code> + * + * @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/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 2b2a2f7e35df..c7db8d8d1646 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -263,10 +263,10 @@ public interface Bubbles { void onBubbleExpandChanged(boolean isExpanding, String key); } - /** Listener to be notified when the flags for notification or bubble suppression changes.*/ - interface SuppressionChangedListener { - /** Called when the notification suppression state of a bubble changes. */ - void onBubbleNotificationSuppressionChange(Bubble bubble); + /** Listener to be notified when the flags on BubbleMetadata have changed. */ + interface BubbleMetadataFlagListener { + /** Called when the flags on BubbleMetadata have changed for the provided bubble. */ + void onBubbleMetadataFlagChanged(Bubble bubble); } /** Listener to be notified when a pending intent has been canceled for a bubble. */ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index 169f03e7bc3e..bde94d9d6c29 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -115,7 +115,7 @@ public class BubbleDataTest extends ShellTestCase { private ArgumentCaptor<BubbleData.Update> mUpdateCaptor; @Mock - private Bubbles.SuppressionChangedListener mSuppressionListener; + private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener; @Mock private Bubbles.PendingIntentCanceledListener mPendingIntentCanceledListener; @@ -136,30 +136,47 @@ public class BubbleDataTest extends ShellTestCase { mock(NotificationListenerService.Ranking.class); when(ranking.isTextChanged()).thenReturn(true); mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking); - mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null, + mBubbleInterruptive = new Bubble(mEntryInterruptive, mBubbleMetadataFlagListener, null, mMainExecutor); mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d", null); - mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener, null, + mBubbleDismissed = new Bubble(mEntryDismissed, mBubbleMetadataFlagListener, null, mMainExecutor); mEntryLocusId = createBubbleEntry(1, "keyLocus", "package.e", null, new LocusId("locusId1")); - mBubbleLocusId = new Bubble(mEntryLocusId, mSuppressionListener, null, mMainExecutor); + mBubbleLocusId = new Bubble(mEntryLocusId, + mBubbleMetadataFlagListener, + null /* pendingIntentCanceledListener */, + mMainExecutor); - mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener, mPendingIntentCanceledListener, + mBubbleA1 = new Bubble(mEntryA1, + mBubbleMetadataFlagListener, + mPendingIntentCanceledListener, mMainExecutor); - mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener, mPendingIntentCanceledListener, + mBubbleA2 = new Bubble(mEntryA2, + mBubbleMetadataFlagListener, + mPendingIntentCanceledListener, mMainExecutor); - mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener, mPendingIntentCanceledListener, + mBubbleA3 = new Bubble(mEntryA3, + mBubbleMetadataFlagListener, + mPendingIntentCanceledListener, mMainExecutor); - mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener, mPendingIntentCanceledListener, + mBubbleB1 = new Bubble(mEntryB1, + mBubbleMetadataFlagListener, + mPendingIntentCanceledListener, mMainExecutor); - mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener, mPendingIntentCanceledListener, + mBubbleB2 = new Bubble(mEntryB2, + mBubbleMetadataFlagListener, + mPendingIntentCanceledListener, mMainExecutor); - mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener, mPendingIntentCanceledListener, + mBubbleB3 = new Bubble(mEntryB3, + mBubbleMetadataFlagListener, + mPendingIntentCanceledListener, mMainExecutor); - mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener, + mBubbleC1 = new Bubble(mEntryC1, + mBubbleMetadataFlagListener, + mPendingIntentCanceledListener, mMainExecutor); mPositioner = new TestableBubblePositioner(mContext, mock(WindowManager.class)); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java index 819a984b4a77..e8f3f69ca64e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java @@ -63,7 +63,7 @@ public class BubbleTest extends ShellTestCase { private Bubble mBubble; @Mock - private Bubbles.SuppressionChangedListener mSuppressionListener; + private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener; @Before public void setUp() { @@ -81,7 +81,7 @@ public class BubbleTest extends ShellTestCase { when(mNotif.getBubbleMetadata()).thenReturn(metadata); when(mSbn.getKey()).thenReturn("mock"); mBubbleEntry = new BubbleEntry(mSbn, null, true, false, false, false); - mBubble = new Bubble(mBubbleEntry, mSuppressionListener, null, mMainExecutor); + mBubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor); } @Test @@ -144,22 +144,22 @@ public class BubbleTest extends ShellTestCase { } @Test - public void testSuppressionListener_change_notified() { + public void testBubbleMetadataFlagListener_change_notified() { assertThat(mBubble.showInShade()).isTrue(); mBubble.setSuppressNotification(true); assertThat(mBubble.showInShade()).isFalse(); - verify(mSuppressionListener).onBubbleNotificationSuppressionChange(mBubble); + verify(mBubbleMetadataFlagListener).onBubbleMetadataFlagChanged(mBubble); } @Test - public void testSuppressionListener_noChange_doesntNotify() { + public void testBubbleMetadataFlagListener_noChange_doesntNotify() { assertThat(mBubble.showInShade()).isTrue(); mBubble.setSuppressNotification(false); - verify(mSuppressionListener, never()).onBubbleNotificationSuppressionChange(any()); + verify(mBubbleMetadataFlagListener, never()).onBubbleMetadataFlagChanged(any()); } } 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 193879e5c55c..238a4d37a872 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; @@ -1010,7 +1014,7 @@ public class BubblesTest extends SysuiTestCase { assertBubbleNotificationSuppressedFromShade(mBubbleEntry); // Should notify delegate that shade state changed - verify(mBubbleController).onBubbleNotificationSuppressionChanged( + verify(mBubbleController).onBubbleMetadataFlagChanged( mBubbleData.getBubbleInStackWithKey(mRow.getKey())); } @@ -1027,7 +1031,7 @@ public class BubblesTest extends SysuiTestCase { assertBubbleNotificationSuppressedFromShade(mBubbleEntry); // Should notify delegate that shade state changed - verify(mBubbleController).onBubbleNotificationSuppressionChanged( + verify(mBubbleController).onBubbleMetadataFlagChanged( mBubbleData.getBubbleInStackWithKey(mRow.getKey())); } @@ -1447,6 +1451,69 @@ public class BubblesTest extends SysuiTestCase { assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE); } + @Test + public void testSetShouldAutoExpand_notifiesFlagChanged() { + mEntryListener.onPendingEntryAdded(mRow); + + assertTrue(mBubbleController.hasBubbles()); + Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey()); + assertThat(b.shouldAutoExpand()).isFalse(); + + // Set it to the same thing + b.setShouldAutoExpand(false); + + // Verify it doesn't notify + verify(mBubbleController, never()).onBubbleMetadataFlagChanged(any()); + + // Set it to something different + b.setShouldAutoExpand(true); + 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<UserInfo> 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<String, Pair<BubbleEntry, Boolean>> entryDataByKey = new HashMap<>(); + mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(), + mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */, + mRow.shouldSuppressPeek()); + Pair<BubbleEntry, Boolean> 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 02d869172030..dff89e0a5558 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; @@ -880,7 +884,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { assertBubbleNotificationSuppressedFromShade(mBubbleEntry); // Should notify delegate that shade state changed - verify(mBubbleController).onBubbleNotificationSuppressionChanged( + verify(mBubbleController).onBubbleMetadataFlagChanged( mBubbleData.getBubbleInStackWithKey(mRow.getKey())); } @@ -897,7 +901,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { assertBubbleNotificationSuppressedFromShade(mBubbleEntry); // Should notify delegate that shade state changed - verify(mBubbleController).onBubbleNotificationSuppressionChanged( + verify(mBubbleController).onBubbleMetadataFlagChanged( mBubbleData.getBubbleInStackWithKey(mRow.getKey())); } @@ -1267,6 +1271,69 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE); } + @Test + public void testSetShouldAutoExpand_notifiesFlagChanged() { + mBubbleController.updateBubble(mBubbleEntry); + + assertTrue(mBubbleController.hasBubbles()); + Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey()); + assertThat(b.shouldAutoExpand()).isFalse(); + + // Set it to the same thing + b.setShouldAutoExpand(false); + + // Verify it doesn't notify + verify(mBubbleController, never()).onBubbleMetadataFlagChanged(any()); + + // Set it to something different + b.setShouldAutoExpand(true); + 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<UserInfo> 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<String, Pair<BubbleEntry, Boolean>> entryDataByKey = new HashMap<>(); + mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(), + mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */, + mRow.shouldSuppressPeek()); + Pair<BubbleEntry, Boolean> 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 diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index 02f9ceb2d11d..89902f7f8321 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -51,14 +51,16 @@ public interface NotificationDelegate { void onNotificationSettingsViewed(String key); /** * Called when the state of {@link Notification#FLAG_BUBBLE} is changed. + * + * @param key the notification key + * @param isBubble whether the notification should have {@link Notification#FLAG_BUBBLE} applied + * @param flags the flags to apply to the notification's {@link Notification.BubbleMetadata} */ void onNotificationBubbleChanged(String key, boolean isBubble, int flags); /** - * Called when the state of {@link Notification.BubbleMetadata#FLAG_SUPPRESS_NOTIFICATION} - * or {@link Notification.BubbleMetadata#FLAG_SUPPRESS_BUBBLE} changes. + * Called when the flags on {@link Notification.BubbleMetadata} are changed. */ - void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed, - boolean isBubbleSuppressed); + void onBubbleMetadataFlagChanged(String key, int flags); /** * Grant permission to read the specified URI to the package associated with the diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 7710a25b95fc..83c576e9259d 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -18,6 +18,7 @@ package com.android.server.notification; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY; import static android.app.Notification.FLAG_BUBBLE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; @@ -1429,8 +1430,7 @@ public class NotificationManagerService extends SystemService { } @Override - public void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed, - boolean isBubbleSuppressed) { + public void onBubbleMetadataFlagChanged(String key, int flags) { synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { @@ -1440,17 +1440,12 @@ public class NotificationManagerService extends SystemService { return; } - boolean flagChanged = false; - if (data.isNotificationSuppressed() != isNotifSuppressed) { - flagChanged = true; - data.setSuppressNotification(isNotifSuppressed); - } - if (data.isBubbleSuppressed() != isBubbleSuppressed) { - flagChanged = true; - data.setSuppressBubble(isBubbleSuppressed); - } - if (flagChanged) { + if (flags != data.getFlags()) { + data.setFlags(flags); + // Shouldn't alert again just because of a flag change. r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE; + // Force isAppForeground true here, because for sysui's purposes we + // want to be able to adjust the flag behaviour. mHandler.post( new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r, true /* isAppForeground */, SystemClock.elapsedRealtime())); @@ -7182,10 +7177,12 @@ public class NotificationManagerService extends SystemService { && r.getNotification().isBubbleNotification()) || (mReason == REASON_CLICK && r.canBubble() && r.isFlagBubbleRemoved())) { - boolean isBubbleSuppressed = r.getNotification().getBubbleMetadata() != null - && r.getNotification().getBubbleMetadata().isBubbleSuppressed(); - mNotificationDelegate.onBubbleNotificationSuppressionChanged( - r.getKey(), true /* notifSuppressed */, isBubbleSuppressed); + int flags = 0; + if (r.getNotification().getBubbleMetadata() != null) { + flags = r.getNotification().getBubbleMetadata().getFlags(); + } + flags |= FLAG_SUPPRESS_NOTIFICATION; + mNotificationDelegate.onBubbleMetadataFlagChanged(r.getKey(), flags); return; } if ((r.getNotification().flags & mMustHaveFlags) != mMustHaveFlags) { diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index b6855726c122..d48f26332017 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -1650,13 +1650,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed, - boolean isBubbleSuppressed) { + public void onBubbleMetadataFlagChanged(String key, int flags) { enforceStatusBarService(); final long identity = Binder.clearCallingIdentity(); try { - mNotificationDelegate.onBubbleNotificationSuppressionChanged(key, isNotifSuppressed, - isBubbleSuppressed); + mNotificationDelegate.onBubbleMetadataFlagChanged(key, flags); } finally { Binder.restoreCallingIdentity(identity); } 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 d85c40ecebe8..c0cd7a755e25 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -7520,46 +7520,53 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testOnBubbleNotificationSuppressionChanged() throws Exception { + public void testOnBubbleMetadataFlagChanged() throws Exception { setUpPrefsForBubbles(PKG, mUid, true /* global */, BUBBLE_PREFERENCE_ALL /* app */, true /* channel */); - // Bubble notification + // Post a bubble notification NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag"); - + // Set this so that the bubble can be suppressed + nr.getNotification().getBubbleMetadata().setFlags( + Notification.BubbleMetadata.FLAG_SUPPRESSABLE_BUBBLE); mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); - // NOT suppressed + // Check the flags Notification n = mBinderService.getActiveNotifications(PKG)[0].getNotification(); assertFalse(n.getBubbleMetadata().isNotificationSuppressed()); + assertFalse(n.getBubbleMetadata().getAutoExpandBubble()); + assertFalse(n.getBubbleMetadata().isBubbleSuppressed()); + assertTrue(n.getBubbleMetadata().isBubbleSuppressable()); // Reset as this is called when the notif is first sent reset(mListeners); - // Test: update suppression to true - mService.mNotificationDelegate.onBubbleNotificationSuppressionChanged(nr.getKey(), true, - false); + // Test: change the flags + int flags = Notification.BubbleMetadata.FLAG_SUPPRESSABLE_BUBBLE; + flags |= Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE; + flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; + mService.mNotificationDelegate.onBubbleMetadataFlagChanged(nr.getKey(), flags); waitForIdle(); // Check n = mBinderService.getActiveNotifications(PKG)[0].getNotification(); - assertTrue(n.getBubbleMetadata().isNotificationSuppressed()); + assertEquals(flags, n.getBubbleMetadata().getFlags()); // Reset to check again reset(mListeners); - // Test: update suppression to false - mService.mNotificationDelegate.onBubbleNotificationSuppressionChanged(nr.getKey(), false, - false); + // Test: clear flags + mService.mNotificationDelegate.onBubbleMetadataFlagChanged(nr.getKey(), 0); waitForIdle(); // Check n = mBinderService.getActiveNotifications(PKG)[0].getNotification(); - assertFalse(n.getBubbleMetadata().isNotificationSuppressed()); + assertEquals(0, n.getBubbleMetadata().getFlags()); } @Test |