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

Commit 2f6e89d2 authored by Lyn Han's avatar Lyn Han
Browse files

Notification removal with overflow bubbles

Intercept dismissal for overflow bubbles.

Remove notifications for inactive (not in stack and overflow)
bubbles cancelled or suppressed from shade.

Set "isBubble" SysUI flag
- false for inactive bubbles still in shade,
- true for bubbles promoted from overflow.

Account for overflow bubbles in event handling
- onEntryUpdated: remove overflow bubble if no longer a bubble
- onRankingUpdated: remove overflow bubble if blocked
- expandStackAndSelectBubble: promote overflow bubble then expand
- isBubbleNotificationSuppressedFromShade: check overflow bubbles

Update tests
Enable overflow flag

----------------
Fixes: 151104690
Test: manual: cancel oldest bubble => oldest overflow bubble removed
Test: atest SystemUITests

Change-Id: I44bb623f99f9473055787cf10693f7a3bfd1c768
parent 323d6357
Loading
Loading
Loading
Loading
+61 −44
Original line number Diff line number Diff line
@@ -123,7 +123,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
    @Retention(SOURCE)
    @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
            DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
            DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT})
            DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT,
            DISMISS_OVERFLOW_MAX_REACHED})
    @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
    @interface DismissReason {}

@@ -137,6 +138,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
    static final int DISMISS_USER_CHANGED = 8;
    static final int DISMISS_GROUP_CANCELLED = 9;
    static final int DISMISS_INVALID_INTENT = 10;
    static final int DISMISS_OVERFLOW_MAX_REACHED = 11;

    private final Context mContext;
    private final NotificationEntryManager mNotificationEntryManager;
@@ -465,7 +467,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                        if (userRemovedNotif) {
                            return handleDismissalInterception(entry);
                        }

                        return false;
                    }
                });
@@ -736,18 +737,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
     */
    public boolean isBubbleNotificationSuppressedFromShade(NotificationEntry entry) {
        String key = entry.getKey();
        boolean isBubbleAndSuppressed = mBubbleData.hasBubbleWithKey(key)
                && !mBubbleData.getBubbleWithKey(key).showInShade();
        boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key)
                && !mBubbleData.getAnyBubbleWithkey(key).showInShade());

        String groupKey = entry.getSbn().getGroupKey();
        boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey);
        boolean isSummary = key.equals(mBubbleData.getSummaryKey(groupKey));

        return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed;
        return (isSummary && isSuppressedSummary) || isSuppressedBubble;
    }

    void promoteBubbleFromOverflow(Bubble bubble) {
        bubble.setInflateSynchronously(mInflateSynchronously);
        setIsBubble(bubble, /* isBubble */ true);
        mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
    }

