diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java index 833be61315..3c9fb61069 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java @@ -81,6 +81,7 @@ public class BubbleBarView extends FrameLayout { public static final long FADE_OUT_ANIM_POSITION_DURATION_MS = 100L; public static final long FADE_IN_ANIM_ALPHA_DURATION_MS = 100L; + public static final long FADE_OUT_BUBBLE_BAR_DURATION_MS = 150L; private static final String TAG = "BubbleBarView"; // TODO: (b/273594744) calculate the amount of space we have and base the max on that // if it's smaller than 5. @@ -146,6 +147,9 @@ public class BubbleBarView extends FrameLayout { // collapsed state and 1 to the fully expanded state. private ValueAnimator mWidthAnimator = createExpansionAnimator(/* expanding = */ false); + @Nullable + private ValueAnimator mDismissAnimator = null; + /** An animator used for animating individual bubbles in the bubble bar while expanded. */ @Nullable private BubbleAnimator mBubbleAnimator = null; @@ -799,11 +803,11 @@ public class BubbleBarView extends FrameLayout { /** Removes the given bubble from the bubble bar. */ public void removeBubble(View bubble) { if (isExpanded()) { - // TODO b/347062801 - animate the bubble bar if the last bubble is removed final boolean dismissedByDrag = mDraggedBubbleView == bubble; if (dismissedByDrag) { mDismissedByDragBubbleView = mDraggedBubbleView; } + boolean removingLastRemainingBubble = getBubbleChildCount() == 1; int bubbleCount = getChildCount(); mBubbleAnimator = new BubbleAnimator(mIconSize, mExpandedBarIconsSpacing, bubbleCount, mBubbleBarLocation.isOnLeft(isLayoutRtl())); @@ -841,7 +845,10 @@ public class BubbleBarView extends FrameLayout { : bubbleIndex == bubbleCount - 1; mBubbleAnimator.animateRemovedBubble( indexOfChild(bubble), indexOfChild(mSelectedBubbleView), removingLastBubble, - listener); + removingLastRemainingBubble, listener); + if (removingLastRemainingBubble && mDismissAnimator == null) { + createDismissAnimator().start(); + } } else { removeView(bubble); } @@ -862,6 +869,28 @@ public class BubbleBarView extends FrameLayout { updateDotsAndBadgesIfCollapsed(); } + private ValueAnimator createDismissAnimator() { + ValueAnimator animator = + ValueAnimator.ofFloat(0, 1).setDuration(FADE_OUT_BUBBLE_BAR_DURATION_MS); + animator.setInterpolator(Interpolators.EMPHASIZED); + Runnable onEnd = () -> { + mDismissAnimator = null; + setAlpha(0); + }; + addAnimationCallBacks(animator, /* onStart= */ null, onEnd, + /* onUpdate= */ anim -> setAlpha(1 - anim.getAnimatedFraction())); + mDismissAnimator = animator; + return animator; + } + + /** Dismisses the bubble bar */ + public void dismiss(Runnable onDismissed) { + if (mDismissAnimator == null) { + createDismissAnimator().start(); + } + addAnimationCallBacks(mDismissAnimator, null, onDismissed, null); + } + /** * Return child views in the order which they are shown on the screen. *

diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java index fd08078109..ce772a5ecf 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java @@ -586,13 +586,17 @@ public class BubbleBarViewController { public void setHiddenForBubbles(boolean hidden) { if (mHiddenForNoBubbles != hidden) { mHiddenForNoBubbles = hidden; - updateVisibilityForStateChange(); if (hidden) { - mBarView.setAlpha(0); - mBarView.setExpanded(false); - adjustTaskbarAndHotseatToBubbleBarState(/* isBubbleBarExpanded = */ false); + mBarView.dismiss(() -> { + updateVisibilityForStateChange(); + mBarView.setExpanded(false); + adjustTaskbarAndHotseatToBubbleBarState(/* isBubbleBarExpanded= */ false); + mActivity.bubbleBarVisibilityChanged(/* isVisible= */ false); + }); + } else { + updateVisibilityForStateChange(); + mActivity.bubbleBarVisibilityChanged(/* isVisible= */ true); } - mActivity.bubbleBarVisibilityChanged(!hidden); } } @@ -625,7 +629,6 @@ public class BubbleBarViewController { } } - // TODO: (b/273592694) animate it private void updateVisibilityForStateChange() { if (!mHiddenForSysui && !mHiddenForNoBubbles && !mHiddenForStashed) { mBarView.setVisibility(VISIBLE); diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt index 8af8ffb22a..360416723c 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt @@ -49,23 +49,30 @@ class BubbleAnimator( bubbleIndex: Int, selectedBubbleIndex: Int, removingLastBubble: Boolean, - listener: Listener + removingLastRemainingBubble: Boolean, + listener: Listener, ) { animator = createAnimator(listener) - state = State.RemovingBubble(bubbleIndex, selectedBubbleIndex, removingLastBubble) + state = + State.RemovingBubble( + bubbleIndex = bubbleIndex, + selectedBubbleIndex = selectedBubbleIndex, + removingLastBubble = removingLastBubble, + removingLastRemainingBubble = removingLastRemainingBubble, + ) animator.start() } fun animateNewAndRemoveOld( selectedBubbleIndex: Int, removedBubbleIndex: Int, - listener: Listener + listener: Listener, ) { animator = createAnimator(listener) state = State.AddingAndRemoving( selectedBubbleIndex = selectedBubbleIndex, - removedBubbleIndex = removedBubbleIndex + removedBubbleIndex = removedBubbleIndex, ) animator.start() } @@ -111,20 +118,22 @@ class BubbleAnimator( getBubbleTranslationXWhileScalingBubble( bubbleIndex = bubbleIndex, scalingBubbleIndex = 0, - bubbleScale = animator.animatedFraction + bubbleScale = animator.animatedFraction, ) + is State.RemovingBubble -> getBubbleTranslationXWhileScalingBubble( bubbleIndex = bubbleIndex, scalingBubbleIndex = state.bubbleIndex, - bubbleScale = 1 - animator.animatedFraction + bubbleScale = 1 - animator.animatedFraction, ) + is State.AddingAndRemoving -> getBubbleTranslationXWhileAddingBubbleAtLimit( bubbleIndex = bubbleIndex, removedBubbleIndex = state.removedBubbleIndex, addedBubbleScale = animator.animatedFraction, - removedBubbleScale = 1 - animator.animatedFraction + removedBubbleScale = 1 - animator.animatedFraction, ) } } @@ -176,10 +185,11 @@ class BubbleAnimator( getBubbleTranslationXWhileScalingBubble( bubbleIndex = state.selectedBubbleIndex, scalingBubbleIndex = 0, - bubbleScale = animator.animatedFraction + bubbleScale = animator.animatedFraction, ) tx + iconSize / 2f } + is State.RemovingBubble -> getArrowPositionWhenRemovingBubble(state) is State.AddingAndRemoving -> { // we never remove the selected bubble, so the arrow stays pointing to its center @@ -188,22 +198,23 @@ class BubbleAnimator( bubbleIndex = state.selectedBubbleIndex, removedBubbleIndex = state.removedBubbleIndex, addedBubbleScale = animator.animatedFraction, - removedBubbleScale = 1 - animator.animatedFraction + removedBubbleScale = 1 - animator.animatedFraction, ) tx + iconSize / 2f } } } - private fun getArrowPositionWhenRemovingBubble(state: State.RemovingBubble): Float { - return if (state.selectedBubbleIndex != state.bubbleIndex) { - // if we're not removing the selected bubble, the selected bubble doesn't change so just - // return the translation X of the selected bubble and add half icon + private fun getArrowPositionWhenRemovingBubble(state: State.RemovingBubble): Float = + if (state.selectedBubbleIndex != state.bubbleIndex || state.removingLastRemainingBubble) { + // if we're not removing the selected bubble or if we're removing the last remaining + // bubble, the selected bubble doesn't change so just return the translation X of the + // selected bubble and add half icon val tx = getBubbleTranslationXWhileScalingBubble( bubbleIndex = state.selectedBubbleIndex, scalingBubbleIndex = state.bubbleIndex, - bubbleScale = 1 - animator.animatedFraction + bubbleScale = 1 - animator.animatedFraction, ) tx + iconSize / 2f } else { @@ -238,7 +249,6 @@ class BubbleAnimator( } } } - } /** * Returns the translation X for the bubble at index {@code bubbleIndex} when the bubble bar is @@ -251,7 +261,7 @@ class BubbleAnimator( private fun getBubbleTranslationXWhileScalingBubble( bubbleIndex: Int, scalingBubbleIndex: Int, - bubbleScale: Float + bubbleScale: Float, ): Float { val iconAndSpacing = iconSize + expandedBarIconSpacing // the bubble is scaling from the center, so we need to adjust its translation so @@ -300,7 +310,7 @@ class BubbleAnimator( bubbleIndex: Int, removedBubbleIndex: Int, addedBubbleScale: Float, - removedBubbleScale: Float + removedBubbleScale: Float, ): Float { val iconAndSpacing = iconSize + expandedBarIconSpacing // the bubbles are scaling from the center, so we need to adjust their translation so @@ -377,7 +387,9 @@ class BubbleAnimator( /** The index of the selected bubble. */ val selectedBubbleIndex: Int, /** Whether the bubble being removed is also the last bubble. */ - val removingLastBubble: Boolean + val removingLastBubble: Boolean, + /** Whether we're removing the last remaining bubble. */ + val removingLastRemainingBubble: Boolean, ) : State /** A new bubble is being added and an old bubble is being removed from the bubble bar. */ diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt index cc8582c957..d7bb190b5c 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt @@ -16,6 +16,7 @@ package com.android.launcher3.taskbar +import android.animation.AnimatorTestRule import android.content.ComponentName import android.content.Intent import android.platform.test.annotations.EnableFlags @@ -75,7 +76,9 @@ class TaskbarOverflowTest { @get:Rule(order = 3) val taskbarModeRule = TaskbarModeRule(context) - @get:Rule(order = 4) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context) + @get:Rule(order = 4) val animatorTestRule = AnimatorTestRule(this) + + @get:Rule(order = 5) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context) @InjectController lateinit var taskbarViewController: TaskbarViewController @InjectController lateinit var recentAppsController: TaskbarRecentAppsController @@ -208,7 +211,10 @@ class TaskbarOverflowTest { val initialNumIcons = currentNumberOfTaskbarIcons val initialMaxNumIconViews = addRunningAppsAndVerifyOverflowState(5) - runOnMainSync { bubbleBarViewController.setHiddenForBubbles(true) } + runOnMainSync { + bubbleBarViewController.setHiddenForBubbles(true) + animatorTestRule.advanceTimeBy(150) + } val maxNumIconViews = maxNumberOfTaskbarIcons assertThat(maxNumIconViews).isGreaterThan(initialMaxNumIconViews) @@ -226,7 +232,10 @@ class TaskbarOverflowTest { val initialNumIcons = currentNumberOfTaskbarIcons val initialMaxNumIconViews = addRunningAppsAndVerifyOverflowState(5) - runOnMainSync { bubbleBarViewController.setHiddenForBubbles(true) } + runOnMainSync { + bubbleBarViewController.setHiddenForBubbles(true) + animatorTestRule.advanceTimeBy(150) + } val maxNumIconViews = maxNumberOfTaskbarIcons assertThat(maxNumIconViews).isGreaterThan(initialMaxNumIconViews) diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt index d5a76a2f4c..eae181f1b0 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt @@ -40,7 +40,7 @@ class BubbleAnimatorTest { iconSize = 40f, expandedBarIconSpacing = 10f, bubbleCount = 5, - onLeft = false + onLeft = false, ) val listener = TestBubbleAnimatorListener() InstrumentationRegistry.getInstrumentation().runOnMainSync { @@ -61,7 +61,7 @@ class BubbleAnimatorTest { iconSize = 40f, expandedBarIconSpacing = 10f, bubbleCount = 5, - onLeft = false + onLeft = false, ) val listener = TestBubbleAnimatorListener() InstrumentationRegistry.getInstrumentation().runOnMainSync { @@ -69,7 +69,8 @@ class BubbleAnimatorTest { bubbleIndex = 2, selectedBubbleIndex = 3, removingLastBubble = false, - listener + removingLastRemainingBubble = false, + listener, ) } @@ -87,14 +88,14 @@ class BubbleAnimatorTest { iconSize = 40f, expandedBarIconSpacing = 10f, bubbleCount = 5, - onLeft = false + onLeft = false, ) val listener = TestBubbleAnimatorListener() InstrumentationRegistry.getInstrumentation().runOnMainSync { bubbleAnimator.animateNewAndRemoveOld( selectedBubbleIndex = 3, removedBubbleIndex = 2, - listener + listener, ) }