diff options
7 files changed, 166 insertions, 103 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 5540dc648ca0..37de67ff155c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -726,7 +726,7 @@ public class BubbleStackView extends FrameLayout { public void updateBubbleOrder(List<Bubble> bubbles) { for (int i = 0; i < bubbles.size(); i++) { Bubble bubble = bubbles.get(i); - mBubbleContainer.moveViewTo(bubble.iconView, i); + mBubbleContainer.reorderView(bubble.iconView, i); } } @@ -908,16 +908,14 @@ public class BubbleStackView extends FrameLayout { if (shouldExpand) { mBubbleContainer.setActiveController(mExpandedAnimationController); - mExpandedAnimationController.expandFromStack( - mStackAnimationController.getStackPositionAlongNearestHorizontalEdge() - /* collapseTo */, - () -> { - updatePointerPosition(); - updateAfter.run(); - } /* after */); + mExpandedAnimationController.expandFromStack(() -> { + updatePointerPosition(); + updateAfter.run(); + } /* after */); } else { mBubbleContainer.cancelAllAnimations(); mExpandedAnimationController.collapseBackToStack( + mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(), () -> { mBubbleContainer.setActiveController(mStackAnimationController); updateAfter.run(); @@ -1110,6 +1108,10 @@ public class BubbleStackView extends FrameLayout { /** Called when a gesture is completed or cancelled. */ void onGestureFinished() { mIsGestureInProgress = false; + + if (mIsExpanded) { + mExpandedAnimationController.onGestureFinished(); + } } /** Prepares and starts the desaturate/darken animation on the bubble stack. */ @@ -1200,6 +1202,7 @@ public class BubbleStackView extends FrameLayout { */ void magnetToStackIfNeededThenAnimateDismissal( View touchedView, float velX, float velY, Runnable after) { + final View draggedOutBubble = mExpandedAnimationController.getDraggedOutBubble(); final Runnable animateDismissal = () -> { mAfterMagnet = null; @@ -1217,7 +1220,7 @@ public class BubbleStackView extends FrameLayout { resetDesaturationAndDarken(); }); } else { - mExpandedAnimationController.dismissDraggedOutBubble(() -> { + mExpandedAnimationController.dismissDraggedOutBubble(draggedOutBubble, () -> { mAnimatingMagnet = false; mShowingDismiss = false; mDraggingInDismissTarget = false; @@ -1384,10 +1387,18 @@ public class BubbleStackView extends FrameLayout { }; // Post in case layout isn't complete and getWidth returns 0. - post(() -> mFlyout.showFlyout( - updateMessage, mStackAnimationController.getStackPosition(), getWidth(), - mStackAnimationController.isStackOnLeftSide(), - bubble.iconView.getBadgeColor(), mAfterFlyoutHides)); + post(() -> { + // An auto-expanding bubble could have been posted during the time it takes to + // layout. + if (isExpanded()) { + return; + } + + mFlyout.showFlyout( + updateMessage, mStackAnimationController.getStackPosition(), getWidth(), + mStackAnimationController.isStackOnLeftSide(), + bubble.iconView.getBadgeColor(), mAfterFlyoutHides); + }); } mFlyout.removeCallbacks(mHideFlyout); 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 67ae18600f09..1fa0e12452e1 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -22,6 +22,7 @@ import android.graphics.PointF; import android.view.View; import android.view.WindowInsets; +import androidx.annotation.Nullable; import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.SpringForce; @@ -67,6 +68,12 @@ public class ExpandedAnimationController /** Whether the dragged-out bubble is in the dismiss target. */ private boolean mIndividualBubbleWithinDismissTarget = false; + private boolean mAnimatingExpand = false; + private boolean mAnimatingCollapse = false; + private Runnable mAfterExpand; + private Runnable mAfterCollapse; + private PointF mCollapsePoint; + /** * Whether the dragged out bubble is springing towards the touch point, rather than using the * default behavior of moving directly to the touch point. @@ -95,42 +102,60 @@ public class ExpandedAnimationController private View mBubbleDraggingOut; /** - * Drag velocities for the dragging-out bubble when the drag finished. These are used by - * {@link #onChildRemoved} to animate out the bubble while respecting touch velocity. - */ - private float mBubbleDraggingOutVelX; - private float mBubbleDraggingOutVelY; - - /** * Animates expanding the bubbles into a row along the top of the screen. */ - public void expandFromStack(PointF collapseTo, Runnable after) { + public void expandFromStack(Runnable after) { + mAnimatingCollapse = false; + mAnimatingExpand = true; + mAfterExpand = after; + + startOrUpdateExpandAnimation(); + } + + /** Animate collapsing the bubbles back to their stacked position. */ + public void collapseBackToStack(PointF collapsePoint, Runnable after) { + mAnimatingExpand = false; + mAnimatingCollapse = true; + mAfterCollapse = after; + mCollapsePoint = collapsePoint; + + startOrUpdateCollapseAnimation(); + } + + private void startOrUpdateExpandAnimation() { animationsForChildrenFromIndex( 0, /* startIndex */ - new ChildAnimationConfigurator() { - @Override - public void configureAnimationForChildAtIndex( - int index, PhysicsAnimationLayout.PhysicsPropertyAnimator animation) { - animation.position(getBubbleLeft(index), getExpandedY()); + (index, animation) -> animation.position(getBubbleLeft(index), getExpandedY())) + .startAll(() -> { + mAnimatingExpand = false; + + if (mAfterExpand != null) { + mAfterExpand.run(); } - }) - .startAll(after); - mCollapseToPoint = collapseTo; + mAfterExpand = null; + }); } - /** Animate collapsing the bubbles back to their stacked position. */ - public void collapseBackToStack(Runnable after) { + private void startOrUpdateCollapseAnimation() { // Stack to the left if we're going to the left, or right if not. - final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mCollapseToPoint.x) ? -1 : 1; - + final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1; animationsForChildrenFromIndex( 0, /* startIndex */ - (index, animation) -> + (index, animation) -> { animation.position( - mCollapseToPoint.x + (sideMultiplier * index * mStackOffsetPx), - mCollapseToPoint.y)) - .startAll(after /* endAction */); + mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx), + mCollapsePoint.y); + }) + .startAll(() -> { + mAnimatingCollapse = false; + + if (mAfterCollapse != null) { + mAfterCollapse.run(); + } + + mAfterCollapse = null; + }); } /** Prepares the given bubble to be dragged out. */ @@ -174,10 +199,10 @@ public class ExpandedAnimationController } /** Plays a dismiss animation on the dragged out bubble. */ - public void dismissDraggedOutBubble(Runnable after) { + public void dismissDraggedOutBubble(View bubble, Runnable after) { mIndividualBubbleWithinDismissTarget = false; - animationForChild(mBubbleDraggingOut) + animationForChild(bubble) .withStiffness(SpringForce.STIFFNESS_HIGH) .scaleX(1.1f) .scaleY(1.1f) @@ -187,6 +212,10 @@ public class ExpandedAnimationController updateBubblePositions(); } + @Nullable public View getDraggedOutBubble() { + return mBubbleDraggingOut; + } + /** Magnets the given bubble to the dismiss target. */ public void magnetBubbleToDismiss( View bubbleView, float velX, float velY, float destY, Runnable after) { @@ -225,24 +254,17 @@ public class ExpandedAnimationController final int index = mLayout.indexOfChild(bubbleView); animationForChildAtIndex(index) - .position(getBubbleLeft(index), getExpandedY()) - .withPositionStartVelocities(velX, velY) - .start(() -> bubbleView.setTranslationZ(0f) /* after */); + .position(getBubbleLeft(index), getExpandedY()) + .withPositionStartVelocities(velX, velY) + .start(() -> bubbleView.setTranslationZ(0f) /* after */); - mBubbleDraggingOut = null; - mBubbleDraggedOutEnough = false; updateBubblePositions(); } - /** - * Sets configuration variables so that when the given bubble is removed, the animations are - * started with the given velocities. - */ - public void prepareForDismissalWithVelocity(View bubbleView, float velX, float velY) { - mBubbleDraggingOut = bubbleView; - mBubbleDraggingOutVelX = velX; - mBubbleDraggingOutVelY = velY; + /** Resets bubble drag out gesture flags. */ + public void onGestureFinished() { mBubbleDraggedOutEnough = false; + mBubbleDraggingOut = null; } /** @@ -326,14 +348,21 @@ public class ExpandedAnimationController @Override void onChildAdded(View child, int index) { - child.setTranslationX(getXForChildAtIndex(index)); - - animationForChild(child) - .translationY( - getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */ - getExpandedY() /* to */) - .start(); - updateBubblePositions(); + // If a bubble is added while the expand/collapse animations are playing, update the + // animation to include the new bubble. + if (mAnimatingExpand) { + startOrUpdateExpandAnimation(); + } else if (mAnimatingCollapse) { + startOrUpdateCollapseAnimation(); + } else { + child.setTranslationX(getXForChildAtIndex(index)); + animationForChild(child) + .translationY( + getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */ + getExpandedY() /* to */) + .start(); + updateBubblePositions(); + } } @Override @@ -357,7 +386,16 @@ public class ExpandedAnimationController updateBubblePositions(); } + @Override + void onChildReordered(View child, int oldIndex, int newIndex) { + updateBubblePositions(); + } + private void updateBubblePositions() { + if (mAnimatingExpand || mAnimatingCollapse) { + return; + } + for (int i = 0; i < mLayout.getChildCount(); i++) { final View bubble = mLayout.getChildAt(i); @@ -366,6 +404,7 @@ public class ExpandedAnimationController if (bubble.equals(mBubbleDraggingOut)) { return; } + animationForChild(bubble) .translationX(getBubbleLeft(i)) .start(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java index b8535adfc280..3a3339249d5b 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java @@ -139,6 +139,9 @@ public class PhysicsAnimationLayout extends FrameLayout { */ abstract void onChildRemoved(View child, int index, Runnable finishRemoval); + /** Called when a child view has been reordered in the view hierachy. */ + abstract void onChildReordered(View child, int oldIndex, int newIndex); + /** * Called when the controller is set as the active animation controller for the given * layout. Once active, the controller can start animations using the animator instances @@ -311,40 +314,11 @@ public class PhysicsAnimationLayout extends FrameLayout { @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { - super.addView(child, index, params); - - // Set up animations for the new view, if the controller is set. If it isn't set, we'll be - // setting up animations for all children when setActiveController is called. - if (mController != null) { - for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { - setUpAnimationForChild(property, child, index); - } - - mController.onChildAdded(child, index); - } + addViewInternal(child, index, params, false /* isReorder */); } @Override public void removeView(View view) { - removeViewAndThen(view, /* callback */ null); - } - - @Override - public void removeViewAt(int index) { - removeView(getChildAt(index)); - } - - /** Immediately moves the view from wherever it currently is, to the given index. */ - public void moveViewTo(View view, int index) { - super.removeView(view); - addView(view, index); - } - - /** - * Let the controller know that this view should be removed, and then call the callback once the - * controller has finished any removal animations and the view has actually been removed. - */ - public void removeViewAndThen(View view, Runnable callback) { if (mController != null) { final int index = indexOfChild(view); @@ -359,19 +333,28 @@ public class PhysicsAnimationLayout extends FrameLayout { // any are still running and then remove it. cancelAnimationsOnView(view); removeTransientView(view); - - if (callback != null) { - callback.run(); - } }); } else { // Without a controller, nobody will animate this view out, so it gets an unceremonious // departure. super.removeView(view); + } + } - if (callback != null) { - callback.run(); - } + @Override + public void removeViewAt(int index) { + removeView(getChildAt(index)); + } + + /** Immediately re-orders the view to the given index. */ + public void reorderView(View view, int index) { + final int oldIndex = indexOfChild(view); + + super.removeView(view); + addViewInternal(view, index, view.getLayoutParams(), true /* isReorder */); + + if (mController != null) { + mController.onChildReordered(view, oldIndex, index); } } @@ -453,6 +436,26 @@ public class PhysicsAnimationLayout extends FrameLayout { } /** + * Adds a view to the layout. If this addition is not the result of a call to + * {@link #reorderView}, this will also notify the controller via + * {@link PhysicsAnimationController#onChildAdded} and set up animations for the view. + */ + private void addViewInternal( + View child, int index, ViewGroup.LayoutParams params, boolean isReorder) { + super.addView(child, index, params); + + // Set up animations for the new view, if the controller is set. If it isn't set, we'll be + // setting up animations for all children when setActiveController is called. + if (mController != null && !isReorder) { + for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { + setUpAnimationForChild(property, child, index); + } + + mController.onChildAdded(child, index); + } + } + + /** * Retrieves the animation of the given property from the view at the given index via the view * tag system. */ 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 6f1304b5fbcd..ab8752e4195f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -651,6 +651,9 @@ public class StackAnimationController extends } @Override + void onChildReordered(View child, int oldIndex, int newIndex) {} + + @Override void onActiveControllerForLayout(PhysicsAnimationLayout layout) { Resources res = layout.getResources(); mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); 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 5df5c6e32db9..b324235106c2 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 @@ -73,14 +73,14 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC @Test public void testExpansionAndCollapse() throws InterruptedException { Runnable afterExpand = Mockito.mock(Runnable.class); - mExpandedController.expandFromStack(mExpansionPoint, afterExpand); + mExpandedController.expandFromStack(afterExpand); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); testBubblesInCorrectExpandedPositions(); verify(afterExpand).run(); Runnable afterCollapse = Mockito.mock(Runnable.class); - mExpandedController.collapseBackToStack(afterCollapse); + mExpandedController.collapseBackToStack(mExpansionPoint, afterCollapse); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1); @@ -139,7 +139,6 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC assertEquals(500f, draggedBubble.getTranslationY(), 1f); // Snap it back and make sure it made it back correctly. - mExpandedController.prepareForDismissalWithVelocity(draggedBubble, 0f, 0f); mLayout.removeView(draggedBubble); waitForLayoutMessageQueue(); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); @@ -169,7 +168,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC // Dismiss the now-magneted bubble, verify that the callback was called. final Runnable afterDismiss = Mockito.mock(Runnable.class); - mExpandedController.dismissDraggedOutBubble(afterDismiss); + mExpandedController.dismissDraggedOutBubble(draggedOutView, afterDismiss); waitForPropertyAnimations(DynamicAnimation.ALPHA); verify(after).run(); @@ -224,7 +223,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC /** Expand the stack and wait for animations to finish. */ private void expand() throws InterruptedException { - mExpandedController.expandFromStack(mExpansionPoint, Mockito.mock(Runnable.class)); + mExpandedController.expandFromStack(Mockito.mock(Runnable.class)); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java index 95e01774f948..f8b32c213109 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java @@ -23,7 +23,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -488,6 +487,9 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { } @Override + void onChildReordered(View child, int oldIndex, int newIndex) {} + + @Override void onActiveControllerForLayout(PhysicsAnimationLayout layout) {} } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java index f17d7a7da9f3..f633f3996d13 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java @@ -275,6 +275,12 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { } @Override + void onChildReordered(View child, int oldIndex, int newIndex) { + runOnMainThreadAndBlock( + () -> mWrappedController.onChildReordered(child, oldIndex, newIndex)); + } + + @Override void onActiveControllerForLayout(PhysicsAnimationLayout layout) { runOnMainThreadAndBlock( () -> mWrappedController.onActiveControllerForLayout(layout)); |