@@ -757,11 +758,16 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
     * @param notificationKey the notification key for the bubble to be selected
     */
    public void expandStackAndSelectBubble(String notificationKey) {
        Bubble bubble = mBubbleData.getBubbleWithKey(notificationKey);
        Bubble bubble = mBubbleData.getBubbleInStackWithKey(notificationKey);
        if (bubble == null) {
            bubble = mBubbleData.getOverflowBubbleWithKey(notificationKey);
            if (bubble != null) {
                mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
            }
        } else if (bubble.getEntry().isBubble()){
            mBubbleData.setSelectedBubble(bubble);
            mBubbleData.setExpanded(true);
        }
        mBubbleData.setExpanded(true);
    }

    /**
@@ -856,7 +862,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
     */
    @MainThread
    void removeBubble(NotificationEntry entry, int reason) {
        if (mBubbleData.hasBubbleWithKey(entry.getKey())) {
        if (mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
            mBubbleData.notificationEntryRemoved(entry, reason);
        }
    }
@@ -871,7 +877,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
    private void onEntryUpdated(NotificationEntry entry) {
        boolean shouldBubble = mNotificationInterruptStateProvider.shouldBubbleUp(entry)
                && canLaunchInActivityView(mContext, entry);
        if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) {
        if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
            // It was previously a bubble but no longer a bubble -- lets remove it
            removeBubble(entry, DISMISS_NO_LONGER_BUBBLE);
        } else if (shouldBubble) {
@@ -910,7 +916,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
            String key = orderedKeys[i];
            NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(key);
            rankingMap.getRanking(key, mTmpRanking);
            boolean isActiveBubble = mBubbleData.hasBubbleWithKey(key);
            boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
            if (isActiveBubble && !mTmpRanking.canBubble()) {
                mBubbleData.notificationEntryRemoved(entry, BubbleController.DISMISS_BLOCKED);
            } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) {
@@ -920,6 +926,19 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
        }
    }

    private void setIsBubble(Bubble b, boolean isBubble) {
        if (isBubble) {
            b.getEntry().getSbn().getNotification().flags |= FLAG_BUBBLE;
        } else {
            b.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
        }
        try {
            mBarService.onNotificationBubbleChanged(b.getKey(), isBubble, 0);
        } catch (RemoteException e) {
            // Bad things have happened
        }
    }

    @SuppressWarnings("FieldCanBeLocal")
    private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {

@@ -942,30 +961,31 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                final Bubble bubble = removed.first;
                @DismissReason final int reason = removed.second;
                mStackView.removeBubble(bubble);

                // If the bubble is removed for user switching, leave the notification in place.
                if (reason != DISMISS_USER_CHANGED) {
                    if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
                            && !bubble.showInShade()) {
                if (reason == DISMISS_USER_CHANGED) {
                    continue;
                }
                if (!mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
                    if (!mBubbleData.hasOverflowBubbleWithKey(bubble.getKey())
                        && (!bubble.showInShade()
                            || reason == DISMISS_NOTIF_CANCEL
                            || reason == DISMISS_GROUP_CANCELLED)) {
                        // The bubble is now gone & the notification is hidden from the shade, so
                        // time to actually remove it
                        for (NotifCallback cb : mCallbacks) {
                            cb.removeNotification(bubble.getEntry(), REASON_CANCEL);
                        }
                    } else {
                        // Update the flag for SysUI
                        bubble.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
                        if (bubble.getEntry().isBubble() && bubble.showInShade()) {
                            setIsBubble(bubble, /* isBubble */ false);
                        }
                        if (bubble.getEntry().getRow() != null) {
                            bubble.getEntry().getRow().updateBubbleButton();
                        }

                        // Update the state in NotificationManagerService
                        try {
                            mBarService.onNotificationBubbleChanged(bubble.getKey(),
                                    false /* isBubble */, 0 /* flags */);
                        } catch (RemoteException e) {
                        }
                    }

                }
                final String groupKey = bubble.getEntry().getSbn().getGroupKey();
                if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
                    // Time to potentially remove the summary
@@ -974,7 +994,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                    }
                }
            }
            }

            if (update.addedBubble != null) {
                mStackView.addBubble(update.addedBubble);
@@ -1020,7 +1039,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                }
                Log.d(TAG, "\n[BubbleData] overflow:");
                Log.d(TAG, BubbleDebugConfig.formatBubblesString(mBubbleData.getOverflowBubbles(),
                        null));
                        null) + "\n");
            }
        }
    };
