Merge changes Ieb77b211,Iad01b8f3,Ib9653ed3 into tm-dev am: e8ee9cd8d4

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18011489

Change-Id: Ib1a240a9f0b20b18b72ff58cbf5f1c419e4b5cb1
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Mady Mellor
2022-05-04 18:01:50 +00:00
committed by Automerger Merge Worker
13 changed files with 280 additions and 89 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);
}
}
@@ -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 + '}';

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

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

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

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

View File

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

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

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

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