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