@@ -1039,21 +1058,19 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
        if (entry == null) {
            return false;
        }

        final boolean interceptBubbleDismissal = mBubbleData.hasBubbleWithKey(entry.getKey())
                && entry.isBubble();
        final boolean interceptSummaryDismissal = isSummaryOfBubbles(entry);

        if (interceptSummaryDismissal) {
        if (isSummaryOfBubbles(entry)) {
            handleSummaryDismissalInterception(entry);
        } else if (interceptBubbleDismissal) {
            Bubble bubble = mBubbleData.getBubbleWithKey(entry.getKey());
            bubble.setSuppressNotification(true);
            bubble.setShowDot(false /* show */);
        } else {
            Bubble bubble = mBubbleData.getBubbleInStackWithKey(entry.getKey());
            if (bubble == null || !entry.isBubble()) {
                bubble = mBubbleData.getOverflowBubbleWithKey(entry.getKey());
            }
            if (bubble == null) {
                return false;
            }

            bubble.setSuppressNotification(true);
            bubble.setShowDot(false /* show */);
        }
        // Update the shade
        for (NotifCallback cb : mCallbacks) {
            cb.invalidateNotifications("BubbleController.handleDismissalInterception");
@@ -1082,11 +1099,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
        if (children != null) {
            for (int i = 0; i < children.size(); i++) {
                NotificationEntry child = children.get(i);
                if (mBubbleData.hasBubbleWithKey(child.getKey())) {
                if (mBubbleData.hasAnyBubbleWithKey(child.getKey())) {
                    // Suppress the bubbled child
                    // As far as group manager is concerned, once a child is no longer shown
                    // in the shade, it is essentially removed.
                    Bubble bubbleChild = mBubbleData.getBubbleWithKey(child.getKey());
                    Bubble bubbleChild = mBubbleData.getAnyBubbleWithkey(child.getKey());
                    mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry());
                    bubbleChild.setSuppressNotification(true);
                    bubbleChild.setShowDot(false /* show */);
+72 −31
Original line number Diff line number Diff line
@@ -123,7 +123,7 @@ public class BubbleData {
    private boolean mShowingOverflow;
    private boolean mExpanded;
    private final int mMaxBubbles;
    private final int mMaxOverflowBubbles;
    private int mMaxOverflowBubbles;

    // State tracked during an operation -- keeps track of what listener events to dispatch.
    private Update mStateChange;
@@ -175,8 +175,16 @@ public class BubbleData {
        return mExpanded;
    }

    public boolean hasBubbleWithKey(String key) {
        return getBubbleWithKey(key) != null;
    public boolean hasAnyBubbleWithKey(String key) {
        return hasBubbleInStackWithKey(key) || hasOverflowBubbleWithKey(key);
    }

    public boolean hasBubbleInStackWithKey(String key) {
        return getBubbleInStackWithKey(key) != null;
    }

    public boolean hasOverflowBubbleWithKey(String key) {
        return getOverflowBubbleWithKey(key) != null;
    }

    @Nullable
@@ -206,6 +214,8 @@ public class BubbleData {
            Log.d(TAG, "promoteBubbleFromOverflow: " + bubble);
        }
        moveOverflowBubbleToPending(bubble);
        // Preserve new order for next repack, which sorts by last updated time.
        bubble.markUpdatedAt(mTimeSource.currentTimeMillis());
        bubble.inflate(
                b -> {
                    notificationEntryUpdated(bubble, /* suppressFlyout */
@@ -221,8 +231,6 @@ public class BubbleData {
    }

    private void moveOverflowBubbleToPending(Bubble b) {
        // Preserve new order for next repack, which sorts by last updated time.
        b.markUpdatedAt(mTimeSource.currentTimeMillis());
        mOverflowBubbles.remove(b);
        mPendingBubbles.add(b);
    }
@@ -233,15 +241,16 @@ public class BubbleData {
     * for that.
     */
    Bubble getOrCreateBubble(NotificationEntry entry) {
        Bubble bubble = getBubbleWithKey(entry.getKey());
        if (bubble == null) {
            for (int i = 0; i < mOverflowBubbles.size(); i++) {
                Bubble b = mOverflowBubbles.get(i);
                if (b.getKey().equals(entry.getKey())) {
                    moveOverflowBubbleToPending(b);
                    b.setEntry(entry);
                    return b;
                }
        String key = entry.getKey();
        Bubble bubble = getBubbleInStackWithKey(entry.getKey());
        if (bubble != null) {
            bubble.setEntry(entry);
        } else {
            bubble = getOverflowBubbleWithKey(key);
            if (bubble != null) {
                moveOverflowBubbleToPending(bubble);
                bubble.setEntry(entry);
                return bubble;
            }
            // Check for it in pending
            for (int i = 0; i < mPendingBubbles.size(); i++) {
@@ -253,8 +262,6 @@ public class BubbleData {
            }
            bubble = new Bubble(entry, mSuppressionListener);
            mPendingBubbles.add(bubble);
        } else {
            bubble.setEntry(entry);
        }
        return bubble;
    }
@@ -269,7 +276,7 @@ public class BubbleData {
            Log.d(TAG, "notificationEntryUpdated: " + bubble);
        }
        mPendingBubbles.remove(bubble); // No longer pending once we're here
        Bubble prevBubble = getBubbleWithKey(bubble.getKey());
        Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey());
        suppressFlyout |= !bubble.getEntry().getRanking().visuallyInterruptive();

        if (prevBubble == null) {
@@ -422,6 +429,19 @@ public class BubbleData {
        }
        int indexToRemove = indexForKey(key);
        if (indexToRemove == -1) {
            if (hasOverflowBubbleWithKey(key)
                && (reason == BubbleController.DISMISS_NOTIF_CANCEL
                || reason == BubbleController.DISMISS_GROUP_CANCELLED
                || reason == BubbleController.DISMISS_NO_LONGER_BUBBLE
                || reason == BubbleController.DISMISS_BLOCKED)) {

                Bubble b = getOverflowBubbleWithKey(key);
                if (DEBUG_BUBBLE_DATA) {
                    Log.d(TAG, "Cancel overflow bubble: " + b);
                }
                mStateChange.bubbleRemoved(b, reason);
                mOverflowBubbles.remove(b);
            }
            return;
        }
        Bubble bubbleToRemove = mBubbles.get(indexToRemove);
@@ -453,8 +473,10 @@ public class BubbleData {
    }

    void overflowBubble(@DismissReason int reason, Bubble bubble) {
        if (reason == BubbleController.DISMISS_AGED
                || reason == BubbleController.DISMISS_USER_GESTURE) {
        if (!(reason == BubbleController.DISMISS_AGED
                || reason == BubbleController.DISMISS_USER_GESTURE)) {
            return;
        }
        if (DEBUG_BUBBLE_DATA) {
            Log.d(TAG, "Overflowing: " + bubble);
        }
@@ -462,12 +484,12 @@ public class BubbleData {
        bubble.stopInflation();
        if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
            // Remove oldest bubble.
            Bubble oldest = mOverflowBubbles.get(mOverflowBubbles.size() - 1);
            if (DEBUG_BUBBLE_DATA) {
                    Log.d(TAG, "Overflow full. Remove: " + mOverflowBubbles.get(
                            mOverflowBubbles.size() - 1));
                }
                mOverflowBubbles.remove(mOverflowBubbles.size() - 1);
                Log.d(TAG, "Overflow full. Remove: " + oldest);
            }
            mStateChange.bubbleRemoved(oldest, BubbleController.DISMISS_OVERFLOW_MAX_REACHED);
            mOverflowBubbles.remove(oldest);
        }
    }

@@ -764,7 +786,17 @@ public class BubbleData {

    @VisibleForTesting(visibility = PRIVATE)
    @Nullable
    Bubble getBubbleWithKey(String key) {
    Bubble getAnyBubbleWithkey(String key) {
        Bubble b = getBubbleInStackWithKey(key);
        if (b == null) {
            b = getOverflowBubbleWithKey(key);
        }
        return b;
    }

    @VisibleForTesting(visibility = PRIVATE)
    @Nullable
    Bubble getBubbleInStackWithKey(String key) {
        for (int i = 0; i < mBubbles.size(); i++) {
            Bubble bubble = mBubbles.get(i);
            if (bubble.getKey().equals(key)) {
@@ -805,6 +837,15 @@ public class BubbleData {
        mListener = listener;
    }

    /**
     * Set maximum number of bubbles allowed in overflow.
     * This method should only be used in tests, not in production.
     */
    @VisibleForTesting
    void setMaxOverflowBubbles(int maxOverflowBubbles) {
        mMaxOverflowBubbles = maxOverflowBubbles;
    }

    /**
     * Description of current bubble data state.
     */
+1 −1
Original line number Diff line number Diff line
@@ -71,7 +71,7 @@ public class BubbleExperimentConfig {
    private static final String WHITELISTED_AUTO_BUBBLE_APPS = "whitelisted_auto_bubble_apps";

    private static final String ALLOW_BUBBLE_OVERFLOW = "allow_bubble_overflow";
    private static final boolean ALLOW_BUBBLE_OVERFLOW_DEFAULT = false;
    private static final boolean ALLOW_BUBBLE_OVERFLOW_DEFAULT = true;

    /**
     * When true, if a notification has the information necessary to bubble (i.e. valid
+6 −7
Original line number Diff line number Diff line
@@ -906,7 +906,7 @@ public class BubbleStackView extends FrameLayout {
                view -> {
                    showManageMenu(false /* show */);
                    final Bubble bubble = mBubbleData.getSelectedBubble();
                    if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) {
                    if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
                        mUnbubbleConversationCallback.accept(bubble.getEntry());
                    }
                });
@@ -915,7 +915,7 @@ public class BubbleStackView extends FrameLayout {
                view -> {
                    showManageMenu(false /* show */);
                    final Bubble bubble = mBubbleData.getSelectedBubble();
                    if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) {
                    if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
                        final Intent intent = bubble.getSettingsIntent();
                        collapseStack(() -> {
                            mContext.startActivityAsUser(
@@ -1756,14 +1756,13 @@ public class BubbleStackView extends FrameLayout {
        if (mIsExpanded) {
            final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject();
            dismissBubbleIfExists(mBubbleData.getBubbleWithView(draggedOutBubbleView));

        } else {
            mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
        }
    }

    private void dismissBubbleIfExists(@Nullable Bubble bubble) {
        if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) {
        if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
            mBubbleData.notificationEntryRemoved(
                    bubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
        }
@@ -2024,8 +2023,8 @@ public class BubbleStackView extends FrameLayout {

        // If available, update the manage menu's settings option with the expanded bubble's app
        // name and icon.
        if (show && mBubbleData.hasBubbleWithKey(mExpandedBubble.getKey())) {
            final Bubble bubble = mBubbleData.getBubbleWithKey(mExpandedBubble.getKey());
        if (show && mBubbleData.hasBubbleInStackWithKey(mExpandedBubble.getKey())) {
            final Bubble bubble = mBubbleData.getBubbleInStackWithKey(mExpandedBubble.getKey());
            mManageSettingsIcon.setImageDrawable(bubble.getBadgedAppIcon());
            mManageSettingsText.setText(getResources().getString(
                    R.string.bubbles_app_settings, bubble.getAppName()));
@@ -2241,7 +2240,7 @@ public class BubbleStackView extends FrameLayout {
            View child = mBubbleContainer.getChildAt(i);
            if (child instanceof BadgedImageView) {
                String key = ((BadgedImageView) child).getKey();
                Bubble bubble = mBubbleData.getBubbleWithKey(key);
                Bubble bubble = mBubbleData.getBubbleInStackWithKey(key);
                bubbles.add(bubble);
            }
        }
+103 −49

File changed.

Preview size limit exceeded, changes collapsed.

Loading