From 12f77ba713d7c63a0b335d39c0696b581e979989 Mon Sep 17 00:00:00 2001 From: Liran Binyamin Date: Thu, 17 Oct 2024 12:14:40 -0400 Subject: [PATCH] Wire up flyout to new bubble animation When a bubble is created or updated we now animate the flyout view as part of the bar animation. Note that the flyout is not clickable yet, and that we're not yet handling bubble notifications interrupting each other. Flag: com.android.wm.shell.enable_bubble_bar Bug: 277815200 Test: atest BubbleBarViewAnimatorTest Test: atest BubbleBarFlyoutControllerTest Test: manual - verify flyout view is showing when creating bubble - on home - in app - when bubble bar is empty Change-Id: I315e46c89a4d20aaaa22972f0d71290a63481d9d --- quickstep/res/values/ids.xml | 2 + .../taskbar/bubbles/BubbleBarController.java | 2 - .../bubbles/BubbleBarViewController.java | 13 +- .../launcher3/taskbar/bubbles/BubbleView.java | 2 +- .../animation/BubbleBarViewAnimator.kt | 79 +++++++++--- .../flyout/BubbleBarFlyoutController.kt | 52 +++++--- .../bubbles/flyout/BubbleBarFlyoutView.kt | 1 + .../animation/BubbleBarViewAnimatorTest.kt | 113 +++++++++++++++++- .../flyout/BubbleBarFlyoutControllerTest.kt | 35 ++++-- 9 files changed, 248 insertions(+), 51 deletions(-) diff --git a/quickstep/res/values/ids.xml b/quickstep/res/values/ids.xml index 3091d9e930..c71bb762db 100644 --- a/quickstep/res/values/ids.xml +++ b/quickstep/res/values/ids.xml @@ -19,4 +19,6 @@ + + \ No newline at end of file diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java index 51e09abc65..b22fd6f3d0 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java @@ -377,8 +377,6 @@ public class BubbleBarController extends IBubblesListener.Stub { // Updates mean the dot state may have changed; any other changes were updated in // the populateBubble step. BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey()); - // If we're not stashed, we're visible so animate - bb.getView().updateDotVisibility(!mBubbleStashController.isStashed() /* animate */); mBubbleBarViewController.animateBubbleNotification( bb, /* isExpanding= */ false, /* isUpdate= */ true); } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java index 63f101fd51..76d36061f1 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java @@ -148,7 +148,8 @@ public class BubbleBarViewController { mBubbleBarFlyoutController = new BubbleBarFlyoutController( mBubbleBarContainer, createFlyoutPositioner(), createFlyoutTopBoundaryListener()); mBubbleBarViewAnimator = new BubbleBarViewAnimator( - mBarView, mBubbleStashController, mBubbleBarController::showExpandedView); + mBarView, mBubbleStashController, mBubbleBarFlyoutController, + mBubbleBarController::showExpandedView); mTaskbarViewPropertiesProvider = taskbarViewPropertiesProvider; onBubbleBarConfigurationChanged(/* animate= */ false); mActivity.addOnDeviceProfileChangeListener( @@ -781,6 +782,11 @@ public class BubbleBarViewController { /** Animates the bubble bar to notify the user about a bubble change. */ public void animateBubbleNotification(BubbleBarBubble bubble, boolean isExpanding, boolean isUpdate) { + // if we're expanded, don't animate the bubble bar. just show the notification dot. + if (isExpanded()) { + bubble.getView().updateDotVisibility(/* animate= */ true); + return; + } boolean isInApp = mTaskbarStashController.isInApp(); // if this is the first bubble, animate to the initial state. if (mBarView.getBubbleChildCount() == 1 && !isUpdate) { @@ -789,13 +795,12 @@ public class BubbleBarViewController { } boolean persistentTaskbarOrOnHome = mBubbleStashController.isBubblesShowingOnHome() || !mBubbleStashController.isTransientTaskBar(); - if (persistentTaskbarOrOnHome && !isExpanded()) { + if (persistentTaskbarOrOnHome) { mBubbleBarViewAnimator.animateBubbleBarForCollapsed(bubble, isExpanding); return; } - // only animate the new bubble if we're in an app, have handle view and not auto expanding - if (isInApp && mBubbleStashController.getHasHandleView() && !isExpanded()) { + if (isInApp && mBubbleStashController.getHasHandleView()) { mBubbleBarViewAnimator.animateBubbleInForStashed(bubble, isExpanding); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java index 707655c038..4f3e1ae87e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java @@ -301,7 +301,7 @@ public class BubbleView extends ConstraintLayout { void updateDotVisibility(boolean animate) { if (mDotSuppressedForBubbleUpdate) { - // if the dot is suppressed for + // if the dot is suppressed for an update, there's nothing to do return; } final float targetScale = hasUnseenContent() ? 1f : 0f; diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt index 6a955d92a0..8a52ca96a1 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt @@ -27,6 +27,8 @@ 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.BubbleView +import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutController +import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutMessage import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController import com.android.wm.shell.shared.animation.PhysicsAnimator @@ -36,8 +38,9 @@ class BubbleBarViewAnimator constructor( private val bubbleBarView: BubbleBarView, private val bubbleStashController: BubbleStashController, + private val bubbleBarFlyoutController: BubbleBarFlyoutController, private val onExpanded: Runnable, - private val scheduler: Scheduler = HandlerScheduler(bubbleBarView) + private val scheduler: Scheduler = HandlerScheduler(bubbleBarView), ) { private var animatingBubble: AnimatingBubble? = null @@ -54,7 +57,7 @@ constructor( private companion object { /** The time to show the flyout. */ - const val FLYOUT_DELAY_MS: Long = 2500 + const val FLYOUT_DELAY_MS: Long = 3000 /** The initial scale Y value that the new bubble is set to before the animation starts. */ const val BUBBLE_ANIMATION_INITIAL_SCALE_Y = 0.3f /** The minimum alpha value to make the bubble bar touchable. */ @@ -69,7 +72,7 @@ constructor( val showAnimation: Runnable, val hideAnimation: Runnable, val expand: Boolean, - val state: State = State.CREATED + val state: State = State.CREATED, ) { /** @@ -91,7 +94,7 @@ constructor( /** The bubble notification is now fully showing and waiting to be hidden. */ IN, /** The bubble notification is animating out. */ - ANIMATING_OUT + ANIMATING_OUT, } } @@ -127,7 +130,7 @@ constructor( private val springConfig = PhysicsAnimator.SpringConfig( stiffness = SpringForce.STIFFNESS_LOW, - dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY + dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY, ) /** Animates a bubble for the state where the bubble bar is stashed. */ @@ -137,8 +140,9 @@ constructor( val bubbleView = b.view val animator = PhysicsAnimator.getInstance(bubbleView) 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. + // the animation of a new bubble is divided into 2 parts. The first part transforms the + // handle to the bubble bar and then shows the flyout. The second part hides the flyout and + // transforms the bubble bar back to the handle. val showAnimation = buildHandleToBubbleBarAnimation() val hideAnimation = if (isExpanding) Runnable {} else buildBubbleBarToHandleAnimation() animatingBubble = @@ -243,7 +247,8 @@ constructor( cancelHideAnimation() return@addEndListener } - moveToState(AnimatingBubble.State.IN) + setupAndShowFlyout() + // the bubble bar is now fully settled in. update taskbar touch region so it's touchable bubbleStashController.updateTaskbarTouchRegion() } @@ -316,7 +321,17 @@ constructor( bubbleBarView.scaleY = 1f bubbleStashController.updateTaskbarTouchRegion() } - animator.start() + + val bubble = animatingBubble?.bubbleView?.bubble as? BubbleBarBubble + val flyout = bubble?.flyoutMessage + if (flyout != null) { + bubbleBarFlyoutController.collapseFlyout { + onFlyoutRemoved(bubble.view) + animator.start() + } + } else { + animator.start() + } } /** Animates to the initial state of the bubble bar, when there are no previous bubbles. */ @@ -326,16 +341,16 @@ constructor( val bubbleView = b.view val animator = PhysicsAnimator.getInstance(bubbleView) 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. + // the animation of a new bubble is divided into 2 parts. The first part slides in the + // bubble bar and shows the flyout. The second part hides the flyout and transforms the + // bubble bar to the handle if we're in an app. val showAnimation = buildBubbleBarSpringInAnimation() val hideAnimation = if (isInApp && !isExpanding) { buildBubbleBarToHandleAnimation() } else { - // in this case the bubble bar remains visible so not much to do. once we implement - // the flyout we'll update this runnable to hide it. Runnable { + bubbleBarFlyoutController.collapseFlyout { onFlyoutRemoved(bubbleView) } animatingBubble = null bubbleStashController.showBubbleBarImmediate() bubbleStashController.updateTaskbarTouchRegion() @@ -370,7 +385,7 @@ constructor( if (animatingBubble?.expand == true) { cancelHideAnimation() } else { - moveToState(AnimatingBubble.State.IN) + setupAndShowFlyout() } // the bubble bar is now fully settled in. update taskbar touch region so it's touchable bubbleStashController.updateTaskbarTouchRegion() @@ -384,8 +399,10 @@ constructor( val bubbleView = b.view val animator = PhysicsAnimator.getInstance(bubbleView) if (animator.isRunning()) animator.cancel() + // first bounce the bubble bar and show the flyout. Then hide the flyout. val showAnimation = buildBubbleBarBounceAnimation() val hideAnimation = Runnable { + bubbleBarFlyoutController.collapseFlyout { onFlyoutRemoved(bubbleView) } animatingBubble = null bubbleStashController.showBubbleBarImmediate() bubbleStashController.updateTaskbarTouchRegion() @@ -413,7 +430,7 @@ constructor( expandBubbleBar() cancelHideAnimation() } else { - moveToState(AnimatingBubble.State.IN) + setupAndShowFlyout() } } @@ -427,10 +444,38 @@ constructor( .start() } + private fun setupAndShowFlyout() { + val bubbleView = animatingBubble?.bubbleView + val bubble = bubbleView?.bubble as? BubbleBarBubble + val flyout = bubble?.flyoutMessage + if (flyout != null) { + bubbleView.suppressDotForBubbleUpdate(true) + bubbleBarFlyoutController.setUpAndShowFlyout( + BubbleBarFlyoutMessage(flyout.icon, flyout.title, flyout.message) + ) { + moveToState(AnimatingBubble.State.IN) + bubbleStashController.updateTaskbarTouchRegion() + } + } else { + moveToState(AnimatingBubble.State.IN) + } + } + + private fun cancelFlyout() { + val bubbleView = animatingBubble?.bubbleView + bubbleBarFlyoutController.cancelFlyout { onFlyoutRemoved(bubbleView) } + } + + private fun onFlyoutRemoved(bubbleView: BubbleView?) { + bubbleView?.suppressDotForBubbleUpdate(false) + bubbleStashController.updateTaskbarTouchRegion() + } + /** Handles touching the animating bubble bar. */ fun onBubbleBarTouchedWhileAnimating() { PhysicsAnimator.getInstance(bubbleBarView).cancelIfRunning() bubbleStashController.getStashedHandlePhysicsAnimator().cancelIfRunning() + cancelFlyout() val hideAnimation = animatingBubble?.hideAnimation ?: return scheduler.cancel(hideAnimation) bubbleBarView.relativePivotY = 1f @@ -439,6 +484,7 @@ constructor( /** Notifies the animator that the taskbar area was touched during an animation. */ fun onStashStateChangingWhileAnimating() { + cancelFlyout() val hideAnimation = animatingBubble?.hideAnimation ?: return scheduler.cancel(hideAnimation) animatingBubble = null @@ -446,7 +492,7 @@ constructor( bubbleBarView.relativePivotY = 1f bubbleStashController.onNewBubbleAnimationInterrupted( /* isStashed= */ bubbleBarView.alpha == 0f, - bubbleBarView.translationY + bubbleBarView.translationY, ) } @@ -455,6 +501,7 @@ constructor( this.animatingBubble = animatingBubble.copy(expand = true) // if we're fully in and waiting to hide, cancel the hide animation and clean up if (animatingBubble.state == AnimatingBubble.State.IN) { + cancelFlyout() expandBubbleBar() cancelHideAnimation() } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt index c431deb27c..d6400bbaa1 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt @@ -21,8 +21,8 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.core.animation.ValueAnimator import com.android.launcher3.R +import com.android.systemui.util.addListener import com.android.systemui.util.doOnEnd -import com.android.systemui.util.doOnStart /** Creates and manages the visibility of the [BubbleBarFlyoutView]. */ class BubbleBarFlyoutController @@ -35,14 +35,19 @@ constructor( ) { private companion object { - const val EXPAND_COLLAPSE_ANIMATION_DURATION_MS = 250L + const val ANIMATION_DURATION_MS = 250L } private var flyout: BubbleBarFlyoutView? = null private val horizontalMargin = container.context.resources.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin) - fun setUpFlyout(message: BubbleBarFlyoutMessage) { + private enum class AnimationType { + COLLAPSE, + FADE, + } + + fun setUpAndShowFlyout(message: BubbleBarFlyoutMessage, onEnd: () -> Unit) { flyout?.let(container::removeView) val flyout = BubbleBarFlyoutView(container.context, positioner, flyoutScheduler) @@ -58,27 +63,42 @@ constructor( lp.marginEnd = horizontalMargin container.addView(flyout, lp) - val animator = - ValueAnimator.ofFloat(0f, 1f).setDuration(EXPAND_COLLAPSE_ANIMATION_DURATION_MS) + val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIMATION_DURATION_MS) animator.addUpdateListener { _ -> flyout.updateExpansionProgress(animator.animatedValue as Float) } - animator.doOnStart { - val flyoutTop = flyout.top + flyout.translationY - // If the top position of the flyout is negative, then it's bleeding over the - // top boundary of its parent view - if (flyoutTop < 0) topBoundaryListener.extendTopBoundary(space = -flyoutTop.toInt()) - } + animator.addListener( + onStart = { + val flyoutTop = flyout.top + flyout.translationY + // If the top position of the flyout is negative, then it's bleeding over the + // top boundary of its parent view + if (flyoutTop < 0) topBoundaryListener.extendTopBoundary(space = -flyoutTop.toInt()) + }, + onEnd = { onEnd() }, + ) flyout.showFromCollapsed(message) { animator.start() } this.flyout = flyout } - fun hideFlyout(endAction: () -> Unit) { + fun cancelFlyout(endAction: () -> Unit) { + hideFlyout(AnimationType.FADE, endAction) + } + + fun collapseFlyout(endAction: () -> Unit) { + hideFlyout(AnimationType.COLLAPSE, endAction) + } + + private fun hideFlyout(animationType: AnimationType, endAction: () -> Unit) { + // TODO: b/277815200 - stop the current animation if it's running val flyout = this.flyout ?: return - val animator = - ValueAnimator.ofFloat(1f, 0f).setDuration(EXPAND_COLLAPSE_ANIMATION_DURATION_MS) - animator.addUpdateListener { _ -> - flyout.updateExpansionProgress(animator.animatedValue as Float) + val animator = ValueAnimator.ofFloat(1f, 0f).setDuration(ANIMATION_DURATION_MS) + when (animationType) { + AnimationType.FADE -> + animator.addUpdateListener { _ -> flyout.alpha = animator.animatedValue as Float } + AnimationType.COLLAPSE -> + animator.addUpdateListener { _ -> + flyout.updateExpansionProgress(animator.animatedValue as Float) + } } animator.doOnEnd { container.removeView(flyout) diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt index c60fba2e21..6903c87aab 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt @@ -140,6 +140,7 @@ class BubbleBarFlyoutView( init { LayoutInflater.from(context).inflate(R.layout.bubblebar_flyout, this, true) + id = R.id.bubble_bar_flyout_view val ta = context.obtainStyledAttributes(intArrayOf(android.R.attr.dialogCornerRadius)) cornerRadius = ta.getDimensionPixelSize(0, 0).toFloat() diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt index 7eee4de9ba..b37048afc0 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt @@ -19,6 +19,7 @@ package com.android.launcher3.taskbar.bubbles.animation import android.content.Context import android.graphics.Color import android.graphics.Path +import android.graphics.PointF import android.graphics.drawable.ColorDrawable import android.view.LayoutInflater import android.view.View @@ -36,6 +37,10 @@ import com.android.launcher3.taskbar.bubbles.BubbleBarBubble import com.android.launcher3.taskbar.bubbles.BubbleBarOverflow import com.android.launcher3.taskbar.bubbles.BubbleBarView import com.android.launcher3.taskbar.bubbles.BubbleView +import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutController +import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutMessage +import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutPositioner +import com.android.launcher3.taskbar.bubbles.flyout.FlyoutScheduler import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController import com.android.wm.shell.shared.animation.PhysicsAnimator import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils @@ -63,13 +68,19 @@ class BubbleBarViewAnimatorTest { private lateinit var bubbleView: BubbleView private lateinit var bubble: BubbleBarBubble private lateinit var bubbleBarView: BubbleBarView + private lateinit var flyoutContainer: FrameLayout private lateinit var bubbleStashController: BubbleStashController + private lateinit var flyoutController: BubbleBarFlyoutController private val onExpandedNoOp = Runnable {} + private val flyoutView: View? + get() = flyoutContainer.findViewById(R.id.bubble_bar_flyout_view) + @Before fun setUp() { animatorScheduler = TestBubbleBarViewAnimatorScheduler() PhysicsAnimatorTestUtils.prepareForTest() + setupFlyoutController() } @Test @@ -85,6 +96,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpandedNoOp, animatorScheduler, ) @@ -106,10 +118,14 @@ class BubbleBarViewAnimatorTest { assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR) assertThat(animator.isAnimating).isTrue() + waitForFlyoutToShow() + // execute the hide bubble animation assertThat(animatorScheduler.delayedBlock).isNotNull() InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + waitForFlyoutToHide() + // let the animation start and wait for it to complete InstrumentationRegistry.getInstrumentation().runOnMainSync {} PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) @@ -134,6 +150,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpandedNoOp, animatorScheduler, ) @@ -157,10 +174,16 @@ class BubbleBarViewAnimatorTest { verify(bubbleStashController, atLeastOnce()).updateTaskbarTouchRegion() + waitForFlyoutToShow() + // verify the hide bubble animation is pending assertThat(animatorScheduler.delayedBlock).isNotNull() - animator.onBubbleBarTouchedWhileAnimating() + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.onBubbleBarTouchedWhileAnimating() + } + + waitForFlyoutToHide() assertThat(animatorScheduler.delayedBlock).isNull() assertThat(bubbleBarView.alpha).isEqualTo(1) @@ -182,6 +205,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpandedNoOp, animatorScheduler, ) @@ -227,6 +251,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpandedNoOp, animatorScheduler, ) @@ -239,10 +264,14 @@ class BubbleBarViewAnimatorTest { InstrumentationRegistry.getInstrumentation().runOnMainSync {} PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + waitForFlyoutToShow() + // execute the hide bubble animation assertThat(animatorScheduler.delayedBlock).isNotNull() InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + waitForFlyoutToHide() + // wait for the hide animation to start InstrumentationRegistry.getInstrumentation().runOnMainSync {} handleAnimator.assertIsRunning() @@ -273,6 +302,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpandedNoOp, animatorScheduler, ) @@ -310,6 +340,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpanded, animatorScheduler, ) @@ -354,6 +385,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpanded, animatorScheduler, ) @@ -404,6 +436,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpanded, animatorScheduler, ) @@ -418,6 +451,9 @@ class BubbleBarViewAnimatorTest { PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) assertThat(animator.isAnimating).isTrue() + + waitForFlyoutToShow() + // verify the hide bubble animation is pending assertThat(animatorScheduler.delayedBlock).isNotNull() @@ -428,6 +464,8 @@ class BubbleBarViewAnimatorTest { // verify that the hide animation was canceled assertThat(animatorScheduler.delayedBlock).isNull() + waitForFlyoutToHide() + assertThat(handle.alpha).isEqualTo(0) assertThat(handle.translationY) .isEqualTo(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS + BAR_TRANSLATION_Y_FOR_TASKBAR) @@ -453,6 +491,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpandedNoOp, animatorScheduler, ) @@ -469,9 +508,13 @@ class BubbleBarViewAnimatorTest { assertThat(bubbleBarView.alpha).isEqualTo(1) assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR) + waitForFlyoutToShow() + assertThat(animatorScheduler.delayedBlock).isNotNull() InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + waitForFlyoutToHide() + InstrumentationRegistry.getInstrumentation().runOnMainSync {} PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) @@ -503,6 +546,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpanded, animatorScheduler, ) @@ -537,6 +581,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpandedNoOp, animatorScheduler, ) @@ -553,9 +598,13 @@ class BubbleBarViewAnimatorTest { assertThat(bubbleBarView.alpha).isEqualTo(1) assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT) + waitForFlyoutToShow() + assertThat(animatorScheduler.delayedBlock).isNotNull() InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + waitForFlyoutToHide() + assertThat(animator.isAnimating).isFalse() assertThat(bubbleBarView.alpha).isEqualTo(1) assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT) @@ -576,6 +625,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpanded, animatorScheduler, ) @@ -624,6 +674,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpanded, animatorScheduler, ) @@ -636,6 +687,8 @@ class BubbleBarViewAnimatorTest { InstrumentationRegistry.getInstrumentation().runOnMainSync {} PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + waitForFlyoutToShow() + assertThat(animator.isAnimating).isTrue() // verify the hide bubble animation is pending assertThat(animatorScheduler.delayedBlock).isNotNull() @@ -644,6 +697,8 @@ class BubbleBarViewAnimatorTest { animator.expandedWhileAnimating() } + waitForFlyoutToHide() + // verify that the hide animation was canceled assertThat(animatorScheduler.delayedBlock).isNull() @@ -665,6 +720,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpandedNoOp, animatorScheduler, ) @@ -687,9 +743,13 @@ class BubbleBarViewAnimatorTest { barAnimator.assertIsRunning() PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + waitForFlyoutToShow() + assertThat(animatorScheduler.delayedBlock).isNotNull() InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + waitForFlyoutToHide() + assertThat(animator.isAnimating).isFalse() // the bubble bar translation y should be back to its initial value assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT) @@ -712,6 +772,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpanded, animatorScheduler, ) @@ -759,6 +820,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpanded, animatorScheduler, ) @@ -817,6 +879,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator( bubbleBarView, bubbleStashController, + flyoutController, onExpanded, animatorScheduler, ) @@ -843,6 +906,8 @@ class BubbleBarViewAnimatorTest { assertThat(animatorScheduler.delayedBlock).isNotNull() assertThat(animator.isAnimating).isTrue() + waitForFlyoutToShow() + InstrumentationRegistry.getInstrumentation().runOnMainSync { animator.expandedWhileAnimating() } @@ -850,6 +915,8 @@ class BubbleBarViewAnimatorTest { // verify that the hide animation was canceled assertThat(animatorScheduler.delayedBlock).isNull() + waitForFlyoutToHide() + assertThat(animator.isAnimating).isFalse() assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT) assertThat(bubbleBarView.isExpanded).isTrue() @@ -894,7 +961,7 @@ class BubbleBarViewAnimatorTest { Color.WHITE, Path(), "", - null, + BubbleBarFlyoutMessage(icon = null, title = "title", message = "message"), ) bubbleView.setBubble(bubble) bubbleBarView.addView(bubbleView) @@ -913,6 +980,34 @@ class BubbleBarViewAnimatorTest { .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR) } + private fun setupFlyoutController() { + flyoutContainer = FrameLayout(context) + val flyoutPositioner = + object : BubbleBarFlyoutPositioner { + override val isOnLeft = true + override val targetTy = 100f + override val distanceToCollapsedPosition = PointF(0f, 0f) + override val collapsedSize = 30f + override val collapsedColor = Color.BLUE + override val collapsedElevation = 1f + override val distanceToRevealTriangle = 10f + } + val topBoundaryListener = + object : BubbleBarFlyoutController.TopBoundaryListener { + override fun extendTopBoundary(space: Int) {} + + override fun resetTopBoundary() {} + } + val flyoutScheduler = FlyoutScheduler { block -> block.invoke() } + flyoutController = + BubbleBarFlyoutController( + flyoutContainer, + flyoutPositioner, + topBoundaryListener, + flyoutScheduler, + ) + } + private fun verifyBubbleBarIsExpandedWithTranslation(ty: Float) { assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE) assertThat(bubbleBarView.scaleX).isEqualTo(1) @@ -921,6 +1016,20 @@ class BubbleBarViewAnimatorTest { assertThat(bubbleBarView.isExpanded).isTrue() } + private fun waitForFlyoutToShow() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(300) + } + assertThat(flyoutView).isNotNull() + } + + private fun waitForFlyoutToHide() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(300) + } + assertThat(flyoutView).isNull() + } + private fun PhysicsAnimator.assertIsRunning() { InstrumentationRegistry.getInstrumentation().runOnMainSync { assertThat(isRunning()).isTrue() diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt index 3dd7689a4e..527bdaa56e 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt @@ -20,6 +20,7 @@ import android.content.Context import android.graphics.Color import android.graphics.PointF import android.view.Gravity +import android.view.View import android.widget.FrameLayout import android.widget.TextView import androidx.core.animation.AnimatorTestRule @@ -80,7 +81,7 @@ class BubbleBarFlyoutControllerTest { @Test fun flyoutPosition_left() { InstrumentationRegistry.getInstrumentation().runOnMainSync { - flyoutController.setUpFlyout(flyoutMessage) + flyoutController.setUpAndShowFlyout(flyoutMessage) {} assertThat(flyoutContainer.childCount).isEqualTo(1) val flyout = flyoutContainer.getChildAt(0) val lp = flyout.layoutParams as FrameLayout.LayoutParams @@ -93,7 +94,7 @@ class BubbleBarFlyoutControllerTest { fun flyoutPosition_right() { onLeft = false InstrumentationRegistry.getInstrumentation().runOnMainSync { - flyoutController.setUpFlyout(flyoutMessage) + flyoutController.setUpAndShowFlyout(flyoutMessage) {} assertThat(flyoutContainer.childCount).isEqualTo(1) val flyout = flyoutContainer.getChildAt(0) val lp = flyout.layoutParams as FrameLayout.LayoutParams @@ -105,7 +106,7 @@ class BubbleBarFlyoutControllerTest { @Test fun flyoutMessage() { InstrumentationRegistry.getInstrumentation().runOnMainSync { - flyoutController.setUpFlyout(flyoutMessage) + flyoutController.setUpAndShowFlyout(flyoutMessage) {} assertThat(flyoutContainer.childCount).isEqualTo(1) val flyout = flyoutContainer.getChildAt(0) val sender = flyout.findViewById(R.id.bubble_flyout_title) @@ -118,9 +119,9 @@ class BubbleBarFlyoutControllerTest { @Test fun hideFlyout_removedFromContainer() { InstrumentationRegistry.getInstrumentation().runOnMainSync { - flyoutController.setUpFlyout(flyoutMessage) + flyoutController.setUpAndShowFlyout(flyoutMessage) {} assertThat(flyoutContainer.childCount).isEqualTo(1) - flyoutController.hideFlyout {} + flyoutController.collapseFlyout {} animatorTestRule.advanceTimeBy(300) } assertThat(flyoutContainer.childCount).isEqualTo(0) @@ -132,7 +133,7 @@ class BubbleBarFlyoutControllerTest { // boundary flyoutTy = -50f InstrumentationRegistry.getInstrumentation().runOnMainSync { - flyoutController.setUpFlyout(flyoutMessage) + flyoutController.setUpAndShowFlyout(flyoutMessage) {} assertThat(flyoutContainer.childCount).isEqualTo(1) } InstrumentationRegistry.getInstrumentation().waitForIdleSync() @@ -145,7 +146,7 @@ class BubbleBarFlyoutControllerTest { @Test fun showFlyout_withinBoundary() { InstrumentationRegistry.getInstrumentation().runOnMainSync { - flyoutController.setUpFlyout(flyoutMessage) + flyoutController.setUpAndShowFlyout(flyoutMessage) {} assertThat(flyoutContainer.childCount).isEqualTo(1) } InstrumentationRegistry.getInstrumentation().waitForIdleSync() @@ -156,16 +157,30 @@ class BubbleBarFlyoutControllerTest { } @Test - fun hideFlyout_resetsTopBoundary() { + fun collapseFlyout_resetsTopBoundary() { InstrumentationRegistry.getInstrumentation().runOnMainSync { - flyoutController.setUpFlyout(flyoutMessage) + flyoutController.setUpAndShowFlyout(flyoutMessage) {} assertThat(flyoutContainer.childCount).isEqualTo(1) - flyoutController.hideFlyout {} + flyoutController.collapseFlyout {} animatorTestRule.advanceTimeBy(300) } assertThat(topBoundaryListener.topBoundaryReset).isTrue() } + @Test + fun cancelFlyout_fadesOutFlyout() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + flyoutController.setUpAndShowFlyout(flyoutMessage) {} + assertThat(flyoutContainer.childCount).isEqualTo(1) + val flyoutView = flyoutContainer.findViewById(R.id.bubble_bar_flyout_view) + assertThat(flyoutView.alpha).isEqualTo(1f) + flyoutController.cancelFlyout {} + animatorTestRule.advanceTimeBy(300) + assertThat(flyoutView.alpha).isEqualTo(0f) + } + assertThat(topBoundaryListener.topBoundaryReset).isTrue() + } + class FakeTopBoundaryListener : BubbleBarFlyoutController.TopBoundaryListener { var topBoundaryExtendedSpace = 0