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

Commit 4d932b19 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[Bubbles] Fix flicker when collasping and expanding bubble" into main

parents d28cd594 fbb08fc9
Loading
Loading
Loading
Loading
+93 −0
Original line number Diff line number Diff line
@@ -257,7 +257,10 @@ class BubbleBarLayerViewTest {
        val bubble = createBubble("first")

        getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(bubble) }
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(bubble)).isTrue()

        waitForExpandedViewAnimation()
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(bubble)).isFalse()

        // Scrim, dismiss view and expanded view
        assertThat(bubbleBarLayerView.childCount).isEqualTo(3)
@@ -270,14 +273,20 @@ class BubbleBarLayerViewTest {
        val secondBubble = createBubble("second")

        getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(firstBubble) }
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(firstBubble)).isTrue()

        waitForExpandedViewAnimation()
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(firstBubble)).isFalse()

        getInstrumentation().runOnMainSync { bubbleBarLayerView.removeBubble(firstBubble) {} }
        // Expanded view is removed when bubble is removed
        assertThat(firstBubble.bubbleBarExpandedView).isNull()

        getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(secondBubble) }
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(secondBubble)).isTrue()

        waitForExpandedViewAnimation()
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(secondBubble)).isFalse()

        assertThat(bubbleBarLayerView.children.count { it is BubbleBarExpandedView }).isEqualTo(1)
        assertThat(bubbleBarLayerView.children.last()).isEqualTo(secondBubble.bubbleBarExpandedView)
@@ -289,10 +298,18 @@ class BubbleBarLayerViewTest {
        val secondBubble = createBubble("second")

        getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(firstBubble) }
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(firstBubble)).isTrue()

        waitForExpandedViewAnimation()
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(firstBubble)).isFalse()

        getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(secondBubble) }
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(firstBubble)).isTrue()
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(secondBubble)).isTrue()

        waitForExpandedViewAnimation()
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(firstBubble)).isFalse()
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(secondBubble)).isFalse()

        assertThat(bubbleBarLayerView.children.count { it is BubbleBarExpandedView }).isEqualTo(1)
        assertThat(bubbleBarLayerView.children.last()).isEqualTo(secondBubble.bubbleBarExpandedView)
