Merge changes from topic "mm-bubble-updates" into tm-qpr-dev
* changes: If the update is non-interruptive don't re-bubble it Don't bubble things when we shouldn't Ignore updates to bubbles that aren't from the system
This commit is contained in:
@@ -452,6 +452,7 @@ public class Bubble implements BubbleViewProvider {
|
|||||||
*/
|
*/
|
||||||
void setEntry(@NonNull final BubbleEntry entry) {
|
void setEntry(@NonNull final BubbleEntry entry) {
|
||||||
Objects.requireNonNull(entry);
|
Objects.requireNonNull(entry);
|
||||||
|
boolean showingDotPreviously = showDot();
|
||||||
mLastUpdated = entry.getStatusBarNotification().getPostTime();
|
mLastUpdated = entry.getStatusBarNotification().getPostTime();
|
||||||
mIsBubble = entry.getStatusBarNotification().getNotification().isBubbleNotification();
|
mIsBubble = entry.getStatusBarNotification().getNotification().isBubbleNotification();
|
||||||
mPackageName = entry.getStatusBarNotification().getPackageName();
|
mPackageName = entry.getStatusBarNotification().getPackageName();
|
||||||
@@ -498,6 +499,10 @@ public class Bubble implements BubbleViewProvider {
|
|||||||
mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot();
|
mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot();
|
||||||
mShouldSuppressNotificationList = entry.shouldSuppressNotificationList();
|
mShouldSuppressNotificationList = entry.shouldSuppressNotificationList();
|
||||||
mShouldSuppressPeek = entry.shouldSuppressPeek();
|
mShouldSuppressPeek = entry.shouldSuppressPeek();
|
||||||
|
if (showingDotPreviously != showDot()) {
|
||||||
|
// This will update the UI if needed
|
||||||
|
setShowDot(showDot());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@@ -1055,18 +1055,28 @@ public class BubbleController implements ConfigurationChangeListener {
|
|||||||
public void updateBubble(BubbleEntry notif, boolean suppressFlyout, boolean showInShade) {
|
public void updateBubble(BubbleEntry notif, boolean suppressFlyout, boolean showInShade) {
|
||||||
// If this is an interruptive notif, mark that it's interrupted
|
// If this is an interruptive notif, mark that it's interrupted
|
||||||
mSysuiProxy.setNotificationInterruption(notif.getKey());
|
mSysuiProxy.setNotificationInterruption(notif.getKey());
|
||||||
if (!notif.getRanking().isTextChanged()
|
boolean isNonInterruptiveNotExpanding = !notif.getRanking().isTextChanged()
|
||||||
&& (notif.getBubbleMetadata() != null
|
&& (notif.getBubbleMetadata() != null
|
||||||
&& !notif.getBubbleMetadata().getAutoExpandBubble())
|
&& !notif.getBubbleMetadata().getAutoExpandBubble());
|
||||||
|
if (isNonInterruptiveNotExpanding
|
||||||
&& mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) {
|
&& mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) {
|
||||||
// Update the bubble but don't promote it out of overflow
|
// Update the bubble but don't promote it out of overflow
|
||||||
Bubble b = mBubbleData.getOverflowBubbleWithKey(notif.getKey());
|
Bubble b = mBubbleData.getOverflowBubbleWithKey(notif.getKey());
|
||||||
b.setEntry(notif);
|
if (notif.isBubble()) {
|
||||||
|
notif.setFlagBubble(false);
|
||||||
|
}
|
||||||
|
updateNotNotifyingEntry(b, notif, showInShade);
|
||||||
|
} else if (mBubbleData.hasAnyBubbleWithKey(notif.getKey())
|
||||||
|
&& isNonInterruptiveNotExpanding) {
|
||||||
|
Bubble b = mBubbleData.getAnyBubbleWithkey(notif.getKey());
|
||||||
|
if (b != null) {
|
||||||
|
updateNotNotifyingEntry(b, notif, showInShade);
|
||||||
|
}
|
||||||
} else if (mBubbleData.isSuppressedWithLocusId(notif.getLocusId())) {
|
} else if (mBubbleData.isSuppressedWithLocusId(notif.getLocusId())) {
|
||||||
// Update the bubble but don't promote it out of overflow
|
// Update the bubble but don't promote it out of overflow
|
||||||
Bubble b = mBubbleData.getSuppressedBubbleWithKey(notif.getKey());
|
Bubble b = mBubbleData.getSuppressedBubbleWithKey(notif.getKey());
|
||||||
if (b != null) {
|
if (b != null) {
|
||||||
b.setEntry(notif);
|
updateNotNotifyingEntry(b, notif, showInShade);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);
|
Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);
|
||||||
@@ -1076,13 +1086,25 @@ public class BubbleController implements ConfigurationChangeListener {
|
|||||||
if (bubble.shouldAutoExpand()) {
|
if (bubble.shouldAutoExpand()) {
|
||||||
bubble.setShouldAutoExpand(false);
|
bubble.setShouldAutoExpand(false);
|
||||||
}
|
}
|
||||||
|
mImpl.mCachedState.updateBubbleSuppressedState(bubble);
|
||||||
} else {
|
} else {
|
||||||
inflateAndAdd(bubble, suppressFlyout, showInShade);
|
inflateAndAdd(bubble, suppressFlyout, showInShade);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
|
void updateNotNotifyingEntry(Bubble b, BubbleEntry entry, boolean showInShade) {
|
||||||
|
boolean isBubbleSelected = Objects.equals(b, mBubbleData.getSelectedBubble());
|
||||||
|
boolean isBubbleExpandedAndSelected = isStackExpanded() && isBubbleSelected;
|
||||||
|
b.setEntry(entry);
|
||||||
|
boolean suppress = isBubbleExpandedAndSelected || !showInShade || !b.showInShade();
|
||||||
|
b.setSuppressNotification(suppress);
|
||||||
|
b.setShowDot(!isBubbleExpandedAndSelected);
|
||||||
|
mImpl.mCachedState.updateBubbleSuppressedState(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
|
||||||
// Lazy init stack view when a bubble is created
|
// Lazy init stack view when a bubble is created
|
||||||
ensureStackViewCreated();
|
ensureStackViewCreated();
|
||||||
bubble.setInflateSynchronously(mInflateSynchronously);
|
bubble.setInflateSynchronously(mInflateSynchronously);
|
||||||
@@ -1111,7 +1133,10 @@ public class BubbleController implements ConfigurationChangeListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) {
|
public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem) {
|
||||||
|
if (!fromSystem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// shouldBubbleUp checks canBubble & for bubble metadata
|
// shouldBubbleUp checks canBubble & for bubble metadata
|
||||||
boolean shouldBubble = shouldBubbleUp && canLaunchInTaskView(mContext, entry);
|
boolean shouldBubble = shouldBubbleUp && canLaunchInTaskView(mContext, entry);
|
||||||
if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
|
if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
|
||||||
@@ -1172,9 +1197,9 @@ public class BubbleController implements ConfigurationChangeListener {
|
|||||||
// notification, so that the bubble will be re-created if shouldBubbleUp returns
|
// notification, so that the bubble will be re-created if shouldBubbleUp returns
|
||||||
// true.
|
// true.
|
||||||
mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP);
|
mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP);
|
||||||
} else if (entry != null && mTmpRanking.isBubble() && !isActive) {
|
} else if (entry != null && mTmpRanking.isBubble() && !isActiveOrInOverflow) {
|
||||||
entry.setFlagBubble(true);
|
entry.setFlagBubble(true);
|
||||||
onEntryUpdated(entry, shouldBubbleUp);
|
onEntryUpdated(entry, shouldBubbleUp, /* fromSystem= */ true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1773,9 +1798,9 @@ public class BubbleController implements ConfigurationChangeListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) {
|
public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem) {
|
||||||
mMainExecutor.execute(() -> {
|
mMainExecutor.execute(() -> {
|
||||||
BubbleController.this.onEntryUpdated(entry, shouldBubbleUp);
|
BubbleController.this.onEntryUpdated(entry, shouldBubbleUp, fromSystem);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,8 +171,9 @@ public interface Bubbles {
|
|||||||
*
|
*
|
||||||
* @param entry the {@link BubbleEntry} by the notification.
|
* @param entry the {@link BubbleEntry} by the notification.
|
||||||
* @param shouldBubbleUp {@code true} if this notification should bubble up.
|
* @param shouldBubbleUp {@code true} if this notification should bubble up.
|
||||||
|
* @param fromSystem {@code true} if this update is from NotificationManagerService.
|
||||||
*/
|
*/
|
||||||
void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp);
|
void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when new notification entry removed.
|
* Called when new notification entry removed.
|
||||||
|
|||||||
@@ -405,8 +405,8 @@ public class BubblesManager implements Dumpable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEntryUpdated(NotificationEntry entry) {
|
public void onEntryUpdated(NotificationEntry entry, boolean fromSystem) {
|
||||||
BubblesManager.this.onEntryUpdated(entry);
|
BubblesManager.this.onEntryUpdated(entry, fromSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -444,9 +444,10 @@ public class BubblesManager implements Dumpable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onEntryUpdated(NotificationEntry entry) {
|
void onEntryUpdated(NotificationEntry entry, boolean fromSystem) {
|
||||||
|
boolean shouldBubble = mNotificationInterruptStateProvider.shouldBubbleUp(entry);
|
||||||
mBubbles.onEntryUpdated(notifToBubbleEntry(entry),
|
mBubbles.onEntryUpdated(notifToBubbleEntry(entry),
|
||||||
mNotificationInterruptStateProvider.shouldBubbleUp(entry));
|
shouldBubble, fromSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onEntryRemoved(NotificationEntry entry) {
|
void onEntryRemoved(NotificationEntry entry) {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import static org.junit.Assert.assertNotNull;
|
|||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
@@ -621,7 +622,7 @@ public class BubblesTest extends SysuiTestCase {
|
|||||||
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
|
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
|
||||||
|
|
||||||
// Send update
|
// Send update
|
||||||
mEntryListener.onEntryUpdated(mRow);
|
mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true);
|
||||||
|
|
||||||
// Nothing should have changed
|
// Nothing should have changed
|
||||||
// Notif is suppressed after expansion
|
// Notif is suppressed after expansion
|
||||||
@@ -789,7 +790,7 @@ public class BubblesTest extends SysuiTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testAddNotif_notBubble() {
|
public void testAddNotif_notBubble() {
|
||||||
mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry());
|
mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry());
|
||||||
mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry());
|
mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry(), /* fromSystem= */ true);
|
||||||
|
|
||||||
assertThat(mBubbleController.hasBubbles()).isFalse();
|
assertThat(mBubbleController.hasBubbles()).isFalse();
|
||||||
}
|
}
|
||||||
@@ -827,7 +828,7 @@ public class BubblesTest extends SysuiTestCase {
|
|||||||
NotificationListenerService.Ranking ranking = new RankingBuilder(
|
NotificationListenerService.Ranking ranking = new RankingBuilder(
|
||||||
mRow.getRanking()).setCanBubble(false).build();
|
mRow.getRanking()).setCanBubble(false).build();
|
||||||
mRow.setRanking(ranking);
|
mRow.setRanking(ranking);
|
||||||
mEntryListener.onEntryUpdated(mRow);
|
mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true);
|
||||||
|
|
||||||
assertFalse(mBubbleController.hasBubbles());
|
assertFalse(mBubbleController.hasBubbles());
|
||||||
verify(mDeleteIntent, never()).send();
|
verify(mDeleteIntent, never()).send();
|
||||||
@@ -1432,6 +1433,100 @@ public class BubblesTest extends SysuiTestCase {
|
|||||||
assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse();
|
assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that if a bubble is in the overflow and a non-interruptive notification update
|
||||||
|
* comes in for it, it stays in the overflow but the entry is updated.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testNonInterruptiveUpdate_doesntBubbleFromOverflow() {
|
||||||
|
mEntryListener.onEntryAdded(mRow);
|
||||||
|
mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true);
|
||||||
|
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
|
||||||
|
|
||||||
|
// Dismiss the bubble so it's in the overflow
|
||||||
|
mBubbleController.removeBubble(
|
||||||
|
mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
|
||||||
|
assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())).isTrue();
|
||||||
|
|
||||||
|
// Update the entry to not show in shade
|
||||||
|
setMetadataFlags(mRow,
|
||||||
|
Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, /* enableFlag= */ true);
|
||||||
|
mBubbleController.updateBubble(mBubbleEntry,
|
||||||
|
/* suppressFlyout= */ false, /* showInShade= */ true);
|
||||||
|
|
||||||
|
// Check that the update was applied - shouldn't be show in shade
|
||||||
|
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
|
||||||
|
// Check that it wasn't inflated (1 because it would've been inflated via onEntryAdded)
|
||||||
|
verify(mBubbleController, times(1)).inflateAndAdd(
|
||||||
|
any(Bubble.class), anyBoolean(), anyBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that if a bubble is active, and a non-interruptive notification update comes in for
|
||||||
|
* it, it doesn't trigger a new inflate and add for that bubble.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testNonInterruptiveUpdate_doesntTriggerInflate() {
|
||||||
|
mEntryListener.onEntryAdded(mRow);
|
||||||
|
mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true);
|
||||||
|
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
|
||||||
|
|
||||||
|
// Update the entry to not show in shade
|
||||||
|
setMetadataFlags(mRow,
|
||||||
|
Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, /* enableFlag= */ true);
|
||||||
|
mBubbleController.updateBubble(mBubbleEntry,
|
||||||
|
/* suppressFlyout= */ false, /* showInShade= */ true);
|
||||||
|
|
||||||
|
// Check that the update was applied - shouldn't be show in shade
|
||||||
|
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
|
||||||
|
// Check that it wasn't inflated (1 because it would've been inflated via onEntryAdded)
|
||||||
|
verify(mBubbleController, times(1)).inflateAndAdd(
|
||||||
|
any(Bubble.class), anyBoolean(), anyBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that if a bubble is in the overflow and a non-interruptive notification update
|
||||||
|
* comes in for it with FLAG_BUBBLE that the flag is removed.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testNonInterruptiveUpdate_doesntOverrideOverflowFlagBubble() {
|
||||||
|
mEntryListener.onEntryAdded(mRow);
|
||||||
|
mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true);
|
||||||
|
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
|
||||||
|
|
||||||
|
// Dismiss the bubble so it's in the overflow
|
||||||
|
mBubbleController.removeBubble(
|
||||||
|
mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
|
||||||
|
assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())).isTrue();
|
||||||
|
// Once it's in the overflow it's not actively a bubble (doesn't have FLAG_BUBBLE)
|
||||||
|
Bubble b = mBubbleData.getOverflowBubbleWithKey(mBubbleEntry.getKey());
|
||||||
|
assertThat(b.isBubble()).isFalse();
|
||||||
|
|
||||||
|
// Send a non-notifying update that has FLAG_BUBBLE
|
||||||
|
mRow.getSbn().getNotification().flags = FLAG_BUBBLE;
|
||||||
|
assertThat(mRow.getSbn().getNotification().isBubbleNotification()).isTrue();
|
||||||
|
mBubbleController.updateBubble(mBubbleEntry,
|
||||||
|
/* suppressFlyout= */ false, /* showInShade= */ true);
|
||||||
|
|
||||||
|
// Verify that it still doesn't have FLAG_BUBBLE because it's in the overflow.
|
||||||
|
b = mBubbleData.getOverflowBubbleWithKey(mBubbleEntry.getKey());
|
||||||
|
assertThat(b.isBubble()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNonSystemUpdatesIgnored() {
|
||||||
|
mEntryListener.onEntryAdded(mRow);
|
||||||
|
assertThat(mBubbleController.hasBubbles()).isTrue();
|
||||||
|
|
||||||
|
mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ false);
|
||||||
|
mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ false);
|
||||||
|
mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ false);
|
||||||
|
|
||||||
|
// Check that it wasn't inflated (1 because it would've been inflated via onEntryAdded)
|
||||||
|
verify(mBubbleController, times(1)).inflateAndAdd(
|
||||||
|
any(Bubble.class), anyBoolean(), anyBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
/** Creates a bubble using the userId and package. */
|
/** Creates a bubble using the userId and package. */
|
||||||
private Bubble createBubble(int userId, String pkg) {
|
private Bubble createBubble(int userId, String pkg) {
|
||||||
final UserHandle userHandle = new UserHandle(userId);
|
final UserHandle userHandle = new UserHandle(userId);
|
||||||
|
|||||||
Reference in New Issue
Block a user