Allow SysUI to update BubbleMetadata#FLAG_AUTO_EXPAND_BUBBLE

This flag should only really be applied to a notification bubble
one time. Once the bubble has auto-expanded (or if it was posted and
unable to auto-expand at that time due to something like DND), the
flag should be removed so we don't auto-expand it again.

We have a method that allows us to modify some specific flags on
BubbleMetadata. This CL updates that method to be more general and
work with any of the BubbleMetadata flags.

Bug: 226316876
Test: atest NotificationManagerServiceTest
Change-Id: Ib9653ed304f13ac88f489146d503f4de4fa29421
This commit is contained in:
Mady Mellor
2022-04-26 17:04:59 +00:00
parent 3d790a1673
commit 40422db463
13 changed files with 101 additions and 78 deletions

View File

@@ -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);

View File

@@ -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);
}
}
@@ -799,6 +799,10 @@ public class Bubble implements BubbleViewProvider {
return (mFlags & option) != 0;
}
public int getFlags() {
return mFlags;
}
@Override
public String toString() {
return "Bubble{" + mKey + '}';

View File

@@ -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
}

View File

@@ -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

View File

@@ -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. */

View File

@@ -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));

View File

@@ -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());
}
}

View File

@@ -1010,7 +1010,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 +1027,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()));
}

View File

@@ -880,7 +880,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 +897,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()));
}

View File

@@ -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

View File

@@ -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;
@@ -1411,8 +1412,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) {
@@ -1422,17 +1422,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()));
@@ -7087,10 +7082,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) {

View File

@@ -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);
}

View File

@@ -7509,46 +7509,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