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

Commit b4b5e79f authored by Liran Binyamin's avatar Liran Binyamin Committed by Android (Google) Code Review
Browse files

Merge "Interrupt bubble animation on stash change" into main

parents 8dec848a 209c7641
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -173,6 +173,13 @@ public class BubbleBarViewController {
        }
    }

    /** Notifies that the stash state is changing. */
    public void onStashStateChanging() {
        if (isAnimatingNewBubble()) {
            mBubbleBarViewAnimator.onStashStateChangingWhileAnimating();
        }
    }

    //
    // The below animators are exposed to BubbleStashController so it can manage the stashing
    // animation.
+30 −0
Original line number Diff line number Diff line
@@ -250,6 +250,11 @@ public class BubbleStashController {
                && !mBubblesShowingOnHome
                && !mBubblesShowingOnOverview;
        if (mIsStashed != isStashed) {
            // notify the view controller that the stash state is about to change so that it can
            // cancel an ongoing animation if there is one.
            // note that this has to be called before updating mIsStashed with the new value,
            // otherwise interrupting an ongoing animation may update it again with the wrong state
            mBarViewController.onStashStateChanging();
            mIsStashed = isStashed;
            if (mAnimator != null) {
                mAnimator.cancel();
@@ -423,4 +428,29 @@ public class BubbleStashController {
        mIsStashed = true;
        onIsStashedChanged();
    }

    /**
     * Updates the values of the internal animators after the new bubble animation was interrupted
     *
     * @param isStashed whether the current state should be stashed
     * @param bubbleBarTranslationY the current bubble bar translation. this is only used if the
     *                              bubble bar is showing to ensure that the stash animator runs
     *                              smoothly.
     */
    public void onNewBubbleAnimationInterrupted(boolean isStashed, float bubbleBarTranslationY) {
        if (isStashed) {
            mBubbleStashedHandleAlpha.setValue(1);
            mIconAlphaForStash.setValue(0);
            mIconScaleForStash.updateValue(STASHED_BAR_SCALE);
            mIconTranslationYForStash.updateValue(getStashTranslation());
        } else {
            mBubbleStashedHandleAlpha.setValue(0);
            mHandleViewController.setTranslationYForSwipe(0);
            mIconAlphaForStash.setValue(1);
            mIconScaleForStash.updateValue(1);
            mIconTranslationYForStash.updateValue(bubbleBarTranslationY);
        }
        mIsStashed = isStashed;
        onIsStashedChanged();
    }
}
+28 −3
Original line number Diff line number Diff line
@@ -179,7 +179,17 @@ constructor(
                }
            }
        }
        animator.addEndListener { _, _, _, _, _, _, _ ->
        animator.addEndListener { _, _, _, canceled, _, _, _ ->
            // if the show animation was canceled, also cancel the hide animation. this is typically
            // canceled in this class, but could potentially be canceled elsewhere.
            if (canceled) {
                val hideAnimation = animatingBubble?.hideAnimation ?: return@addEndListener
                scheduler.cancel(hideAnimation)
                animatingBubble = null
                bubbleBarView.onAnimatingBubbleCompleted()
                bubbleBarView.relativePivotY = 1f
                return@addEndListener
            }
            // the bubble bar is now fully settled in. update taskbar touch region so it's touchable
            bubbleStashController.updateTaskbarTouchRegion()
        }
@@ -200,6 +210,7 @@ constructor(
     * 3. The third part is the overshoot. The handle is made fully visible.
     */
    private fun buildHideAnimation() = Runnable {
        if (animatingBubble == null) return@Runnable
        val offset = bubbleStashController.diffBetweenHandleAndBarCenters
        val stashedHandleTranslationY =
            bubbleStashController.stashedHandleTranslationForNewBubbleAnimation
@@ -238,9 +249,9 @@ constructor(
                }
            }
        }
        animator.addEndListener { _, _, _, _, _, _, _ ->
        animator.addEndListener { _, _, _, canceled, _, _, _ ->
            animatingBubble = null
            bubbleStashController.stashBubbleBarImmediate()
            if (!canceled) bubbleStashController.stashBubbleBarImmediate()
            bubbleBarView.onAnimatingBubbleCompleted()
            bubbleBarView.relativePivotY = 1f
            bubbleStashController.updateTaskbarTouchRegion()
@@ -256,4 +267,18 @@ constructor(
        bubbleBarView.relativePivotY = 1f
        animatingBubble = null
    }

    /** Notifies the animator that the taskbar area was touched during an animation. */
    fun onStashStateChangingWhileAnimating() {
        val hideAnimation = animatingBubble?.hideAnimation ?: return
        scheduler.cancel(hideAnimation)
        animatingBubble = null
        bubbleStashController.stashedHandlePhysicsAnimator.cancel()
        bubbleBarView.onAnimatingBubbleCompleted()
        bubbleBarView.relativePivotY = 1f
        bubbleStashController.onNewBubbleAnimationInterrupted(
            /* isStashed= */ bubbleBarView.alpha == 0f,
            bubbleBarView.translationY
        )
    }
}
+160 −68
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -52,47 +53,23 @@ import org.mockito.kotlin.whenever
class BubbleBarViewAnimatorTest {

    private val context = ApplicationProvider.getApplicationContext<Context>()
    private val animatorScheduler = TestBubbleBarViewAnimatorScheduler()
    private lateinit var animatorScheduler: TestBubbleBarViewAnimatorScheduler
    private lateinit var overflowView: BubbleView
    private lateinit var bubbleView: BubbleView
    private lateinit var bubble: BubbleBarBubble
    private lateinit var bubbleBarView: BubbleBarView
    private lateinit var bubbleStashController: BubbleStashController

    @Before
    fun setUp() {
        animatorScheduler = TestBubbleBarViewAnimatorScheduler()
        PhysicsAnimatorTestUtils.prepareForTest()
    }

    @Test
    fun animateBubbleInForStashed() {
        lateinit var overflowView: BubbleView
        lateinit var bubbleView: BubbleView
        lateinit var bubble: BubbleBarBubble
        val bubbleBarView = BubbleBarView(context)
        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0)
            val inflater = LayoutInflater.from(context)

            val bitmap = ColorDrawable(Color.WHITE).toBitmap(width = 20, height = 20)
            overflowView =
                inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
            overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
            bubbleBarView.addView(overflowView)

            val bubbleInfo = BubbleInfo("key", 0, null, null, 0, context.packageName, null, false)
            bubbleView =
                inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
            bubble =
                BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
            bubbleView.setBubble(bubble)
            bubbleBarView.addView(bubbleView)
        }
        InstrumentationRegistry.getInstrumentation().waitForIdleSync()

        val bubbleStashController = mock<BubbleStashController>()
        whenever(bubbleStashController.isStashed).thenReturn(true)
        whenever(bubbleStashController.diffBetweenHandleAndBarCenters)
            .thenReturn(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS)
        whenever(bubbleStashController.stashedHandleTranslationForNewBubbleAnimation)
            .thenReturn(HANDLE_TRANSLATION)
        whenever(bubbleStashController.bubbleBarTranslationYForTaskbar)
            .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
        setUpBubbleBar()
        setUpBubbleStashController()

        val handle = View(context)
        val handleAnimator = PhysicsAnimator.getInstance(handle)
@@ -106,7 +83,7 @@ class BubbleBarViewAnimatorTest {
        }

        // let the animation start and wait for it to complete
        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
        InstrumentationRegistry.getInstrumentation().runOnMainSync {}
        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)

        assertThat(handle.alpha).isEqualTo(0)
@@ -123,7 +100,7 @@ class BubbleBarViewAnimatorTest {
        InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)

        // let the animation start and wait for it to complete
        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
        InstrumentationRegistry.getInstrumentation().runOnMainSync {}
        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)

        assertThat(handle.alpha).isEqualTo(1)
