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

Commit 88d33f10 authored by Liran Binyamin's avatar Liran Binyamin
Browse files

Don't remove the bubble if there's a later update

When the removal event is received from launcher, we now check the
removal timestamp against the most recent update before removing it.

This helps ensure that we don't remove bubbles when there are unseen
updates.

Flag: com.android.wm.shell.enable_bubble_bar
Bug: 351026092
Test: atest BubbleDataTest
Change-Id: Ib59658b6dcebc1da0281239be4621250289f7158
parent 78f735a9
Loading
Loading
Loading
Loading
+8 −4
Original line number Diff line number Diff line
@@ -1230,10 +1230,14 @@ public class BubbleController implements ConfigurationChangeListener,
     * A bubble was dragged and is released in dismiss target in Launcher.
     *
     * @param bubbleKey key of the bubble being dragged to dismiss target
     * @param timestamp the timestamp of the removal
     */
    public void dragBubbleToDismiss(String bubbleKey) {
    public void dragBubbleToDismiss(String bubbleKey, long timestamp) {
        String selectedBubbleKey = mBubbleData.getSelectedBubbleKey();
        removeBubble(bubbleKey, Bubbles.DISMISS_USER_GESTURE);
        if (mBubbleData.hasAnyBubbleWithKey(bubbleKey)) {
            mBubbleData.dismissBubbleWithKey(
                    bubbleKey, Bubbles.DISMISS_USER_GESTURE_FROM_LAUNCHER, timestamp);
        }
        if (selectedBubbleKey != null && !selectedBubbleKey.equals(bubbleKey)) {
            // We did not remove the selected bubble. Expand it again
            mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
@@ -2458,8 +2462,8 @@ public class BubbleController implements ConfigurationChangeListener,
        }

        @Override
        public void dragBubbleToDismiss(String key) {
            mMainExecutor.execute(() -> mController.dragBubbleToDismiss(key));
        public void dragBubbleToDismiss(String key, long timestamp) {
            mMainExecutor.execute(() -> mController.dragBubbleToDismiss(key, timestamp));
        }

        @Override
+30 −5
Original line number Diff line number Diff line
@@ -150,9 +150,12 @@ public class BubbleData {
                    : null;
            for (int i = 0; i < removedBubbles.size(); i++) {
                Pair<Bubble, Integer> pair = removedBubbles.get(i);
                // if the removal happened in launcher, don't send it back
                if (pair.second != Bubbles.DISMISS_USER_GESTURE_FROM_LAUNCHER) {
                    bubbleBarUpdate.removedBubbles.add(
                            new RemovedBubble(pair.first.getKey(), pair.second));
                }
            }
            if (orderChanged) {
                // Include the new order
                for (int i = 0; i < bubbles.size(); i++) {
@@ -502,13 +505,35 @@ public class BubbleData {
        dispatchPendingChanges();
    }

    /** Dismisses the bubble with the matching key, if it exists. */
    public void dismissBubbleWithKey(String key, @DismissReason int reason) {
        dismissBubbleWithKey(key, reason, mTimeSource.currentTimeMillis());
    }

    /**
     * Dismisses the bubble with the matching key, if it exists.
     *
     * <p>This is used when the bubble was dismissed in launcher, where the {@code removalTimestamp}
     * represents when the removal happened and can be used to check whether or not the bubble has
     * been updated after the removal. If no updates, it's safe to remove the bubble, otherwise the
     * removal is ignored.
     */
    public void dismissBubbleWithKey(String key, @DismissReason int reason) {
    public void dismissBubbleWithKey(String key, @DismissReason int reason, long removalTimestamp) {
        boolean shouldRemove = true;
        // if the bubble was removed from launcher, verify that the removal happened after the last
        // time it was updated
        if (reason == Bubbles.DISMISS_USER_GESTURE_FROM_LAUNCHER) {
            // if the bubble was removed from launcher it must be active.
            Bubble bubble = getBubbleInStackWithKey(key);
            if (bubble != null && bubble.getLastActivity() > removalTimestamp) {
                shouldRemove = false;
            }
        }
        if (shouldRemove) {
            doRemove(key, reason);
            dispatchPendingChanges();
        }
    }

    /**
     * Adds a group key indicating that the summary for this group should be suppressed.
+2 −1
Original line number Diff line number Diff line
@@ -62,7 +62,7 @@ public interface Bubbles {
            DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT,
            DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED,
            DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_ACCOUNT_REMOVED,
            DISMISS_SWITCH_TO_STACK})
            DISMISS_SWITCH_TO_STACK, DISMISS_USER_GESTURE_FROM_LAUNCHER})
    @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
    @interface DismissReason {
    }
@@ -84,6 +84,7 @@ public interface Bubbles {
    int DISMISS_RELOAD_FROM_DISK = 15;
    int DISMISS_USER_ACCOUNT_REMOVED = 16;
    int DISMISS_SWITCH_TO_STACK = 17;
    int DISMISS_USER_GESTURE_FROM_LAUNCHER = 18;

    /** Returns a binder that can be passed to an external process to manipulate Bubbles. */
    default IBubbles createExternalInterface() {
+1 −1
Original line number Diff line number Diff line
@@ -33,7 +33,7 @@ interface IBubbles {

    oneway void showBubble(in String key, in int topOnScreen) = 3;

    oneway void dragBubbleToDismiss(in String key) = 4;
    oneway void dragBubbleToDismiss(in String key, in long timestamp) = 4;

    oneway void removeAllBubbles() = 5;

+42 −4
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

@@ -254,6 +255,45 @@ public class BubbleDataTest extends ShellTestCase {
        assertBubbleRemoved(mBubbleA1, Bubbles.DISMISS_USER_GESTURE);
    }

    @Test
    public void testRemoveBubbleInLauncher_beforeBubbleUpdate_processedAfter_shouldNotBeRemoved() {
        sendUpdatedEntryAtTime(mEntryA1, 1000);
        sendUpdatedEntryAtTime(mEntryA2, 2000);
        mBubbleData.setListener(mListener);

        sendUpdatedEntryAtTime(mEntryA2, 3000);

        verifyUpdateReceived();
        assertThat(mBubbleData.hasBubbleInStackWithKey(mEntryA2.getKey())).isTrue();
        assertThat(mBubbleData.getBubbleInStackWithKey(mEntryA2.getKey()).getLastActivity())
                .isEqualTo(3000);

        // dismiss the bubble with a timestamp in the past
        mBubbleData.dismissBubbleWithKey(
                mEntryA2.getKey(), Bubbles.DISMISS_USER_GESTURE_FROM_LAUNCHER, 2500);

        verifyNoMoreInteractions(mListener);
        assertThat(mBubbleData.hasBubbleInStackWithKey(mEntryA2.getKey())).isTrue();
    }

    @Test
    public void testRemoveBubbleInLauncher_isNotSentBackToLauncher() {
        sendUpdatedEntryAtTime(mEntryA1, 1000);
        sendUpdatedEntryAtTime(mEntryA2, 2000);
        mBubbleData.setListener(mListener);

        mBubbleData.dismissBubbleWithKey(
                mEntryA2.getKey(), Bubbles.DISMISS_USER_GESTURE_FROM_LAUNCHER, 4000);
        verifyUpdateReceived();

        BubbleData.Update update = mUpdateCaptor.getValue();
        assertThat(update.removedBubbles).hasSize(1);
        assertThat(update.removedBubbles.getFirst().first.getKey()).isEqualTo(mBubbleA2.getKey());

        BubbleBarUpdate bubbleBarUpdate = update.toBubbleBarUpdate();
        assertThat(bubbleBarUpdate.removedBubbles).isEmpty();
    }

    @Test
    public void ifSuppress_hideFlyout() {
        // Setup
@@ -1415,15 +1455,13 @@ public class BubbleDataTest extends ShellTestCase {
        sendUpdatedEntryAtTime(entry, postTime, true /* isTextChanged */);
    }

    private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime,
            boolean textChanged) {
    private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime, boolean textChanged) {
        setPostTime(entry, postTime);
        // BubbleController calls this:
        Bubble b = mBubbleData.getOrCreateBubble(entry, null /* persistedBubble */);
        b.setTextChangedForTest(textChanged);
        // And then this
        mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/,
                true /* showInShade */);
        mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/, true /* showInShade */);
    }

    private void changeExpandedStateAtTime(boolean shouldBeExpanded, long time) {
Loading