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

Commit 82a40e6e authored by Mark Renouf's avatar Mark Renouf
Browse files

Combine discrete listener methods into single state update

This will enable BubbleStackView to apply a set of changes more
intelligently and to more easily set up the correct animations in
response.

Test: atest BubbleDataTest
Bug: 123542488
Change-Id: Ie83b4eeca2d66953141d701780cf3037830c1b31
parent b92f0771
Loading
Loading
Loading
Loading
+48 −43
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import android.os.ServiceManager;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.Pair;
import android.view.Display;
import android.view.IPinnedStackController;
import android.view.IPinnedStackListener;
@@ -486,16 +487,32 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
    private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {

        @Override
        public void onBubbleAdded(Bubble bubble) {
        public void applyUpdate(BubbleData.Update update) {
            if (mStackView == null && update.addedBubble != null) {
                // Lazy init stack view when the first bubble is added.
                ensureStackViewCreated();
            mStackView.addBubble(bubble);
            }

        @Override
        public void onBubbleRemoved(Bubble bubble, @DismissReason int reason) {
            if (mStackView != null) {
                mStackView.removeBubble(bubble);
            // If not yet initialized, ignore all other changes.
            if (mStackView == null) {
                return;
            }

            if (update.addedBubble != null) {
                mStackView.addBubble(update.addedBubble);
            }

            // Collapsing? Do this first before remaining steps.
            if (update.expandedChanged && !update.expanded) {
                mStackView.setExpanded(false);
            }

            // Do removals, if any.
            for (Pair<Bubble, Integer> removed : update.removedBubbles) {
                final Bubble bubble = removed.first;
                @DismissReason final int reason = removed.second;
                mStackView.removeBubble(bubble);

                if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
                        && !bubble.entry.showInShadeWhenBubble()) {
                    // The bubble is gone & the notification is gone, time to actually remove it
@@ -505,43 +522,31 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                    // The notification is still in the shade but we've removed the bubble so
                    // lets make sure NoMan knows it's not a bubble anymore
                    try {
                    mBarService.onNotificationBubbleChanged(bubble.getKey(), false /* isBubble */);
                        mBarService.onNotificationBubbleChanged(bubble.getKey(),
                                false /* isBubble */);
                    } catch (RemoteException e) {
                        // Bad things have happened
                    }
                }
            }

        public void onBubbleUpdated(Bubble bubble) {
            if (mStackView != null) {
                mStackView.updateBubble(bubble);
            }
            if (update.updatedBubble != null) {
                mStackView.updateBubble(update.updatedBubble);
            }

        @Override
        public void onOrderChanged(List<Bubble> bubbles) {
            if (mStackView != null) {
                mStackView.updateBubbleOrder(bubbles);
            }
            if (update.orderChanged) {
                mStackView.updateBubbleOrder(update.bubbles);
            }

        @Override
        public void onSelectionChanged(@Nullable Bubble selectedBubble) {
            if (mStackView != null) {
                mStackView.setSelectedBubble(selectedBubble);
            }
            if (update.selectionChanged) {
                mStackView.setSelectedBubble(update.selectedBubble);
            }

        @Override
        public void onExpandedChanged(boolean expanded) {
            if (mStackView != null) {
                mStackView.setExpanded(expanded);
            }
            // Expanding? Apply this last.
            if (update.expandedChanged && update.expanded) {
                mStackView.setExpanded(true);
            }

        // Runs on state change.
        @Override
        public void apply() {
            mNotificationEntryManager.updateNotifications();
            updateStack();

+61 −129
Original line number Diff line number Diff line
@@ -60,54 +60,46 @@ public class BubbleData {
    private static final Comparator<Map.Entry<String, Long>> GROUPS_BY_MAX_SORT_KEY_DESCENDING =
            Comparator.<Map.Entry<String, Long>, Long>comparing(Map.Entry::getValue).reversed();

    /**
     * This interface reports changes to the state and appearance of bubbles which should be applied
     * as necessary to the UI.
     * <p>
     * Each operation is a report of a pending operation. Each should be considered in
     * combination, when {@link #apply()} is called. For example, both: onExpansionChanged,
     * and onOrderChanged
     */
    interface Listener {
    /** Contains information about changes that have been made to the state of bubbles. */
    static final class Update {
        boolean expandedChanged;
        boolean selectionChanged;
        boolean orderChanged;
        boolean expanded;
        @Nullable Bubble selectedBubble;
        @Nullable Bubble addedBubble;
        @Nullable Bubble updatedBubble;
        // Pair with Bubble and @DismissReason Integer
        final List<Pair<Bubble, Integer>> removedBubbles = new ArrayList<>();

        /**
         * A new Bubble has been added. A call to {@link #onOrderChanged(List)} will
         * follow, including the new Bubble in position
         */
        void onBubbleAdded(Bubble bubble);
        // A read-only view of the bubbles list, changes there will be reflected here.
        final List<Bubble> bubbles;

        /**
         * A Bubble has been removed. A call to {@link #onOrderChanged(List)} will
         * follow.
         */
        void onBubbleRemoved(Bubble bubble, @DismissReason int reason);

        /**
         * An existing bubble has been updated.
         *
         * @param bubble the bubble which was updated
         */
        void onBubbleUpdated(Bubble bubble);
        private Update(List<Bubble> bubbleOrder) {
            bubbles = Collections.unmodifiableList(bubbleOrder);
        }

        /**
         * Indicates that one or more bubbles should change position. This may be result of insert,
         * or removal of a Bubble, in addition to re-sorting existing Bubbles.
         *
         * @param bubbles an immutable list of the bubbles in the new order
         */
        void onOrderChanged(List<Bubble> bubbles);
        boolean anythingChanged() {
            return expandedChanged
                    || selectionChanged
                    || addedBubble != null
                    || updatedBubble != null
                    || !removedBubbles.isEmpty()
                    || orderChanged;
        }

        /** Indicates the selected bubble changed. */
        void onSelectionChanged(@Nullable Bubble selectedBubble);
        void bubbleRemoved(Bubble bubbleToRemove, @DismissReason  int reason) {
            removedBubbles.add(new Pair<>(bubbleToRemove, reason));
        }
    }

    /**
         * The UI should transition to the given state, incorporating any pending changes during
         * the animation.
     * This interface reports changes to the state and appearance of bubbles which should be applied
     * as necessary to the UI.
     */
        void onExpandedChanged(boolean expanded);

        /** Commit any pending operations (since last call of apply()) */
        void apply();
    interface Listener {
        /** Reports changes have have occurred as a result of the most recent operation. */
        void applyUpdate(Update update);
    }

    interface TimeSource {
@@ -115,17 +107,12 @@ public class BubbleData {
    }

    private final Context mContext;
    private List<Bubble> mBubbles;
    private final List<Bubble> mBubbles;
    private Bubble mSelectedBubble;
    private boolean mExpanded;

    // State tracked during an operation -- keeps track of what listener events to dispatch.
    private boolean mExpandedChanged;
    private boolean mOrderChanged;
    private boolean mSelectionChanged;
    private Bubble mUpdatedBubble;
    private Bubble mAddedBubble;
    private final List<Pair<Bubble, Integer>> mRemovedBubbles = new ArrayList<>();
    private Update mStateChange;

    private TimeSource mTimeSource = System::currentTimeMillis;

@@ -136,6 +123,7 @@ public class BubbleData {
    public BubbleData(Context context) {
        mContext = context;
        mBubbles = new ArrayList<>();
        mStateChange = new Update(mBubbles);
    }

    public boolean hasBubbles() {
@@ -185,7 +173,6 @@ public class BubbleData {
            // Updates an existing bubble
            bubble.setEntry(entry);
            doUpdate(bubble);
            mUpdatedBubble = bubble;
        }
        if (shouldAutoExpand(entry)) {
            setSelectedBubbleInternal(bubble);
@@ -217,11 +204,11 @@ public class BubbleData {
            minInsertPoint = newGroup ? 0 : findFirstIndexForGroup(bubble.getGroupId());
        }
        if (insertBubble(minInsertPoint, bubble) < mBubbles.size() - 1) {
            mOrderChanged = true;
            mStateChange.orderChanged = true;
        }
        mAddedBubble = bubble;
        mStateChange.addedBubble = bubble;
        if (!isExpanded()) {
            mOrderChanged |= packGroup(findFirstIndexForGroup(bubble.getGroupId()));
            mStateChange.orderChanged |= packGroup(findFirstIndexForGroup(bubble.getGroupId()));
            // Top bubble becomes selected.
            setSelectedBubbleInternal(mBubbles.get(0));
        }
@@ -243,6 +230,7 @@ public class BubbleData {
        if (DEBUG) {
            Log.d(TAG, "doUpdate: " + bubble);
        }
        mStateChange.updatedBubble = bubble;
        if (!isExpanded()) {
            // while collapsed, update causes re-pack
            int prevPos = mBubbles.indexOf(bubble);
@@ -250,7 +238,7 @@ public class BubbleData {
            int newPos = insertBubble(0, bubble);
            if (prevPos != newPos) {
                packGroup(newPos);
                mOrderChanged = true;
                mStateChange.orderChanged = true;
            }
            setSelectedBubbleInternal(mBubbles.get(0));
        }
@@ -269,12 +257,12 @@ public class BubbleData {
        }
        if (indexToRemove < mBubbles.size() - 1) {
            // Removing anything but the last bubble means positions will change.
            mOrderChanged = true;
            mStateChange.orderChanged = true;
        }
        mBubbles.remove(indexToRemove);
        mRemovedBubbles.add(Pair.create(bubbleToRemove, reason));
        mStateChange.bubbleRemoved(bubbleToRemove, reason);
        if (!isExpanded()) {
            mOrderChanged |= repackAll();
            mStateChange.orderChanged |= repackAll();
        }

        // Note: If mBubbles.isEmpty(), then mSelectedBubble is now null.
@@ -301,77 +289,20 @@ public class BubbleData {
            Bubble bubble = mBubbles.remove(0);
            bubble.setDismissed();
            maybeSendDeleteIntent(reason, bubble.entry);
            mRemovedBubbles.add(Pair.create(bubble, reason));
            mStateChange.bubbleRemoved(bubble, reason);
        }
        dispatchPendingChanges();
    }


    private void dispatchPendingChanges() {
        if (mListener == null) {
            mExpandedChanged = false;
            mAddedBubble = null;
            mSelectionChanged = false;
            mRemovedBubbles.clear();
            mUpdatedBubble = null;
            mOrderChanged = false;
            return;
        }
        boolean anythingChanged = false;

        if (mAddedBubble != null) {
            mListener.onBubbleAdded(mAddedBubble);
            mAddedBubble = null;
            anythingChanged = true;
        }

        // Compat workaround: Always collapse first.
        if (mExpandedChanged && !mExpanded) {
            mListener.onExpandedChanged(mExpanded);
            mExpandedChanged = false;
            anythingChanged = true;
        }

        if (mSelectionChanged) {
            mListener.onSelectionChanged(mSelectedBubble);
            mSelectionChanged = false;
            anythingChanged = true;
        }

        if (!mRemovedBubbles.isEmpty()) {
            for (Pair<Bubble, Integer> removed : mRemovedBubbles) {
                mListener.onBubbleRemoved(removed.first, removed.second);
            }
            mRemovedBubbles.clear();
            anythingChanged = true;
        }

        if (mUpdatedBubble != null) {
            mListener.onBubbleUpdated(mUpdatedBubble);
            mUpdatedBubble = null;
            anythingChanged = true;
        }

        if (mOrderChanged) {
            mListener.onOrderChanged(mBubbles);
            mOrderChanged = false;
            anythingChanged = true;
        }

        if (mExpandedChanged) {
            mListener.onExpandedChanged(mExpanded);
            mExpandedChanged = false;
            anythingChanged = true;
        }

        if (anythingChanged) {
            mListener.apply();
        if (mListener != null && mStateChange.anythingChanged()) {
            mListener.applyUpdate(mStateChange);
        }
        mStateChange = new Update(mBubbles);
    }

    /**
     * Requests a change to the selected bubble. Calls {@link Listener#onSelectionChanged} if
     * the value changes.
     * Requests a change to the selected bubble.
     *
     * @param bubble the new selected bubble
     */
@@ -391,13 +322,12 @@ public class BubbleData {
            bubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
        }
        mSelectedBubble = bubble;
        mSelectionChanged = true;
        return;
        mStateChange.selectedBubble = bubble;
        mStateChange.selectionChanged = true;
    }

    /**
     * Requests a change to the expanded state. Calls {@link Listener#onExpandedChanged} if
     * the value changes.
     * Requests a change to the expanded state.
     *
     * @param shouldExpand the new requested state
     */
@@ -418,11 +348,11 @@ public class BubbleData {
                return;
            }
            mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
            mOrderChanged |= repackAll();
            mStateChange.orderChanged |= repackAll();
        } else if (!mBubbles.isEmpty()) {
            // Apply ordering and grouping rules from expanded -> collapsed, then save
            // the result.
            mOrderChanged |= repackAll();
            mStateChange.orderChanged |= repackAll();
            // Save the state which should be returned to when expanded (with no other changes)

            if (mBubbles.indexOf(mSelectedBubble) > 0) {
@@ -442,7 +372,8 @@ public class BubbleData {
            }
        }
        mExpanded = shouldExpand;
        mExpandedChanged = true;
        mStateChange.expanded = shouldExpand;
        mStateChange.expandedChanged = true;
    }

    private static long sortKey(Bubble bubble) {
@@ -569,7 +500,8 @@ public class BubbleData {
        if (repacked.equals(mBubbles)) {
            return false;
        }
        mBubbles = repacked;
        mBubbles.clear();
        mBubbles.addAll(repacked);
        return true;
    }

@@ -595,7 +527,7 @@ public class BubbleData {
        for (Iterator<Bubble> i = mBubbles.iterator(); i.hasNext(); ) {
            Bubble bubble = i.next();
            if (bubble.getGroupId().equals(blockedGroupId)) {
                mRemovedBubbles.add(Pair.create(bubble, BubbleController.DISMISS_BLOCKED));
                mStateChange.bubbleRemoved(bubble, BubbleController.DISMISS_BLOCKED);
                i.remove();
            }
        }
+169 −78

File changed.

Preview size limit exceeded, changes collapsed.