Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7e347d7e authored by Mady Mellor's avatar Mady Mellor Committed by Android (Google) Code Review
Browse files

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
parents b4b3a8bd 5c5b6218
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -452,6 +452,7 @@ public class Bubble implements BubbleViewProvider {
     */
    void setEntry(@NonNull final BubbleEntry entry) {
        Objects.requireNonNull(entry);
        boolean showingDotPreviously = showDot();
        mLastUpdated = entry.getStatusBarNotification().getPostTime();
        mIsBubble = entry.getStatusBarNotification().getNotification().isBubbleNotification();
        mPackageName = entry.getStatusBarNotification().getPackageName();
@@ -498,6 +499,10 @@ public class Bubble implements BubbleViewProvider {
        mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot();
        mShouldSuppressNotificationList = entry.shouldSuppressNotificationList();
        mShouldSuppressPeek = entry.shouldSuppressPeek();
        if (showingDotPreviously != showDot()) {
            // This will update the UI if needed
            setShowDot(showDot());
        }
    }

    @Nullable
+35 −10
Original line number Diff line number Diff line
@@ -1055,18 +1055,28 @@ public class BubbleController implements ConfigurationChangeListener {
    public void updateBubble(BubbleEntry notif, boolean suppressFlyout, boolean showInShade) {
        // If this is an interruptive notif, mark that it's interrupted
        mSysuiProxy.setNotificationInterruption(notif.getKey());
        if (!notif.getRanking().isTextChanged()
        boolean isNonInterruptiveNotExpanding = !notif.getRanking().isTextChanged()
                && (notif.getBubbleMetadata() != null
                && !notif.getBubbleMetadata().getAutoExpandBubble())
                && !notif.getBubbleMetadata().getAutoExpandBubble());
        if (isNonInterruptiveNotExpanding
                && mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) {
            // Update the bubble but don't promote it out of overflow
            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())) {
            // Update the bubble but don't promote it out of overflow
            Bubble b = mBubbleData.getSuppressedBubbleWithKey(notif.getKey());
            if (b != null) {
                b.setEntry(notif);
                updateNotNotifyingEntry(b, notif, showInShade);
            }
        } else {
            Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);
@@ -1076,13 +1086,25 @@ public class BubbleController implements ConfigurationChangeListener {
                if (bubble.shouldAutoExpand()) {
                    bubble.setShouldAutoExpand(false);
                }
                mImpl.mCachedState.updateBubbleSuppressedState(bubble);
            } else {
                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
        ensureStackViewCreated();
        bubble.setInflateSynchronously(mInflateSynchronously);
@@ -1111,7 +1133,10 @@ public class BubbleController implements ConfigurationChangeListener {
    }

    @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
        boolean shouldBubble = shouldBubbleUp && canLaunchInTaskView(mContext, entry);
        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
                // true.
                mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP);
            } else if (entry != null && mTmpRanking.isBubble() && !isActive) {
            } else if (entry != null && mTmpRanking.isBubble() && !isActiveOrInOverflow) {
                entry.setFlagBubble(true);
                onEntryUpdated(entry, shouldBubbleUp);
                onEntryUpdated(entry, shouldBubbleUp, /* fromSystem= */ true);
            }
        }
    }
@@ -1773,9 +1798,9 @@ public class BubbleController implements ConfigurationChangeListener {
        }

        @Override
        public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) {
        public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem) {
            mMainExecutor.execute(() -> {
                BubbleController.this.onEntryUpdated(entry, shouldBubbleUp);
                BubbleController.this.onEntryUpdated(entry, shouldBubbleUp, fromSystem);
            });
        }

+2 −1
Original line number Diff line number Diff line
@@ -171,8 +171,9 @@ public interface Bubbles {
     *
     * @param entry the {@link BubbleEntry} by the notification.
     * @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.
+5 −4
Original line number Diff line number Diff line
@@ -405,8 +405,8 @@ public class BubblesManager implements Dumpable {
            }

            @Override
            public void onEntryUpdated(NotificationEntry entry) {
                BubblesManager.this.onEntryUpdated(entry);
            public void onEntryUpdated(NotificationEntry entry, boolean fromSystem) {
                BubblesManager.this.onEntryUpdated(entry, fromSystem);
            }

            @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),
                mNotificationInterruptStateProvider.shouldBubbleUp(entry));
                shouldBubble, fromSystem);
    }

    void onEntryRemoved(NotificationEntry entry) {
+98 −3
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -621,7 +622,7 @@ public class BubblesTest extends SysuiTestCase {
        assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());

        // Send update
        mEntryListener.onEntryUpdated(mRow);
        mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true);

        // Nothing should have changed
        // Notif is suppressed after expansion
@@ -789,7 +790,7 @@ public class BubblesTest extends SysuiTestCase {
    @Test
    public void testAddNotif_notBubble() {
        mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry());
        mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry());
        mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry(), /* fromSystem= */ true);

        assertThat(mBubbleController.hasBubbles()).isFalse();
    }
@@ -827,7 +828,7 @@ public class BubblesTest extends SysuiTestCase {
        NotificationListenerService.Ranking ranking = new RankingBuilder(
                mRow.getRanking()).setCanBubble(false).build();
        mRow.setRanking(ranking);
        mEntryListener.onEntryUpdated(mRow);
        mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true);

        assertFalse(mBubbleController.hasBubbles());
        verify(mDeleteIntent, never()).send();
@@ -1432,6 +1433,100 @@ public class BubblesTest extends SysuiTestCase {
        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. */
    private Bubble createBubble(int userId, String pkg) {
        final UserHandle userHandle = new UserHandle(userId);