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

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

Merge "Implement new bubble animation when collapsed" into main

parents 0fb4cce4 9e15f07c
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 {