@@ -441,6 +458,76 @@ class BubbleBarLayerViewTest {
        assertThat(bubble.bubbleBarExpandedView!!.x).isEqualTo(previousX)
    }

    @Test
    fun testSwitchBubblesQuickly() {
        val firstBubble = createBubble("first")
        val secondBubble = createBubble("second")
        val thirdBubble = createBubble("third")

        getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(firstBubble) }
        waitForExpandedViewAnimation()

        getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(secondBubble) }
        waitForExpandedViewAnimation()

        getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(thirdBubble) }
        waitForExpandedViewAnimation()

        // Switch from 3rd -> 2nd, then immediately followed by 2nd -> 1st, without waiting for
        // animation completion.
        getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(secondBubble) }
        getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(firstBubble) }
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(firstBubble)).isTrue()
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(secondBubble)).isTrue()
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(thirdBubble)).isTrue()

        waitForExpandedViewAnimation()
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(firstBubble)).isFalse()
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(secondBubble)).isFalse()
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(thirdBubble)).isFalse()

        assertThat(bubbleBarLayerView.children.count { it is BubbleBarExpandedView }).isEqualTo(1)
        assertThat(bubbleBarLayerView.children.last()).isEqualTo(firstBubble.bubbleBarExpandedView)
    }

    @Test
    fun testCollapse() {
        val bubble = createBubble("first")

        getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(bubble) }
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(bubble)).isTrue()

        waitForExpandedViewAnimation()
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(bubble)).isFalse()

        getInstrumentation().runOnMainSync { bubbleBarLayerView.collapse() }
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(bubble)).isTrue()

        waitForCollapseViewAnimation()
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(bubble)).isFalse()

        assertThat(bubbleBarLayerView.children.count { it is BubbleBarExpandedView }).isEqualTo(0)
    }

    @Test
    fun testCollapseThenImmediatelyExpand() {
        val bubble = createBubble("first")

        getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(bubble) }
        waitForExpandedViewAnimation()

        // Without waiting for animation completion, we collapse then immediately expand.
        getInstrumentation().runOnMainSync { bubbleBarLayerView.collapse() }
        getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(bubble) }
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(bubble)).isTrue()

        // waitForExpandedViewAnimation() also covers the collapse animation.
        waitForExpandedViewAnimation()
        assertThat(bubbleBarLayerView.isAnimatingBubbleTracked(bubble)).isFalse()

        assertThat(bubbleBarLayerView.children.count { it is BubbleBarExpandedView }).isEqualTo(1)
    }

    private fun createBubble(key: String): Bubble {
        val bubble = FakeBubbleFactory.createChatBubble(context, key).also {
            testBubblesList.add(it)
@@ -484,6 +571,12 @@ class BubbleBarLayerViewTest {
        getInstrumentation().waitForIdleSync()
    }

    private fun waitForCollapseViewAnimation() {
        // Collapse animation also uses both the animator and physical animator, so waiting for
        // collapse animation is the same as waiting for the expand animation.
        waitForExpandedViewAnimation()
    }

    private fun View.dispatchTouchEvent(eventTime: Long, action: Int, point: PointF) {
        val event = MotionEvent.obtain(0L, eventTime, action, point.x, point.y, 0)
        getInstrumentation().runOnMainSync { dispatchTouchEvent(event) }
+39 −26
Original line number Diff line number Diff line
@@ -110,10 +110,10 @@ public class BubbleBarLayerView extends FrameLayout
    private Insets mInsets;

    /**
     * Tracks bubbles used in switch animations. Ideally, we should track bubbles used in all types
     * of animations, but so far it only seems helpful for switch animations.
     * Tracks bubbles used in animations. We keep animating bubbles attached until the end of all
     * animations.
     */
    private final ReferenceCounter<BubbleViewProvider> mSwitchAnimationBubbleTracker =
    private final ReferenceCounter<BubbleViewProvider> mAnimatingBubbleTracker =
            new ReferenceCounter();

    public BubbleBarLayerView(Context context, BubbleController controller, BubbleData bubbleData,
@@ -354,9 +354,9 @@ public class BubbleBarLayerView extends FrameLayout
                // Add temporary references to the previous and the current bubbles to prevent
                // them from being removed when canceling ongoing animations. References will be
                // added again when starting the switch animation.
                mSwitchAnimationBubbleTracker.increment(previousBubble, b);
                mAnimatingBubbleTracker.increment(previousBubble, b);
                mAnimationHelper.cancelAnimations();
                mSwitchAnimationBubbleTracker.decrement(previousBubble, b);
                mAnimatingBubbleTracker.decrement(previousBubble, b);

                // Need to check again because cancelAnimations might remove it from the parent.
                // TODO(b/403612574) use reference tracking for other animations.
@@ -459,7 +459,11 @@ public class BubbleBarLayerView extends FrameLayout
        if (!mIsExpanded || mExpandedBubble == null) {
            throw new IllegalStateException("Can't animateExpand without expnaded state");
        }
        final Runnable afterAnimation = () -> {
        final BubbleViewProvider expandedBubble = mExpandedBubble;
        mAnimatingBubbleTracker.increment(previousBubble, expandedBubble);
        final Runnable endRunnable = () -> {
            mAnimatingBubbleTracker.decrement(previousBubble, expandedBubble);
            ensureAnimationEndingState();
            if (mExpandedView == null) return;
            // Touch delegate for the menu
            BubbleBarHandleView view = mExpandedView.getHandleView();
@@ -476,15 +480,9 @@ public class BubbleBarLayerView extends FrameLayout
        };

        if (previousBubble != null) {
            final BubbleViewProvider expandedBubble = mExpandedBubble;
            mSwitchAnimationBubbleTracker.increment(previousBubble, expandedBubble);
            mAnimationHelper.animateSwitch(previousBubble, expandedBubble, () -> {
                mSwitchAnimationBubbleTracker.decrement(previousBubble, expandedBubble);
                ensureSwitchAnimationEndingState();
                afterAnimation.run();
            });
            mAnimationHelper.animateSwitch(previousBubble, expandedBubble, endRunnable);
        } else {
            mAnimationHelper.animateExpansion(mExpandedBubble, afterAnimation);
            mAnimationHelper.animateExpansion(expandedBubble, endRunnable);
        }
    }

@@ -522,8 +520,15 @@ public class BubbleBarLayerView extends FrameLayout
        if (!mIsExpanded || mExpandedBubble == null) {
            throw new IllegalStateException("Can't animateExpand without expanded state");
        }
        final BubbleViewProvider expandedBubble = mExpandedBubble;
        mAnimatingBubbleTracker.increment(expandedBubble);
        final Runnable endRunnable = () -> {
            mAnimatingBubbleTracker.decrement(expandedBubble);
            ensureAnimationEndingState();
            animFinish.run();
        };
        mAnimationHelper.animateConvert(mExpandedBubble, startT, startBounds, startScale, snapshot,
                taskLeash, animFinish);
                taskLeash, endRunnable);
    }

    public void removeBubble(@NonNull Bubble bubble, @NonNull Runnable endAction) {
@@ -568,10 +573,12 @@ public class BubbleBarLayerView extends FrameLayout
            return;
        }
        mIsExpanded = false;
        final BubbleBarExpandedView viewToRemove = mExpandedView;
        final BubbleViewProvider bubbleToCollapse = mExpandedBubble;
        mEducationViewController.hideEducation(/* animated = */ true);
        Runnable runnable = () -> {
            removeView(viewToRemove);
        mAnimatingBubbleTracker.increment(bubbleToCollapse);
        final Runnable endRunnable = () -> {
            mAnimatingBubbleTracker.decrement(bubbleToCollapse);
            ensureAnimationEndingState();
            if (endAction != null) {
                endAction.run();
            }
@@ -580,12 +587,13 @@ public class BubbleBarLayerView extends FrameLayout
            }
        };
        if (mDragController != null && mDragController.isStuckToDismiss()) {
            mAnimationHelper.animateDismiss(runnable);
            mAnimationHelper.animateDismiss(endRunnable);
        } else {
            mAnimationHelper.animateCollapse(runnable);
            mAnimationHelper.animateCollapse(endRunnable);
        }
        mBubbleController.getSysuiProxy().onStackExpandChanged(false);
        mExpandedView = null;
        mExpandedBubble = null;
        mDragController = null;
        setTouchDelegate(null);
        showScrim(false);
@@ -720,16 +728,21 @@ public class BubbleBarLayerView extends FrameLayout
        setupDragZoneFactory();
    }

    /** Ensures that only the expanded bubble is added at the end of all switch animations. */
    private void ensureSwitchAnimationEndingState() {
        if (!mSwitchAnimationBubbleTracker.hasReferences()) {
            // All switch animations are done, so we remove bubbles except for the expanded one.
            mSwitchAnimationBubbleTracker.forEach(bubble -> {
    /** Ensures that only the expanded bubble is added at the end of all animations. */
    private void ensureAnimationEndingState() {
        if (!mAnimatingBubbleTracker.hasReferences()) {
            // All animations are done, so we remove bubbles except for the expanded one.
            mAnimatingBubbleTracker.forEach(bubble -> {
                if (bubble != mExpandedBubble) {
                    removeView(bubble.getBubbleBarExpandedView());
                }
            });
            mSwitchAnimationBubbleTracker.clear();
            mAnimatingBubbleTracker.clear();
        }
    }

    @VisibleForTesting
    boolean isAnimatingBubbleTracked(@NonNull BubbleViewProvider bubble) {
        return mAnimatingBubbleTracker.isTracked(bubble);
    }
}
+9 −0
Original line number Diff line number Diff line
@@ -88,4 +88,13 @@ public class ReferenceCounter<T> {
    public void clear() {
        mReferences.clear();
    }

    /**
     * Returns whether the item is tracked. An item is tracked even if its reference count is
     * zero.
     */
    @VisibleForTesting
    public boolean isTracked(@NonNull T item) {
        return mReferences.containsKey(item);
    }
}