diff options
14 files changed, 144 insertions, 50 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 0eb3b49ac8b1..cfffaf0d230f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -5808,6 +5808,8 @@ package android.app { method @Nullable public android.graphics.drawable.Icon getIcon(); method @Nullable public android.app.PendingIntent getIntent(); method @Nullable public String getShortcutId(); + method public boolean isBubbleSuppressable(); + method public boolean isBubbleSuppressed(); method public boolean isNotificationSuppressed(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.Notification.BubbleMetadata> CREATOR; @@ -5824,6 +5826,7 @@ package android.app { method @NonNull public android.app.Notification.BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int); method @NonNull public android.app.Notification.BubbleMetadata.Builder setIcon(@NonNull android.graphics.drawable.Icon); method @NonNull public android.app.Notification.BubbleMetadata.Builder setIntent(@NonNull android.app.PendingIntent); + method @NonNull public android.app.Notification.BubbleMetadata.Builder setSuppressBubble(boolean); method @NonNull public android.app.Notification.BubbleMetadata.Builder setSuppressNotification(boolean); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 899cdb5eb572..57b84fcca742 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -9789,6 +9789,22 @@ public class Notification implements Parcelable */ public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002; + /** + * Indicates whether the bubble should be visually suppressed from the bubble stack if the + * user is viewing the same content outside of the bubble. For example, the user has a + * bubble with Alice and then opens up the main app and navigates to Alice's page. + * + * @hide + */ + public static final int FLAG_SHOULD_SUPPRESS_BUBBLE = 0x00000004; + + /** + * Indicates whether the bubble is visually suppressed from the bubble stack. + * + * @hide + */ + public static final int FLAG_SUPPRESS_BUBBLE = 0x00000008; + private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId, String shortcutId) { mPendingIntent = expandIntent; @@ -9931,6 +9947,32 @@ public class Notification implements Parcelable return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0; } + /** + * Indicates whether the bubble should be visually suppressed from the bubble stack if the + * user is viewing the same content outside of the bubble. For example, the user has a + * bubble with Alice and then opens up the main app and navigates to Alice's page. + * + * To match the activity and the bubble notification, the bubble notification should + * have a locus id set that matches a locus id set on the activity. + * + * @return whether this bubble should be suppressed when the same content is visible + * outside of the bubble. + * + * @see BubbleMetadata.Builder#setSuppressBubble(boolean) + */ + public boolean isBubbleSuppressable() { + return (mFlags & FLAG_SHOULD_SUPPRESS_BUBBLE) != 0; + } + + /** + * Indicates whether the bubble is currently visually suppressed from the bubble stack. + * + * @see BubbleMetadata.Builder#setSuppressBubble(boolean) + */ + public boolean isBubbleSuppressed() { + return (mFlags & FLAG_SUPPRESS_BUBBLE) != 0; + } + public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR = new Parcelable.Creator<BubbleMetadata>() { @@ -10273,6 +10315,23 @@ public class Notification implements Parcelable } /** + * Indicates whether the bubble should be visually suppressed from the bubble stack if + * the user is viewing the same content outside of the bubble. For example, the user has + * a bubble with Alice and then opens up the main app and navigates to Alice's page. + * + * To match the activity and the bubble notification, the bubble notification should + * have a locus id set that matches a locus id set on the activity. + * + * {@link Notification.Builder#setLocusId(LocusId)} + * {@link Activity#setLocusContext(LocusId, Bundle)} + */ + @NonNull + public BubbleMetadata.Builder setSuppressBubble(boolean suppressBubble) { + setFlag(FLAG_SHOULD_SUPPRESS_BUBBLE, suppressBubble); + return this; + } + + /** * Sets an intent to send when this bubble is explicitly removed by the user. * * <p>Setting a delete intent is optional.</p> diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 7edc6c855ec2..3e63ea6454a0 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -79,7 +79,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 isSuppressed); + void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed, boolean isBubbleSuppressed); void hideCurrentInputMethodForBubbles(); void grantInlineReplyUriPermission(String key, in Uri uri, in UserHandle user, String packageName); 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 8697be9db3fd..728d1c52e3a4 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 @@ -68,7 +68,7 @@ public class Bubble implements BubbleViewProvider { private long mLastAccessed; @Nullable - private Bubbles.NotificationSuppressionChangedListener mSuppressionListener; + private Bubbles.SuppressionChangedListener mSuppressionListener; /** Whether the bubble should show a dot for the notification indicating updated content. */ private boolean mShowBubbleUpdateDot = true; @@ -184,7 +184,7 @@ public class Bubble implements BubbleViewProvider { @VisibleForTesting(visibility = PRIVATE) Bubble(@NonNull final BubbleEntry entry, - @Nullable final Bubbles.NotificationSuppressionChangedListener listener, + @Nullable final Bubbles.SuppressionChangedListener listener, final Bubbles.PendingIntentCanceledListener intentCancelListener, Executor mainExecutor) { mKey = entry.getKey(); @@ -550,6 +550,13 @@ public class Bubble implements BubbleViewProvider { } /** + * Whether this bubble is currently being hidden from the stack. + */ + boolean isSuppressed() { + return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE) != 0; + } + + /** * Whether this notification conversation is important. */ boolean isImportantConversation() { 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 047df5ba7ca9..f5faea34b155 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 @@ -16,7 +16,6 @@ package com.android.wm.shell.bubbles; -import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; @@ -28,7 +27,6 @@ import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_BOT import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_LEFT; import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_NONE; import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_RIGHT; -import static com.android.wm.shell.bubbles.Bubbles.DISMISS_AGED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_GROUP_CANCELLED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_INVALID_INTENT; @@ -42,7 +40,6 @@ import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager; -import android.app.ActivityTaskManager; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; @@ -243,15 +240,15 @@ public class BubbleController { mBubbleData = data; mBubbleData.setListener(mBubbleDataListener); mBubbleData.setSuppressionChangedListener(bubble -> { - // Make sure NoMan knows it's not showing in the shade anymore so anyone querying it - // can tell. + // Make sure NoMan knows suppression state so that anyone querying it can tell. try { mBarService.onBubbleNotificationSuppressionChanged(bubble.getKey(), - !bubble.showInShade()); + !bubble.showInShade(), bubble.isSuppressed()); } catch (RemoteException e) { // Bad things have happened } }); + mBubbleData.setPendingIntentCancelledListener(bubble -> { if (bubble.getBubbleIntent() == null) { return; 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 8e061e9c9874..98978b562011 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 @@ -234,8 +234,8 @@ public interface Bubbles { void onBubbleExpandChanged(boolean isExpanding, String key); } - /** Listener to be notified when a bubbles' notification suppression state changes.*/ - interface NotificationSuppressionChangedListener { + /** 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); } 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 d3a736e9153e..9a80a5545984 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 @@ -106,7 +106,7 @@ public class BubbleDataTest extends ShellTestCase { private ArgumentCaptor<BubbleData.Update> mUpdateCaptor; @Mock - private Bubbles.NotificationSuppressionChangedListener mSuppressionListener; + private Bubbles.SuppressionChangedListener mSuppressionListener; @Mock private Bubbles.PendingIntentCanceledListener mPendingIntentCanceledListener; 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 fc828b30279f..819a984b4a77 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.NotificationSuppressionChangedListener mSuppressionListener; + private Bubbles.SuppressionChangedListener mSuppressionListener; @Before public void setUp() { 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 f1fc0b7723fc..9b9937b9e260 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -955,8 +955,8 @@ public class BubblesTest extends SysuiTestCase { @Test public void testNotifyShadeSuppressionChange_notificationDismiss() { - Bubbles.NotificationSuppressionChangedListener listener = - mock(Bubbles.NotificationSuppressionChangedListener.class); + Bubbles.SuppressionChangedListener listener = + mock(Bubbles.SuppressionChangedListener.class); mBubbleData.setSuppressionChangedListener(listener); mEntryListener.onPendingEntryAdded(mRow.getEntry()); @@ -979,8 +979,8 @@ public class BubblesTest extends SysuiTestCase { @Test public void testNotifyShadeSuppressionChange_bubbleExpanded() { - Bubbles.NotificationSuppressionChangedListener listener = - mock(Bubbles.NotificationSuppressionChangedListener.class); + Bubbles.SuppressionChangedListener listener = + mock(Bubbles.SuppressionChangedListener.class); mBubbleData.setSuppressionChangedListener(listener); mEntryListener.onPendingEntryAdded(mRow.getEntry()); 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 9e10b21ce3b7..b0ec628b638b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java @@ -789,8 +789,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { @Test public void testNotifyShadeSuppressionChange_notificationDismiss() { - Bubbles.NotificationSuppressionChangedListener listener = - mock(Bubbles.NotificationSuppressionChangedListener.class); + Bubbles.SuppressionChangedListener listener = + mock(Bubbles.SuppressionChangedListener.class); mBubbleData.setSuppressionChangedListener(listener); mEntryListener.onEntryAdded(mRow.getEntry()); @@ -812,8 +812,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { @Test public void testNotifyShadeSuppressionChange_bubbleExpanded() { - Bubbles.NotificationSuppressionChangedListener listener = - mock(Bubbles.NotificationSuppressionChangedListener.class); + Bubbles.SuppressionChangedListener listener = + mock(Bubbles.SuppressionChangedListener.class); mBubbleData.setSuppressionChangedListener(listener); mEntryListener.onEntryAdded(mRow.getEntry()); diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index 160d2daab6a2..4b51d7cd805e 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -55,9 +55,10 @@ public interface NotificationDelegate { void onNotificationBubbleChanged(String key, boolean isBubble, int flags); /** * Called when the state of {@link Notification.BubbleMetadata#FLAG_SUPPRESS_NOTIFICATION} - * changes. + * or {@link Notification.BubbleMetadata#FLAG_SUPPRESS_BUBBLE} changes. */ - void onBubbleNotificationSuppressionChanged(String key, boolean isSuppressed); + void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed, + boolean isBubbleSuppressed); /** * 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 8b4c6392fec0..27db16ec7a1c 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1261,7 +1261,7 @@ public class NotificationManagerService extends SystemService { } @Override - public void onNotificationBubbleChanged(String key, boolean isBubble, int flags) { + public void onNotificationBubbleChanged(String key, boolean isBubble, int bubbleFlags) { synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { @@ -1279,7 +1279,7 @@ public class NotificationManagerService extends SystemService { r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE; r.setFlagBubbleRemoved(false); if (r.getNotification().getBubbleMetadata() != null) { - r.getNotification().getBubbleMetadata().setFlags(flags); + r.getNotification().getBubbleMetadata().setFlags(bubbleFlags); } // Force isAppForeground true here, because for sysui's purposes we // want to adjust the flag behaviour. @@ -1291,7 +1291,8 @@ public class NotificationManagerService extends SystemService { } @Override - public void onBubbleNotificationSuppressionChanged(String key, boolean isSuppressed) { + public void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed, + boolean isBubbleSuppressed) { synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { @@ -1300,26 +1301,36 @@ public class NotificationManagerService extends SystemService { // No data, do nothing return; } - boolean currentlySuppressed = data.isNotificationSuppressed(); - if (currentlySuppressed == isSuppressed) { - // No changes, do nothing - return; - } + int flags = data.getFlags(); - if (isSuppressed) { - flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; - } else { - flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + boolean flagChanged = false; + if (data.isNotificationSuppressed() != isNotifSuppressed) { + flagChanged = true; + if (isNotifSuppressed) { + flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + } else { + flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + } + } + if (data.isBubbleSuppressed() != isBubbleSuppressed) { + flagChanged = true; + if (isBubbleSuppressed) { + flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; + } else { + flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; + } + } + if (flagChanged) { + data.setFlags(flags); + r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE; + mHandler.post( + new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r, + true /* isAppForeground */)); } - data.setFlags(flags); - r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE; - mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r, - true /* isAppForeground */)); } } } - @Override /** * Grant permission to read the specified URI to the package specified in the * NotificationRecord associated with the given key. The callingUid represents the UID of @@ -1329,6 +1340,7 @@ public class NotificationManagerService extends SystemService { * user associated with the NotificationRecord, and this grant will fail when trying * to grant URI permissions across users. */ + @Override public void grantInlineReplyUriPermission(String key, Uri uri, UserHandle user, String packageName, int callingUid) { synchronized (mNotificationLock) { @@ -6108,18 +6120,27 @@ public class NotificationManagerService extends SystemService { } /** - * Some bubble specific flags only work if the app is foreground, this will strip those flags - * if the app wasn't foreground. + * Strips any flags from BubbleMetadata that wouldn't apply (e.g. app not foreground). */ private void updateNotificationBubbleFlags(NotificationRecord r, boolean isAppForeground) { - // Remove any bubble specific flags that only work when foregrounded Notification notification = r.getNotification(); Notification.BubbleMetadata metadata = notification.getBubbleMetadata(); - if (!isAppForeground && metadata != null) { + if (metadata == null) { + // Nothing to update + return; + } + if (!isAppForeground) { + // Auto expand only works if foreground int flags = metadata.getFlags(); flags &= ~Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE; metadata.setFlags(flags); } + if (!metadata.isBubbleSuppressable()) { + // If it's not suppressable remove the suppress flag + int flags = metadata.getFlags(); + flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; + metadata.setFlags(flags); + } } private ShortcutHelper.ShortcutListener mShortcutListener = @@ -6495,9 +6516,11 @@ public class NotificationManagerService extends SystemService { } if (mReason == REASON_LISTENER_CANCEL - && (r.getNotification().flags & FLAG_BUBBLE) != 0) { + && r.getNotification().isBubbleNotification()) { + boolean isBubbleSuppressed = r.getNotification().getBubbleMetadata() != null + && r.getNotification().getBubbleMetadata().isBubbleSuppressed(); mNotificationDelegate.onBubbleNotificationSuppressionChanged( - r.getKey(), /* suppressed */ true); + r.getKey(), true /* suppressed */, isBubbleSuppressed); return; } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index a390df9edae1..2da489a05eca 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -1453,11 +1453,13 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed) { + public void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed, + boolean isBubbleSuppressed) { enforceStatusBarService(); final long identity = Binder.clearCallingIdentity(); try { - mNotificationDelegate.onBubbleNotificationSuppressionChanged(key, isNotifSuppressed); + mNotificationDelegate.onBubbleNotificationSuppressionChanged(key, isNotifSuppressed, + isBubbleSuppressed); } 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 a64050996d42..662cc0e3dc56 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -6207,7 +6207,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { reset(mListeners); // Test: update suppression to true - mService.mNotificationDelegate.onBubbleNotificationSuppressionChanged(nr.getKey(), true); + mService.mNotificationDelegate.onBubbleNotificationSuppressionChanged(nr.getKey(), true, + false); waitForIdle(); // Check @@ -6218,7 +6219,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { reset(mListeners); // Test: update suppression to false - mService.mNotificationDelegate.onBubbleNotificationSuppressionChanged(nr.getKey(), false); + mService.mNotificationDelegate.onBubbleNotificationSuppressionChanged(nr.getKey(), false, + false); waitForIdle(); // Check |