@@ -135,38 +112,8 @@ class BubbleBarViewAnimatorTest {

    @Test
    fun animateBubbleInForStashed_tapAnimatingBubble() {
        lateinit var overflowView: BubbleView
        lateinit var bubbleView: BubbleView
        lateinit var bubble: BubbleBarBubble
        val bubbleBarView = BubbleBarView(context)
        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0)
            val inflater = LayoutInflater.from(context)

            val bitmap = ColorDrawable(Color.WHITE).toBitmap(width = 20, height = 20)
            overflowView =
                inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
            overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
            bubbleBarView.addView(overflowView)

            val bubbleInfo = BubbleInfo("key", 0, null, null, 0, context.packageName, null, false)
            bubbleView =
                inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
            bubble =
                BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
            bubbleView.setBubble(bubble)
            bubbleBarView.addView(bubbleView)
        }
        InstrumentationRegistry.getInstrumentation().waitForIdleSync()

        val bubbleStashController = mock<BubbleStashController>()
        whenever(bubbleStashController.isStashed).thenReturn(true)
        whenever(bubbleStashController.diffBetweenHandleAndBarCenters)
            .thenReturn(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS)
        whenever(bubbleStashController.stashedHandleTranslationForNewBubbleAnimation)
            .thenReturn(HANDLE_TRANSLATION)
        whenever(bubbleStashController.bubbleBarTranslationYForTaskbar)
            .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
        setUpBubbleBar()
        setUpBubbleStashController()

        val handle = View(context)
        val handleAnimator = PhysicsAnimator.getInstance(handle)
