Merge "Remove Bubbles from the WM, only after all bubbles finish animating out." into rvc-dev
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index a578f33..2587369 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -185,9 +185,10 @@
// Used to post to main UI thread
private Handler mHandler = new Handler();
- /** LayoutParams used to add the BubbleStackView to the window maanger. */
+ /** LayoutParams used to add the BubbleStackView to the window manager. */
private WindowManager.LayoutParams mWmLayoutParams;
-
+ /** Whether or not the BubbleStackView has been added to the WindowManager. */
+ private boolean mAddedToWindowManager = false;
// Used for determining view rect for touch interaction
private Rect mTempRect = new Rect();
@@ -595,9 +596,8 @@
if (mStackView == null) {
mStackView = new BubbleStackView(
mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
- mSysUiState, mNotificationShadeWindowController);
+ mSysUiState, mNotificationShadeWindowController, this::onAllBubblesAnimatedOut);
mStackView.addView(mBubbleScrim);
- addToWindowManager();
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
}
@@ -605,10 +605,17 @@
mStackView.setUnbubbleConversationCallback(notificationEntry ->
onUserChangedBubble(notificationEntry, false /* shouldBubble */));
}
+
+ addToWindowManagerMaybe();
}
- /** Adds the BubbleStackView to the WindowManager. */
- private void addToWindowManager() {
+ /** Adds the BubbleStackView to the WindowManager if it's not already there. */
+ private void addToWindowManagerMaybe() {
+ // If the stack is null, or already added, don't add it.
+ if (mStackView == null || mAddedToWindowManager) {
+ return;
+ }
+
mWmLayoutParams = new WindowManager.LayoutParams(
// Fill the screen so we can use translation animations to position the bubble
// stack. We'll use touchable regions to ignore touches that are not on the bubbles
@@ -629,9 +636,37 @@
mWmLayoutParams.packageName = mContext.getPackageName();
mWmLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- mWindowManager.addView(mStackView, mWmLayoutParams);
+ try {
+ mAddedToWindowManager = true;
+ mWindowManager.addView(mStackView, mWmLayoutParams);
+ } catch (IllegalStateException e) {
+ // This means the stack has already been added. This shouldn't happen, since we keep
+ // track of that, but just in case, update the previously added view's layout params.
+ e.printStackTrace();
+ updateWmFlags();
+ }
}
+ /** Removes the BubbleStackView from the WindowManager if it's there. */
+ private void removeFromWindowManagerMaybe() {
+ if (!mAddedToWindowManager) {
+ return;
+ }
+
+ try {
+ mAddedToWindowManager = false;
+ mWindowManager.removeView(mStackView);
+ } catch (IllegalArgumentException e) {
+ // This means the stack has already been removed - it shouldn't happen, but ignore if it
+ // does, since we wanted it removed anyway.
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Updates the BubbleStackView's WindowManager.LayoutParams, and updates the WindowManager with
+ * the new params if the stack has been added.
+ */
private void updateWmFlags() {
if (isStackExpanded()) {
// If we're expanded, we want to be focusable so that the ActivityView can receive focus
@@ -643,7 +678,25 @@
mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
}
- mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
+ if (mStackView != null && mAddedToWindowManager) {
+ try {
+ mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
+ } catch (IllegalArgumentException e) {
+ // If the stack is somehow not there, ignore the attempt to update it.
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Called by the BubbleStackView and whenever all bubbles have animated out, and none have been
+ * added in the meantime.
+ */
+ private void onAllBubblesAnimatedOut() {
+ if (mStackView != null) {
+ mStackView.setVisibility(INVISIBLE);
+ removeFromWindowManagerMaybe();
+ }
}
/**
@@ -833,10 +886,9 @@
}
void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) {
- if (mStackView == null) {
- // Lazy init stack view when a bubble is created
- ensureStackViewCreated();
- }
+ // Lazy init stack view when a bubble is created
+ ensureStackViewCreated();
+
// If this is an interruptive notif, mark that it's interrupted
if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
notif.setInterruption();
@@ -1196,11 +1248,15 @@
if (mStackView == null) {
return;
}
- if (mStatusBarStateListener.getCurrentState() == SHADE && hasBubbles()) {
- // Bubbles only appear in unlocked shade
- mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE);
- } else if (mStackView != null) {
+
+ if (mStatusBarStateListener.getCurrentState() != SHADE) {
+ // Bubbles don't appear over the locked shade.
mStackView.setVisibility(INVISIBLE);
+ } else if (hasBubbles()) {
+ // If we're unlocked, show the stack if we have bubbles. If we don't have bubbles, the
+ // stack will be set to INVISIBLE in onAllBubblesAnimatedOut after the bubbles animate
+ // out.
+ mStackView.setVisibility(VISIBLE);
}
mStackView.updateContentDescription();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 88f5eb0..c97ca2b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -656,7 +656,8 @@
@Nullable SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
SysUiState sysUiState,
- NotificationShadeWindowController notificationShadeWindowController) {
+ NotificationShadeWindowController notificationShadeWindowController,
+ Runnable allBubblesAnimatedOutAction) {
super(context);
mBubbleData = data;
@@ -691,11 +692,18 @@
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
+ final Runnable onBubbleAnimatedOut = () -> {
+ if (getBubbleCount() == 0) {
+ allBubblesAnimatedOutAction.run();
+ }
+ };
+
mStackAnimationController = new StackAnimationController(
- floatingContentCoordinator, this::getBubbleCount);
+ floatingContentCoordinator, this::getBubbleCount, onBubbleAnimatedOut);
mExpandedAnimationController = new ExpandedAnimationController(
- mDisplaySize, mExpandedViewPadding, res.getConfiguration().orientation);
+ mDisplaySize, mExpandedViewPadding, res.getConfiguration().orientation,
+ onBubbleAnimatedOut);
mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
setUpUserEducation();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index f57cf42..76ff1af 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -32,6 +32,7 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.util.animation.PhysicsAnimator;
import com.android.systemui.util.magnetictarget.MagnetizedObject;
import com.google.android.collect.Sets;
@@ -69,6 +70,10 @@
*/
private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f;
+ private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfig =
+ new PhysicsAnimator.SpringConfig(
+ EXPAND_COLLAPSE_ANIM_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
+
/** Horizontal offset between bubbles, which we need to know to re-stack them. */
private float mStackOffsetPx;
/** Space between status bar and bubbles in the expanded state. */
@@ -116,10 +121,17 @@
private int mExpandedViewPadding;
+ /**
+ * Callback to run whenever any bubble is animated out. The BubbleStackView will check if the
+ * end of this animation means we have no bubbles left, and notify the BubbleController.
+ */
+ private Runnable mOnBubbleAnimatedOutAction;
+
public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
- int orientation) {
+ int orientation, Runnable onBubbleAnimatedOutAction) {
updateResources(orientation, displaySize);
mExpandedViewPadding = expandedViewPadding;
+ mOnBubbleAnimatedOutAction = onBubbleAnimatedOutAction;
}
/**
@@ -355,8 +367,8 @@
}
animationForChild(bubble)
.withStiffness(SpringForce.STIFFNESS_HIGH)
- .scaleX(1.1f)
- .scaleY(1.1f)
+ .scaleX(0f)
+ .scaleY(0f)
.translationY(bubble.getTranslationY() + translationYBy)
.alpha(0f, after)
.start();
@@ -500,18 +512,17 @@
@Override
void onChildRemoved(View child, int index, Runnable finishRemoval) {
- final PhysicsAnimationLayout.PhysicsPropertyAnimator animator = animationForChild(child);
-
// If we're removing the dragged-out bubble, that means it got dismissed.
if (child.equals(getDraggedOutBubble())) {
mMagnetizedBubbleDraggingOut = null;
finishRemoval.run();
+ mOnBubbleAnimatedOutAction.run();
} else {
- animator.alpha(0f, finishRemoval /* endAction */)
- .withStiffness(SpringForce.STIFFNESS_HIGH)
- .withDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
- .scaleX(1.1f)
- .scaleY(1.1f)
+ PhysicsAnimator.getInstance(child)
+ .spring(DynamicAnimation.ALPHA, 0f)
+ .spring(DynamicAnimation.SCALE_X, 0f, mAnimateOutSpringConfig)
+ .spring(DynamicAnimation.SCALE_Y, 0f, mAnimateOutSpringConfig)
+ .withEndActions(finishRemoval, mOnBubbleAnimatedOutAction)
.start();
}
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 2cfe1dd..8318c21 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -74,6 +74,10 @@
private static final int FLING_FOLLOW_STIFFNESS = 20000;
public static final float DEFAULT_BOUNCINESS = 0.9f;
+ private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfig =
+ new PhysicsAnimator.SpringConfig(
+ ANIMATE_IN_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
+
/**
* Friction applied to fling animations. Since the stack must land on one of the sides of the
* screen, we want less friction horizontally so that the stack has a better chance of making it
@@ -248,12 +252,19 @@
/** Returns the number of 'real' bubbles (excluding the overflow bubble). */
private IntSupplier mBubbleCountSupplier;
+ /**
+ * Callback to run whenever any bubble is animated out. The BubbleStackView will check if the
+ * end of this animation means we have no bubbles left, and notify the BubbleController.
+ */
+ private Runnable mOnBubbleAnimatedOutAction;
+
public StackAnimationController(
FloatingContentCoordinator floatingContentCoordinator,
- IntSupplier bubbleCountSupplier) {
+ IntSupplier bubbleCountSupplier,
+ Runnable onBubbleAnimatedOutAction) {
mFloatingContentCoordinator = floatingContentCoordinator;
mBubbleCountSupplier = bubbleCountSupplier;
-
+ mOnBubbleAnimatedOutAction = onBubbleAnimatedOutAction;
}
/**
@@ -652,8 +663,8 @@
public void animateStackDismissal(float translationYBy, Runnable after) {
animationsForChildrenFromIndex(0, (index, animation) ->
animation
- .scaleX(0.5f)
- .scaleY(0.5f)
+ .scaleX(0f)
+ .scaleY(0f)
.alpha(0f)
.translationY(
mLayout.getChildAt(index).getTranslationY() + translationYBy)
@@ -760,13 +771,11 @@
@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))
+ PhysicsAnimator.getInstance(child)
+ .spring(DynamicAnimation.ALPHA, 0f)
+ .spring(DynamicAnimation.SCALE_X, 0f, mAnimateOutSpringConfig)
+ .spring(DynamicAnimation.SCALE_Y, 0f, mAnimateOutSpringConfig)
+ .withEndActions(finishRemoval, mOnBubbleAnimatedOutAction)
.start();
// If there are other bubbles, pull them into the correct position.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
index ec6d3e9..6a14863 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -49,11 +49,13 @@
private int mOrientation = Configuration.ORIENTATION_PORTRAIT;
private float mLauncherGridDiff = 30f;
+ private Runnable mOnBubbleAnimatedOutAction = Mockito.mock(Runnable.class);
+
@Spy
private ExpandedAnimationController mExpandedController =
new ExpandedAnimationController(
new Point(mDisplayWidth, mDisplayHeight) /* displaySize */,
- mExpandedViewPadding, mOrientation);
+ mExpandedViewPadding, mOrientation, mOnBubbleAnimatedOutAction);
private int mStackOffset;
private float mBubblePaddingTop;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
index b1ac022..cc62a2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
@@ -40,6 +40,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -66,7 +67,7 @@
public int getAsInt() {
return mLayout.getChildCount();
}
- }));
+ }, Mockito.mock(Runnable.class)));
mLayout.setActiveController(mStackController);
addOneMoreThanBubbleLimitBubbles();
mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset);
@@ -303,8 +304,9 @@
private class TestableStackController extends StackAnimationController {
TestableStackController(
FloatingContentCoordinator floatingContentCoordinator,
- IntSupplier bubbleCountSupplier) {
- super(floatingContentCoordinator, bubbleCountSupplier);
+ IntSupplier bubbleCountSupplier,
+ Runnable onBubbleAnimatedOutAction) {
+ super(floatingContentCoordinator, bubbleCountSupplier, onBubbleAnimatedOutAction);
}
@Override