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

Commit 9e15f07c authored by Liran Binyamin's avatar Liran Binyamin
Browse files

Implement new bubble animation when collapsed

Bounce the bubble bar when a new bubble is received while the bubble
bar is collapsed.

Demo: http://recall/-/bJtug1HhvXkkeA4MQvIaiP/fn7NWNY3htuR6K3wxhfcK2

Flag: com.android.wm.shell.enable_bubble_bar
Bug: 280605790
Test: atest BubbleBarViewAnimatorTest
Change-Id: I4c622454fd99f6bb5a332b3fe4aa2764c8af93af
parent ff83f1c4
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -445,6 +445,7 @@
    <dimen name="bubblebar_elevation">1dp</dimen>
    <dimen name="bubblebar_drag_elevation">2dp</dimen>
    <dimen name="bubblebar_hotseat_adjustment_threshold">90dp</dimen>
    <dimen name="bubblebar_bounce_distance">20dp</dimen>

    <dimen name="bubblebar_icon_size_small">32dp</dimen>
    <dimen name="bubblebar_icon_size">36dp</dimen>
+5 −0
Original line number Diff line number Diff line
@@ -435,6 +435,11 @@ public class BubbleBarViewController {
            return;
        }

        if (mBubbleStashController.isBubblesShowingOnHome() && !isExpanding && !isExpanded()) {
            mBubbleBarViewAnimator.animateBubbleBarForCollapsed(bubble);
            return;
        }

        // only animate the new bubble if we're in an app and not auto expanding
        if (isInApp && !isExpanding && !isExpanded()) {
            mBubbleBarViewAnimator.animateBubbleInForStashed(bubble);
+62 −2
Original line number Diff line number Diff line
@@ -18,8 +18,12 @@ package com.android.launcher3.taskbar.bubbles.animation

import android.view.View
import android.view.View.VISIBLE
import androidx.core.animation.Animator
import androidx.core.animation.AnimatorListenerAdapter
import androidx.core.animation.ObjectAnimator
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.launcher3.R
import com.android.launcher3.taskbar.bubbles.BubbleBarBubble
import com.android.launcher3.taskbar.bubbles.BubbleBarView
import com.android.launcher3.taskbar.bubbles.BubbleStashController
@@ -36,6 +40,8 @@ constructor(
) {

    private var animatingBubble: AnimatingBubble? = null
    private val bubbleBarBounceDistanceInPx =
            bubbleBarView.resources.getDimensionPixelSize(R.dimen.bubblebar_bounce_distance)

    private companion object {
        /** The time to show the flyout. */
@@ -44,6 +50,8 @@ constructor(
        const val BUBBLE_ANIMATION_INITIAL_SCALE_Y = 0.3f
        /** The minimum alpha value to make the bubble bar touchable. */
        const val MIN_ALPHA_FOR_TOUCHABLE = 0.5f
        /** The duration of the bounce animation. */
        const val BUBBLE_BAR_BOUNCE_ANIMATION_DURATION_MS = 250L
    }

    /** Wrapper around the animating bubble with its show and hide animations. */
@@ -277,7 +285,7 @@ constructor(
        if (animator.isRunning()) animator.cancel()
        // the animation of a new bubble is divided into 2 parts. The first part shows the bubble
        // and the second part hides it after a delay if we are in an app.
        val showAnimation = buildBubbleBarBounceAnimation()
        val showAnimation = buildBubbleBarSpringInAnimation()
        val hideAnimation =
            if (isInApp && !isExpanding) {
                buildBubbleBarToHandleAnimation()
@@ -296,7 +304,7 @@ constructor(
        scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
    }

    private fun buildBubbleBarBounceAnimation() = Runnable {
    private fun buildBubbleBarSpringInAnimation() = Runnable {
        // prepare the bubble bar for the animation
        bubbleBarView.onAnimatingBubbleStarted()
        bubbleBarView.translationY = bubbleBarView.height.toFloat()
@@ -316,6 +324,42 @@ constructor(
        animator.start()
    }

    fun animateBubbleBarForCollapsed(b: BubbleBarBubble) {
        val bubbleView = b.view
        val animator = PhysicsAnimator.getInstance(bubbleView)
        if (animator.isRunning()) animator.cancel()
        val showAnimation = buildBubbleBarBounceAnimation()
        val hideAnimation = Runnable {
            animatingBubble = null
            bubbleStashController.showBubbleBarImmediate()
            bubbleBarView.onAnimatingBubbleCompleted()
            bubbleStashController.updateTaskbarTouchRegion()
        }
        animatingBubble = AnimatingBubble(bubbleView, showAnimation, hideAnimation)
        scheduler.post(showAnimation)
        scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
    }

    /**
     * The bubble bar animation when it is collapsed is divided into 2 chained animations. The first
     * animation is a regular accelerate animation that moves the bubble bar upwards. When it ends
     * the bubble bar moves back to its initial position with a spring animation.
     */
    private fun buildBubbleBarBounceAnimation() = Runnable {
        bubbleBarView.onAnimatingBubbleStarted()
        val ty = bubbleBarView.translationY

        val springBackAnimation = PhysicsAnimator.getInstance(bubbleBarView)
        springBackAnimation.setDefaultSpringConfig(springConfig)
        springBackAnimation.spring(DynamicAnimation.TRANSLATION_Y, ty)

        // animate the bubble bar up and start the spring back down animation when it ends.
        ObjectAnimator.ofFloat(bubbleBarView, View.TRANSLATION_Y, ty - bubbleBarBounceDistanceInPx)
            .withDuration(BUBBLE_BAR_BOUNCE_ANIMATION_DURATION_MS)
            .withEndAction { springBackAnimation.start() }
            .start()
    }

    /** Handles touching the animating bubble bar. */
    fun onBubbleBarTouchedWhileAnimating() {
        PhysicsAnimator.getInstance(bubbleBarView).cancelIfRunning()
@@ -344,4 +388,20 @@ constructor(
    private fun <T> PhysicsAnimator<T>.cancelIfRunning() {
        if (isRunning()) cancel()
    }

    private fun ObjectAnimator.withDuration(duration: Long): ObjectAnimator {
        setDuration(duration)
        return this
    }

    private fun ObjectAnimator.withEndAction(endAction: () -> Unit): ObjectAnimator {
        addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    endAction()
                }
            }
        )
        return this
    }
}
+43 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.View.VISIBLE
import android.widget.FrameLayout
import androidx.core.animation.AnimatorTestRule
import androidx.core.graphics.drawable.toBitmap
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.test.core.app.ApplicationProvider
@@ -41,6 +42,7 @@ import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
@@ -53,6 +55,8 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class BubbleBarViewAnimatorTest {

    @get:Rule val animatorTestRule = AnimatorTestRule()

    private val context = ApplicationProvider.getApplicationContext<Context>()
    private lateinit var animatorScheduler: TestBubbleBarViewAnimatorScheduler
    private lateinit var overflowView: BubbleView
@@ -380,6 +384,45 @@ class BubbleBarViewAnimatorTest {
        verify(bubbleStashController).showBubbleBarImmediate()
    }

    @Test
    fun animateBubbleBarForCollapsed() {
        setUpBubbleBar()
        setUpBubbleStashController()
        bubbleBarView.translationY = BAR_TRANSLATION_Y_FOR_HOTSEAT

        val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)

        val animator =
            BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)

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

        InstrumentationRegistry.getInstrumentation().runOnMainSync {}
        // verify we started animating
        assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()

        // advance the animation handler by the duration of the initial lift
        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            animatorTestRule.advanceTimeBy(250)
        }

        // the lift animation is complete; the spring back animation should start now
        InstrumentationRegistry.getInstrumentation().runOnMainSync {}
        barAnimator.assertIsRunning()
        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)

        assertThat(animatorScheduler.delayedBlock).isNotNull()
        InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)

        assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
        // the bubble bar translation y should be back to its initial value
        assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)

        verify(bubbleStashController).showBubbleBarImmediate()
    }

    private fun setUpBubbleBar() {
        bubbleBarView = BubbleBarView(context)
        InstrumentationRegistry.getInstrumentation().runOnMainSync {