Animate out bubbles in reverse of the way they animate in.
Also, fixes a bug where the last bubble doesn't animate out because the stack view resizes before the animation completes.
Test: atest SystemUITests
Fixes: 134514227
Change-Id: Ic36b79d96430307e58214bc968e0d4a16e7efa77
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 7d9bb07..494c1a2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -802,16 +802,32 @@
if (mStackView == null) {
return;
}
+
if (mStatusBarStateListener.getCurrentState() == SHADE && hasBubbles()) {
// Bubbles only appear in unlocked shade
mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE);
- } else if (mStackView != null) {
+ } else if (!hasBubbles() && mStackView.hasTransientBubbles()) {
+ // If we only have transient bubbles, then wait until they're gone.
+ mStackView.runActionAfterTransientViewAnimations(() -> {
+ mStackView.setVisibility(INVISIBLE);
+ updateBubblesShowing();
+ });
+ } else {
mStackView.setVisibility(INVISIBLE);
}
- // Let listeners know if bubble state changed.
+ updateBubblesShowing();
+ }
+
+ /**
+ * Updates the status bar window controller and the state change listener with whether bubbles
+ * are currently showing.
+ */
+ public void updateBubblesShowing() {
boolean hadBubbles = mStatusBarWindowController.getBubblesShowing();
- boolean hasBubblesShowing = hasBubbles() && mStackView.getVisibility() == VISIBLE;
+ boolean hasBubblesShowing =
+ (hasBubbles() || mStackView.hasTransientBubbles())
+ && mStackView.getVisibility() == VISIBLE;
mStatusBarWindowController.setBubblesShowing(hasBubblesShowing);
if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) {
mStateChangeListener.onHasBubblesChanged(hasBubblesShowing);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 7379693..ecf3191 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -657,6 +657,10 @@
return mIsExpanded;
}
+ void runActionAfterTransientViewAnimations(Runnable after) {
+ mStackAnimationController.runActionAfterAllViewsAndTransientRemoved(after);
+ }
+
/**
* The {@link BubbleView} that is expanded, null if one does not exist.
*/
@@ -687,6 +691,10 @@
}
}
+ boolean hasTransientBubbles() {
+ return mBubbleContainer.getTransientViewCount() > 0;
+ }
+
// via BubbleData.Listener
void addBubble(Bubble bubble) {
if (DEBUG_BUBBLE_STACK_VIEW) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index 94d4f59..6ec1e46 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -144,6 +144,11 @@
*/
private boolean mFirstBubbleSpringingToTouch = false;
+ /**
+ * Action to run after all views, including transient views, have been removed from the layout.
+ */
+ @Nullable private Runnable mAfterAllViewsAndTransientRemoved = null;
+
/** Horizontal offset of bubbles in the stack. */
private float mStackOffset;
/** Diameter of the bubble icon. */
@@ -561,6 +566,14 @@
}
/**
+ * Sets an action to run after all views, including transient views, have been removed from the
+ * layout.
+ */
+ public void runActionAfterAllViewsAndTransientRemoved(Runnable action) {
+ mAfterAllViewsAndTransientRemoved = action;
+ }
+
+ /**
* Springs the first bubble to the given final position, with the rest of the stack 'following'.
*/
protected void springFirstBubbleWithStackFollowing(
@@ -647,13 +660,26 @@
@Override
void onChildRemoved(View child, int index, Runnable finishRemoval) {
- // Animate the removing view in the opposite direction of the stack.
- final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
animationForChild(child)
- .alpha(0f, finishRemoval /* after */)
- .scaleX(ANIMATE_IN_STARTING_SCALE)
- .scaleY(ANIMATE_IN_STARTING_SCALE)
- .translationX(mStackPosition.x - (-xOffset * ANIMATE_TRANSLATION_FACTOR))
+ .alpha(0f,
+ finishRemoval /* after */,
+ () -> {
+ // If this was the last transient view, run the callback.
+ if (mLayout.getTransientViewCount() == 0
+ && mAfterAllViewsAndTransientRemoved != null) {
+
+ // If a 'real' view was added while we were animating out, don't run
+ // the callback since all views haven't been removed.
+ if (mLayout.getChildCount() == 0) {
+ mAfterAllViewsAndTransientRemoved.run();
+ }
+
+ mAfterAllViewsAndTransientRemoved = null;
+ }
+ } /* after */)
+ .scaleX(0f)
+ .scaleY(0f)
+ .withStiffness(ANIMATE_IN_STIFFNESS)
.start();
if (mLayout.getChildCount() > 0) {