Loading quickstep/res/values/dimens.xml +1 −0 Original line number Diff line number Diff line Loading @@ -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> Loading quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +5 −0 Original line number Diff line number Diff line Loading @@ -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); Loading quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt +62 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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. */ Loading @@ -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. */ Loading Loading @@ -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() Loading @@ -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() Loading @@ -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() Loading Loading @@ -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 } } quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt +43 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 { Loading Loading
quickstep/res/values/dimens.xml +1 −0 Original line number Diff line number Diff line Loading @@ -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> Loading
quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +5 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt +62 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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. */ Loading @@ -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. */ Loading Loading @@ -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() Loading @@ -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() Loading @@ -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() Loading Loading @@ -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 } }
quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt +43 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 { Loading