@@ -180,7 +127,7 @@ class BubbleBarViewAnimatorTest {
        }

        // let the animation start and wait for it to complete
        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
        InstrumentationRegistry.getInstrumentation().runOnMainSync {}
        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)

        assertThat(handle.alpha).isEqualTo(0)
@@ -206,6 +153,151 @@ class BubbleBarViewAnimatorTest {
        assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
    }

    @Test
    fun animateBubbleInForStashed_touchTaskbarArea_whileShowing() {
        setUpBubbleBar()
        setUpBubbleStashController()

        val handle = View(context)
        val handleAnimator = PhysicsAnimator.getInstance(handle)
        whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)

        val animator =
            BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            animator.animateBubbleInForStashed(bubble)
        }

        // wait for the animation to start
        InstrumentationRegistry.getInstrumentation().runOnMainSync {}
        PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true }

        assertThat(handleAnimator.isRunning()).isTrue()
        assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
        // verify the hide bubble animation is pending
        assertThat(animatorScheduler.delayedBlock).isNotNull()

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            animator.onStashStateChangingWhileAnimating()
        }

        // verify that the hide animation was canceled
        assertThat(animatorScheduler.delayedBlock).isNull()
        assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
        verify(bubbleStashController).onNewBubbleAnimationInterrupted(any(), any())

        // PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait
        // again
        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
        assertThat(handleAnimator.isRunning()).isFalse()
    }

    @Test
    fun animateBubbleInForStashed_touchTaskbarArea_whileHiding() {
        setUpBubbleBar()
        setUpBubbleStashController()

        val handle = View(context)
        val handleAnimator = PhysicsAnimator.getInstance(handle)
        whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)

        val animator =
            BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            animator.animateBubbleInForStashed(bubble)
        }

        // let the animation start and wait for it to complete
        InstrumentationRegistry.getInstrumentation().runOnMainSync {}
        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)

        // execute the hide bubble animation
        assertThat(animatorScheduler.delayedBlock).isNotNull()
        InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)

        // wait for the hide animation to start
        InstrumentationRegistry.getInstrumentation().runOnMainSync {}
        assertThat(handleAnimator.isRunning()).isTrue()

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            animator.onStashStateChangingWhileAnimating()
        }

        assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
        verify(bubbleStashController).onNewBubbleAnimationInterrupted(any(), any())

        // PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait
        // again
        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
        assertThat(handleAnimator.isRunning()).isFalse()
    }

    @Test
    fun animateBubbleInForStashed_showAnimationCanceled() {
        setUpBubbleBar()
        setUpBubbleStashController()

        val handle = View(context)
        val handleAnimator = PhysicsAnimator.getInstance(handle)
        whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)

        val animator =
            BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            animator.animateBubbleInForStashed(bubble)
        }

        // wait for the animation to start
        InstrumentationRegistry.getInstrumentation().runOnMainSync {}
        PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true }

        assertThat(handleAnimator.isRunning()).isTrue()
        assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
        assertThat(animatorScheduler.delayedBlock).isNotNull()

        handleAnimator.cancel()
        assertThat(handleAnimator.isRunning()).isFalse()
        assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
        assertThat(animatorScheduler.delayedBlock).isNull()
    }

    private fun setUpBubbleBar() {
        bubbleBarView = BubbleBarView(context)
        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0)
            val inflater = LayoutInflater.from(context)

            val bitmap = ColorDrawable(Color.WHITE).toBitmap(width = 20, height = 20)
            overflowView =
                inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
            overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
            bubbleBarView.addView(overflowView)

            val bubbleInfo = BubbleInfo("key", 0, null, null, 0, context.packageName, null, false)
            bubbleView =
                inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
            bubble =
                BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
            bubbleView.setBubble(bubble)
            bubbleBarView.addView(bubbleView)
        }
        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
    }

    private fun setUpBubbleStashController() {
        bubbleStashController = mock<BubbleStashController>()
        whenever(bubbleStashController.isStashed).thenReturn(true)
        whenever(bubbleStashController.diffBetweenHandleAndBarCenters)
            .thenReturn(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS)
        whenever(bubbleStashController.stashedHandleTranslationForNewBubbleAnimation)
            .thenReturn(HANDLE_TRANSLATION)
        whenever(bubbleStashController.bubbleBarTranslationYForTaskbar)
            .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
    }

    private class TestBubbleBarViewAnimatorScheduler : BubbleBarViewAnimator.Scheduler {

        var delayedBlock: Runnable? = null