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:
@@ -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);
|
||||
|
||||
@@ -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 + '}';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user