Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +1 −2 Original line number Original line Diff line number Diff line Loading @@ -324,8 +324,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F if (updatePosition && !mIsExpanded) { if (updatePosition && !mIsExpanded) { // If alerting it gets promoted to top of the stack. // If alerting it gets promoted to top of the stack. if (mBubbleContainer.indexOfChild(bubbleView) != 0) { if (mBubbleContainer.indexOfChild(bubbleView) != 0) { mBubbleContainer.removeViewAndThen(bubbleView, mBubbleContainer.moveViewTo(bubbleView, 0); () -> mBubbleContainer.addView(bubbleView, 0)); } } requestUpdate(); requestUpdate(); } } Loading packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java +85 −68 Original line number Original line Diff line number Diff line Loading @@ -27,9 +27,8 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.systemui.R; import com.android.systemui.R; import java.util.ArrayList; import java.util.HashMap; import java.util.HashMap; import java.util.List; import java.util.HashSet; import java.util.Set; import java.util.Set; /** /** Loading Loading @@ -122,12 +121,8 @@ public class PhysicsAnimationLayout extends FrameLayout { protected final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation.OnAnimationEndListener> protected final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation.OnAnimationEndListener> mEndListenerForProperty = new HashMap<>(); mEndListenerForProperty = new HashMap<>(); /** /** Set of currently rendered transient views. */ * List of views that were passed to removeView, but are currently being animated out. These private final Set<View> mTransientViews = new HashSet<>(); * views will be actually removed by the controller (via super.removeView) once they're done * animating out. */ private final List<View> mViewsToBeActuallyRemoved = new ArrayList<>(); /** The currently active animation controller. */ /** The currently active animation controller. */ private PhysicsAnimationController mController; private PhysicsAnimationController mController; Loading Loading @@ -186,23 +181,6 @@ public class PhysicsAnimationLayout extends FrameLayout { mEndListenerForProperty.remove(property); mEndListenerForProperty.remove(property); } } /** * Returns the index of the view that precedes the given index, ignoring views that were passed * to removeView, but are currently being animated out before actually being removed. * * @return index of the preceding view, or -1 if there are none. */ public int getPrecedingNonRemovedViewIndex(int index) { for (int i = index + 1; i < getChildCount(); i++) { View precedingView = getChildAt(i); if (!mViewsToBeActuallyRemoved.contains(precedingView)) { return i; } } return -1; } @Override @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { public void addView(View child, int index, ViewGroup.LayoutParams params) { super.addView(child, index, params); super.addView(child, index, params); Loading @@ -224,6 +202,24 @@ public class PhysicsAnimationLayout extends FrameLayout { removeViewAndThen(view, /* callback */ null); removeViewAndThen(view, /* callback */ null); } } @Override public void addTransientView(View view, int index) { super.addTransientView(view, index); mTransientViews.add(view); } @Override public void removeTransientView(View view) { super.removeTransientView(view); mTransientViews.remove(view); } /** 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 * 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. * controller has finished any removal animations and the view has actually been removed. Loading @@ -231,23 +227,31 @@ public class PhysicsAnimationLayout extends FrameLayout { public void removeViewAndThen(View view, Runnable callback) { public void removeViewAndThen(View view, Runnable callback) { if (mController != null) { if (mController != null) { final int index = indexOfChild(view); final int index = indexOfChild(view); // Remove the view only if it exists in this layout, and we're not already working on // animating its removal. // Remove the view and add it back as a transient view so we can animate it out. if (index > -1 && !mViewsToBeActuallyRemoved.contains(view)) { super.removeView(view); mViewsToBeActuallyRemoved.add(view); addTransientView(view, index); setChildrenVisibility(); setChildrenVisibility(); // Tell the controller to animate this view out, and call the callback when it wants // Tell the controller to animate this view out, and call the callback when it's // to actually remove the view. // finished. mController.onChildToBeRemoved(view, index, () -> { mController.onChildToBeRemoved(view, index, () -> { removeViewImmediateAndThen(view, callback); // Done animating, remove the transient view. mViewsToBeActuallyRemoved.remove(view); removeTransientView(view); }); if (callback != null) { callback.run(); } } }); } else { } else { // Without a controller, nobody will animate this view out, so it gets an unceremonious // Without a controller, nobody will animate this view out, so it gets an unceremonious // departure. // departure. removeViewImmediateAndThen(view, callback); super.removeView(view); if (callback != null) { callback.run(); } } } } } Loading Loading @@ -278,17 +282,18 @@ public class PhysicsAnimationLayout extends FrameLayout { } } /** /** * Animates the property of the child at the given index to the given value, then runs the * Animates the property of the given child view, then runs the callback provided when the * callback provided when the animation ends. * animation ends. */ */ protected void animateValueForChildAtIndex( protected void animateValueForChild( DynamicAnimation.ViewProperty property, DynamicAnimation.ViewProperty property, int index, View view, float value, float value, float startVel, float startVel, Runnable after) { Runnable after) { if (index < getChildCount()) { if (view != null) { final SpringAnimation animation = getAnimationAtIndex(property, index); final SpringAnimation animation = (SpringAnimation) view.getTag(getTagIdForProperty(property)); if (after != null) { if (after != null) { animation.addEndListener(new OneTimeEndListener() { animation.addEndListener(new OneTimeEndListener() { @Override @Override Loading @@ -300,12 +305,36 @@ public class PhysicsAnimationLayout extends FrameLayout { }); }); } } if (startVel != Float.MAX_VALUE) { animation.animateToFinalPosition(value); animation.setStartVelocity(startVel); } } } animation.animateToFinalPosition(value); protected void animateValueForChild( DynamicAnimation.ViewProperty property, View view, float value, Runnable after) { animateValueForChild(property, view, value, Float.MAX_VALUE, after); } } protected void animateValueForChild( DynamicAnimation.ViewProperty property, View view, float value) { animateValueForChild(property, view, value, Float.MAX_VALUE, /* after */ null); } /** * Animates the property of the child at the given index to the given value, then runs the * callback provided when the animation ends. */ protected void animateValueForChildAtIndex( DynamicAnimation.ViewProperty property, int index, float value, float startVel, Runnable after) { animateValueForChild(property, getChildAt(index), value, startVel, after); } } /** Shortcut to animate a value with a callback, but no start velocity. */ /** Shortcut to animate a value with a callback, but no start velocity. */ Loading Loading @@ -366,18 +395,6 @@ public class PhysicsAnimationLayout extends FrameLayout { } } } } /** Immediately removes the view, without notifying the controller, then runs the callback. */ private void removeViewImmediateAndThen(View view, Runnable callback) { super.removeView(view); if (callback != null) { callback.run(); } setChildrenVisibility(); } /** /** * Retrieves the animation of the given property from the view at the given index via the view * Retrieves the animation of the given property from the view at the given index via the view * tag system. * tag system. Loading @@ -401,7 +418,11 @@ public class PhysicsAnimationLayout extends FrameLayout { newAnim.addUpdateListener((animation, value, velocity) -> { newAnim.addUpdateListener((animation, value, velocity) -> { final int nextAnimInChain = final int nextAnimInChain = mController.getNextAnimationInChain(property, indexOfChild(child)); mController.getNextAnimationInChain(property, indexOfChild(child)); if (nextAnimInChain == PhysicsAnimationController.NONE) { // If the controller doesn't want us to chain, or if we're a transient view in the // process of being removed, don't chain. if (nextAnimInChain == PhysicsAnimationController.NONE || mTransientViews.contains(child)) { return; return; } } Loading @@ -412,9 +433,7 @@ public class PhysicsAnimationLayout extends FrameLayout { // If this property's animations should be chained, then check to see if there is a // If this property's animations should be chained, then check to see if there is a // subsequent animation within the rendering limit, and if so, tell it to animate to // subsequent animation within the rendering limit, and if so, tell it to animate to // this animation's new value (plus the offset). // this animation's new value (plus the offset). if (nextAnimInChain < Math.min( if (nextAnimInChain < Math.min(getChildCount(), mMaxRenderedChildren)) { getChildCount(), mMaxRenderedChildren + mViewsToBeActuallyRemoved.size())) { getAnimationAtIndex(property, animIndex + 1) getAnimationAtIndex(property, animIndex + 1) .animateToFinalPosition(value + offset); .animateToFinalPosition(value + offset); } else if (nextAnimInChain < getChildCount()) { } else if (nextAnimInChain < getChildCount()) { Loading Loading @@ -442,9 +461,7 @@ public class PhysicsAnimationLayout extends FrameLayout { // Ignore views that are animating out when calculating whether to hide the // Ignore views that are animating out when calculating whether to hide the // view. That is, if we're supposed to render 5 views, but 4 are animating out // view. That is, if we're supposed to render 5 views, but 4 are animating out // and will soon be removed, render up to 9 views temporarily. // and will soon be removed, render up to 9 views temporarily. i < (mMaxRenderedChildren + mViewsToBeActuallyRemoved.size()) i < mMaxRenderedChildren ? View.VISIBLE : View.GONE); ? View.VISIBLE : View.GONE); } } } } Loading packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +16 −24 Original line number Original line Diff line number Diff line Loading @@ -53,8 +53,8 @@ public class StackAnimationController extends /** Scale factor to use initially for new bubbles being animated in. */ /** Scale factor to use initially for new bubbles being animated in. */ private static final float ANIMATE_IN_STARTING_SCALE = 1.15f; private static final float ANIMATE_IN_STARTING_SCALE = 1.15f; /** Translation factor (multiplied by stack offset) to use for new bubbles being animated in. */ /** Translation factor (multiplied by stack offset) to use for bubbles being animated in/out. */ private static final int ANIMATE_IN_TRANSLATION_FACTOR = 4; private static final int ANIMATE_TRANSLATION_FACTOR = 4; /** /** * Values to use for the default {@link SpringForce} provided to the physics animation layout. * Values to use for the default {@link SpringForce} provided to the physics animation layout. Loading Loading @@ -309,7 +309,7 @@ public class StackAnimationController extends // animate in from this position. Since the animations are chained, when the new bubble // animate in from this position. Since the animations are chained, when the new bubble // flies in from the side, it will push the other ones out of the way. // flies in from the side, it will push the other ones out of the way. float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); child.setTranslationX(mStackPosition.x - (ANIMATE_IN_TRANSLATION_FACTOR * xOffset)); child.setTranslationX(mStackPosition.x - ANIMATE_TRANSLATION_FACTOR * xOffset); mLayout.animateValueForChildAtIndex( mLayout.animateValueForChildAtIndex( DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x); DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x); } } Loading @@ -318,27 +318,19 @@ public class StackAnimationController extends @Override @Override void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { // Animate the child out, actually removing it once its alpha is zero. // Animate the child out, actually removing it once its alpha is zero. mLayout.animateValueForChildAtIndex( mLayout.animateValueForChild( DynamicAnimation.ALPHA, index, 0f, () -> { DynamicAnimation.ALPHA, child, 0f, actuallyRemove); actuallyRemove.run(); mLayout.animateValueForChild(DynamicAnimation.SCALE_X, child, ANIMATE_IN_STARTING_SCALE); }); mLayout.animateValueForChild(DynamicAnimation.SCALE_Y, child, ANIMATE_IN_STARTING_SCALE); mLayout.animateValueForChildAtIndex( DynamicAnimation.SCALE_X, index, ANIMATE_IN_STARTING_SCALE); mLayout.animateValueForChildAtIndex( DynamicAnimation.SCALE_Y, index, ANIMATE_IN_STARTING_SCALE); final boolean hasPrecedingChild = index + 1 < mLayout.getChildCount(); // Animate the removing view in the opposite direction of the stack. if (hasPrecedingChild) { final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); final int precedingViewIndex = mLayout.getPrecedingNonRemovedViewIndex(index); mLayout.animateValueForChild(DynamicAnimation.TRANSLATION_X, child, if (precedingViewIndex >= 0) { mStackPosition.x - (-xOffset * ANIMATE_TRANSLATION_FACTOR)); final float offsetX = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); // Pull the top of the stack to the correct position, the chained animations will instruct mLayout.animatePositionForChildAtIndex( // any children that are out of place to animate to the correct position. precedingViewIndex, mLayout.animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x); mStackPosition.x + (index * offsetX), mStackPosition.y); } } } } /** Moves the stack, without any animation, to the starting position. */ /** Moves the stack, without any animation, to the starting position. */ Loading packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java +2 −24 Original line number Original line Diff line number Diff line Loading @@ -75,7 +75,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { } } @Test @Test public void testRenderVisibility() { public void testRenderVisibility() throws InterruptedException { mLayout.setController(mTestableController); mLayout.setController(mTestableController); addOneMoreThanRenderLimitBubbles(); addOneMoreThanRenderLimitBubbles(); Loading @@ -87,7 +87,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { } } @Test @Test public void testHierarchyChanges() { public void testHierarchyChanges() throws InterruptedException { mLayout.setController(mTestableController); mLayout.setController(mTestableController); addOneMoreThanRenderLimitBubbles(); addOneMoreThanRenderLimitBubbles(); Loading Loading @@ -241,26 +241,6 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { endListenerCalls.verifyNoMoreInteractions(); endListenerCalls.verifyNoMoreInteractions(); } } @Test public void testPrecedingNonRemovedIndex() { mLayout.setController(mTestableController); addOneMoreThanRenderLimitBubbles(); // Call removeView at index 4, but don't actually remove it yet (as if we're animating it // out). The preceding, non-removed view index to 3 should initially be 4, but then 5 since // 4 is on its way out. assertEquals(4, mLayout.getPrecedingNonRemovedViewIndex(3)); mLayout.removeView(mViews.get(4)); assertEquals(5, mLayout.getPrecedingNonRemovedViewIndex(3)); // Call removeView at index 1, and actually remove it immediately. With the old view at 1 // instantly gone, the preceding view to 0 should be 1 in both cases. assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0)); mTestableController.setRemoveImmediately(true); mLayout.removeView(mViews.get(1)); assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0)); } @Test @Test public void testSetController() throws InterruptedException { public void testSetController() throws InterruptedException { // Add the bubbles, then set the controller, to make sure that a controller added to an // Add the bubbles, then set the controller, to make sure that a controller added to an Loading Loading @@ -360,8 +340,6 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { mLayout.cancelAllAnimations(); mLayout.cancelAllAnimations(); waitForLayoutMessageQueue(); // Animations should be somewhere before their end point. // Animations should be somewhere before their end point. assertTrue(mViews.get(0).getTranslationX() < 1000); assertTrue(mViews.get(0).getTranslationX() < 1000); assertTrue(mViews.get(0).getTranslationY() < 1000); assertTrue(mViews.get(0).getTranslationY() < 1000); Loading packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java +30 −7 Original line number Original line Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Looper; import android.view.DisplayCutout; import android.view.DisplayCutout; import android.view.View; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowInsets; import android.widget.FrameLayout; import android.widget.FrameLayout; Loading Loading @@ -93,7 +94,7 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { } } /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */ /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */ void addOneMoreThanRenderLimitBubbles() { void addOneMoreThanRenderLimitBubbles() throws InterruptedException { for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { final View newView = new FrameLayout(mContext); final View newView = new FrameLayout(mContext); mLayout.addView(newView, 0); mLayout.addView(newView, 0); Loading Loading @@ -129,7 +130,7 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { void waitForLayoutMessageQueue() throws InterruptedException { void waitForLayoutMessageQueue() throws InterruptedException { // Wait for layout, then the view should be actually removed. // Wait for layout, then the view should be actually removed. CountDownLatch layoutLatch = new CountDownLatch(1); CountDownLatch layoutLatch = new CountDownLatch(1); mLayout.post(layoutLatch::countDown); mMainThreadHandler.post(layoutLatch::countDown); layoutLatch.await(1, TimeUnit.SECONDS); layoutLatch.await(1, TimeUnit.SECONDS); } } Loading @@ -145,11 +146,7 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { @Override @Override public void setController(PhysicsAnimationController controller) { public void setController(PhysicsAnimationController controller) { mMainThreadHandler.post(() -> super.setController(controller)); mMainThreadHandler.post(() -> super.setController(controller)); try { waitForMessageQueueAndIgnoreIfInterrupted(); waitForLayoutMessageQueue(); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override @Override Loading @@ -169,6 +166,32 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { return mWindowInsets; return mWindowInsets; } } @Override public void removeView(View view) { mMainThreadHandler.post(() -> super.removeView(view)); waitForMessageQueueAndIgnoreIfInterrupted(); } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { mMainThreadHandler.post(() -> super.addView(child, index, params)); waitForMessageQueueAndIgnoreIfInterrupted(); } /** * Wait for the queue but just catch and print the exception if interrupted, since we can't * just add the exception to the overridden methods' signatures. */ private void waitForMessageQueueAndIgnoreIfInterrupted() { try { waitForLayoutMessageQueue(); } catch (InterruptedException e) { e.printStackTrace(); } } /** /** * Sets an end listener that will be called after the 'real' end listener that was already * Sets an end listener that will be called after the 'real' end listener that was already * set. * set. Loading Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +1 −2 Original line number Original line Diff line number Diff line Loading @@ -324,8 +324,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F if (updatePosition && !mIsExpanded) { if (updatePosition && !mIsExpanded) { // If alerting it gets promoted to top of the stack. // If alerting it gets promoted to top of the stack. if (mBubbleContainer.indexOfChild(bubbleView) != 0) { if (mBubbleContainer.indexOfChild(bubbleView) != 0) { mBubbleContainer.removeViewAndThen(bubbleView, mBubbleContainer.moveViewTo(bubbleView, 0); () -> mBubbleContainer.addView(bubbleView, 0)); } } requestUpdate(); requestUpdate(); } } Loading
packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java +85 −68 Original line number Original line Diff line number Diff line Loading @@ -27,9 +27,8 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.systemui.R; import com.android.systemui.R; import java.util.ArrayList; import java.util.HashMap; import java.util.HashMap; import java.util.List; import java.util.HashSet; import java.util.Set; import java.util.Set; /** /** Loading Loading @@ -122,12 +121,8 @@ public class PhysicsAnimationLayout extends FrameLayout { protected final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation.OnAnimationEndListener> protected final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation.OnAnimationEndListener> mEndListenerForProperty = new HashMap<>(); mEndListenerForProperty = new HashMap<>(); /** /** Set of currently rendered transient views. */ * List of views that were passed to removeView, but are currently being animated out. These private final Set<View> mTransientViews = new HashSet<>(); * views will be actually removed by the controller (via super.removeView) once they're done * animating out. */ private final List<View> mViewsToBeActuallyRemoved = new ArrayList<>(); /** The currently active animation controller. */ /** The currently active animation controller. */ private PhysicsAnimationController mController; private PhysicsAnimationController mController; Loading Loading @@ -186,23 +181,6 @@ public class PhysicsAnimationLayout extends FrameLayout { mEndListenerForProperty.remove(property); mEndListenerForProperty.remove(property); } } /** * Returns the index of the view that precedes the given index, ignoring views that were passed * to removeView, but are currently being animated out before actually being removed. * * @return index of the preceding view, or -1 if there are none. */ public int getPrecedingNonRemovedViewIndex(int index) { for (int i = index + 1; i < getChildCount(); i++) { View precedingView = getChildAt(i); if (!mViewsToBeActuallyRemoved.contains(precedingView)) { return i; } } return -1; } @Override @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { public void addView(View child, int index, ViewGroup.LayoutParams params) { super.addView(child, index, params); super.addView(child, index, params); Loading @@ -224,6 +202,24 @@ public class PhysicsAnimationLayout extends FrameLayout { removeViewAndThen(view, /* callback */ null); removeViewAndThen(view, /* callback */ null); } } @Override public void addTransientView(View view, int index) { super.addTransientView(view, index); mTransientViews.add(view); } @Override public void removeTransientView(View view) { super.removeTransientView(view); mTransientViews.remove(view); } /** 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 * 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. * controller has finished any removal animations and the view has actually been removed. Loading @@ -231,23 +227,31 @@ public class PhysicsAnimationLayout extends FrameLayout { public void removeViewAndThen(View view, Runnable callback) { public void removeViewAndThen(View view, Runnable callback) { if (mController != null) { if (mController != null) { final int index = indexOfChild(view); final int index = indexOfChild(view); // Remove the view only if it exists in this layout, and we're not already working on // animating its removal. // Remove the view and add it back as a transient view so we can animate it out. if (index > -1 && !mViewsToBeActuallyRemoved.contains(view)) { super.removeView(view); mViewsToBeActuallyRemoved.add(view); addTransientView(view, index); setChildrenVisibility(); setChildrenVisibility(); // Tell the controller to animate this view out, and call the callback when it wants // Tell the controller to animate this view out, and call the callback when it's // to actually remove the view. // finished. mController.onChildToBeRemoved(view, index, () -> { mController.onChildToBeRemoved(view, index, () -> { removeViewImmediateAndThen(view, callback); // Done animating, remove the transient view. mViewsToBeActuallyRemoved.remove(view); removeTransientView(view); }); if (callback != null) { callback.run(); } } }); } else { } else { // Without a controller, nobody will animate this view out, so it gets an unceremonious // Without a controller, nobody will animate this view out, so it gets an unceremonious // departure. // departure. removeViewImmediateAndThen(view, callback); super.removeView(view); if (callback != null) { callback.run(); } } } } } Loading Loading @@ -278,17 +282,18 @@ public class PhysicsAnimationLayout extends FrameLayout { } } /** /** * Animates the property of the child at the given index to the given value, then runs the * Animates the property of the given child view, then runs the callback provided when the * callback provided when the animation ends. * animation ends. */ */ protected void animateValueForChildAtIndex( protected void animateValueForChild( DynamicAnimation.ViewProperty property, DynamicAnimation.ViewProperty property, int index, View view, float value, float value, float startVel, float startVel, Runnable after) { Runnable after) { if (index < getChildCount()) { if (view != null) { final SpringAnimation animation = getAnimationAtIndex(property, index); final SpringAnimation animation = (SpringAnimation) view.getTag(getTagIdForProperty(property)); if (after != null) { if (after != null) { animation.addEndListener(new OneTimeEndListener() { animation.addEndListener(new OneTimeEndListener() { @Override @Override Loading @@ -300,12 +305,36 @@ public class PhysicsAnimationLayout extends FrameLayout { }); }); } } if (startVel != Float.MAX_VALUE) { animation.animateToFinalPosition(value); animation.setStartVelocity(startVel); } } } animation.animateToFinalPosition(value); protected void animateValueForChild( DynamicAnimation.ViewProperty property, View view, float value, Runnable after) { animateValueForChild(property, view, value, Float.MAX_VALUE, after); } } protected void animateValueForChild( DynamicAnimation.ViewProperty property, View view, float value) { animateValueForChild(property, view, value, Float.MAX_VALUE, /* after */ null); } /** * Animates the property of the child at the given index to the given value, then runs the * callback provided when the animation ends. */ protected void animateValueForChildAtIndex( DynamicAnimation.ViewProperty property, int index, float value, float startVel, Runnable after) { animateValueForChild(property, getChildAt(index), value, startVel, after); } } /** Shortcut to animate a value with a callback, but no start velocity. */ /** Shortcut to animate a value with a callback, but no start velocity. */ Loading Loading @@ -366,18 +395,6 @@ public class PhysicsAnimationLayout extends FrameLayout { } } } } /** Immediately removes the view, without notifying the controller, then runs the callback. */ private void removeViewImmediateAndThen(View view, Runnable callback) { super.removeView(view); if (callback != null) { callback.run(); } setChildrenVisibility(); } /** /** * Retrieves the animation of the given property from the view at the given index via the view * Retrieves the animation of the given property from the view at the given index via the view * tag system. * tag system. Loading @@ -401,7 +418,11 @@ public class PhysicsAnimationLayout extends FrameLayout { newAnim.addUpdateListener((animation, value, velocity) -> { newAnim.addUpdateListener((animation, value, velocity) -> { final int nextAnimInChain = final int nextAnimInChain = mController.getNextAnimationInChain(property, indexOfChild(child)); mController.getNextAnimationInChain(property, indexOfChild(child)); if (nextAnimInChain == PhysicsAnimationController.NONE) { // If the controller doesn't want us to chain, or if we're a transient view in the // process of being removed, don't chain. if (nextAnimInChain == PhysicsAnimationController.NONE || mTransientViews.contains(child)) { return; return; } } Loading @@ -412,9 +433,7 @@ public class PhysicsAnimationLayout extends FrameLayout { // If this property's animations should be chained, then check to see if there is a // If this property's animations should be chained, then check to see if there is a // subsequent animation within the rendering limit, and if so, tell it to animate to // subsequent animation within the rendering limit, and if so, tell it to animate to // this animation's new value (plus the offset). // this animation's new value (plus the offset). if (nextAnimInChain < Math.min( if (nextAnimInChain < Math.min(getChildCount(), mMaxRenderedChildren)) { getChildCount(), mMaxRenderedChildren + mViewsToBeActuallyRemoved.size())) { getAnimationAtIndex(property, animIndex + 1) getAnimationAtIndex(property, animIndex + 1) .animateToFinalPosition(value + offset); .animateToFinalPosition(value + offset); } else if (nextAnimInChain < getChildCount()) { } else if (nextAnimInChain < getChildCount()) { Loading Loading @@ -442,9 +461,7 @@ public class PhysicsAnimationLayout extends FrameLayout { // Ignore views that are animating out when calculating whether to hide the // Ignore views that are animating out when calculating whether to hide the // view. That is, if we're supposed to render 5 views, but 4 are animating out // view. That is, if we're supposed to render 5 views, but 4 are animating out // and will soon be removed, render up to 9 views temporarily. // and will soon be removed, render up to 9 views temporarily. i < (mMaxRenderedChildren + mViewsToBeActuallyRemoved.size()) i < mMaxRenderedChildren ? View.VISIBLE : View.GONE); ? View.VISIBLE : View.GONE); } } } } Loading
packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +16 −24 Original line number Original line Diff line number Diff line Loading @@ -53,8 +53,8 @@ public class StackAnimationController extends /** Scale factor to use initially for new bubbles being animated in. */ /** Scale factor to use initially for new bubbles being animated in. */ private static final float ANIMATE_IN_STARTING_SCALE = 1.15f; private static final float ANIMATE_IN_STARTING_SCALE = 1.15f; /** Translation factor (multiplied by stack offset) to use for new bubbles being animated in. */ /** Translation factor (multiplied by stack offset) to use for bubbles being animated in/out. */ private static final int ANIMATE_IN_TRANSLATION_FACTOR = 4; private static final int ANIMATE_TRANSLATION_FACTOR = 4; /** /** * Values to use for the default {@link SpringForce} provided to the physics animation layout. * Values to use for the default {@link SpringForce} provided to the physics animation layout. Loading Loading @@ -309,7 +309,7 @@ public class StackAnimationController extends // animate in from this position. Since the animations are chained, when the new bubble // animate in from this position. Since the animations are chained, when the new bubble // flies in from the side, it will push the other ones out of the way. // flies in from the side, it will push the other ones out of the way. float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); child.setTranslationX(mStackPosition.x - (ANIMATE_IN_TRANSLATION_FACTOR * xOffset)); child.setTranslationX(mStackPosition.x - ANIMATE_TRANSLATION_FACTOR * xOffset); mLayout.animateValueForChildAtIndex( mLayout.animateValueForChildAtIndex( DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x); DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x); } } Loading @@ -318,27 +318,19 @@ public class StackAnimationController extends @Override @Override void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { // Animate the child out, actually removing it once its alpha is zero. // Animate the child out, actually removing it once its alpha is zero. mLayout.animateValueForChildAtIndex( mLayout.animateValueForChild( DynamicAnimation.ALPHA, index, 0f, () -> { DynamicAnimation.ALPHA, child, 0f, actuallyRemove); actuallyRemove.run(); mLayout.animateValueForChild(DynamicAnimation.SCALE_X, child, ANIMATE_IN_STARTING_SCALE); }); mLayout.animateValueForChild(DynamicAnimation.SCALE_Y, child, ANIMATE_IN_STARTING_SCALE); mLayout.animateValueForChildAtIndex( DynamicAnimation.SCALE_X, index, ANIMATE_IN_STARTING_SCALE); mLayout.animateValueForChildAtIndex( DynamicAnimation.SCALE_Y, index, ANIMATE_IN_STARTING_SCALE); final boolean hasPrecedingChild = index + 1 < mLayout.getChildCount(); // Animate the removing view in the opposite direction of the stack. if (hasPrecedingChild) { final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); final int precedingViewIndex = mLayout.getPrecedingNonRemovedViewIndex(index); mLayout.animateValueForChild(DynamicAnimation.TRANSLATION_X, child, if (precedingViewIndex >= 0) { mStackPosition.x - (-xOffset * ANIMATE_TRANSLATION_FACTOR)); final float offsetX = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); // Pull the top of the stack to the correct position, the chained animations will instruct mLayout.animatePositionForChildAtIndex( // any children that are out of place to animate to the correct position. precedingViewIndex, mLayout.animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x); mStackPosition.x + (index * offsetX), mStackPosition.y); } } } } /** Moves the stack, without any animation, to the starting position. */ /** Moves the stack, without any animation, to the starting position. */ Loading
packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java +2 −24 Original line number Original line Diff line number Diff line Loading @@ -75,7 +75,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { } } @Test @Test public void testRenderVisibility() { public void testRenderVisibility() throws InterruptedException { mLayout.setController(mTestableController); mLayout.setController(mTestableController); addOneMoreThanRenderLimitBubbles(); addOneMoreThanRenderLimitBubbles(); Loading @@ -87,7 +87,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { } } @Test @Test public void testHierarchyChanges() { public void testHierarchyChanges() throws InterruptedException { mLayout.setController(mTestableController); mLayout.setController(mTestableController); addOneMoreThanRenderLimitBubbles(); addOneMoreThanRenderLimitBubbles(); Loading Loading @@ -241,26 +241,6 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { endListenerCalls.verifyNoMoreInteractions(); endListenerCalls.verifyNoMoreInteractions(); } } @Test public void testPrecedingNonRemovedIndex() { mLayout.setController(mTestableController); addOneMoreThanRenderLimitBubbles(); // Call removeView at index 4, but don't actually remove it yet (as if we're animating it // out). The preceding, non-removed view index to 3 should initially be 4, but then 5 since // 4 is on its way out. assertEquals(4, mLayout.getPrecedingNonRemovedViewIndex(3)); mLayout.removeView(mViews.get(4)); assertEquals(5, mLayout.getPrecedingNonRemovedViewIndex(3)); // Call removeView at index 1, and actually remove it immediately. With the old view at 1 // instantly gone, the preceding view to 0 should be 1 in both cases. assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0)); mTestableController.setRemoveImmediately(true); mLayout.removeView(mViews.get(1)); assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0)); } @Test @Test public void testSetController() throws InterruptedException { public void testSetController() throws InterruptedException { // Add the bubbles, then set the controller, to make sure that a controller added to an // Add the bubbles, then set the controller, to make sure that a controller added to an Loading Loading @@ -360,8 +340,6 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { mLayout.cancelAllAnimations(); mLayout.cancelAllAnimations(); waitForLayoutMessageQueue(); // Animations should be somewhere before their end point. // Animations should be somewhere before their end point. assertTrue(mViews.get(0).getTranslationX() < 1000); assertTrue(mViews.get(0).getTranslationX() < 1000); assertTrue(mViews.get(0).getTranslationY() < 1000); assertTrue(mViews.get(0).getTranslationY() < 1000); Loading
packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java +30 −7 Original line number Original line Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Looper; import android.view.DisplayCutout; import android.view.DisplayCutout; import android.view.View; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowInsets; import android.widget.FrameLayout; import android.widget.FrameLayout; Loading Loading @@ -93,7 +94,7 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { } } /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */ /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */ void addOneMoreThanRenderLimitBubbles() { void addOneMoreThanRenderLimitBubbles() throws InterruptedException { for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { final View newView = new FrameLayout(mContext); final View newView = new FrameLayout(mContext); mLayout.addView(newView, 0); mLayout.addView(newView, 0); Loading Loading @@ -129,7 +130,7 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { void waitForLayoutMessageQueue() throws InterruptedException { void waitForLayoutMessageQueue() throws InterruptedException { // Wait for layout, then the view should be actually removed. // Wait for layout, then the view should be actually removed. CountDownLatch layoutLatch = new CountDownLatch(1); CountDownLatch layoutLatch = new CountDownLatch(1); mLayout.post(layoutLatch::countDown); mMainThreadHandler.post(layoutLatch::countDown); layoutLatch.await(1, TimeUnit.SECONDS); layoutLatch.await(1, TimeUnit.SECONDS); } } Loading @@ -145,11 +146,7 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { @Override @Override public void setController(PhysicsAnimationController controller) { public void setController(PhysicsAnimationController controller) { mMainThreadHandler.post(() -> super.setController(controller)); mMainThreadHandler.post(() -> super.setController(controller)); try { waitForMessageQueueAndIgnoreIfInterrupted(); waitForLayoutMessageQueue(); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override @Override Loading @@ -169,6 +166,32 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { return mWindowInsets; return mWindowInsets; } } @Override public void removeView(View view) { mMainThreadHandler.post(() -> super.removeView(view)); waitForMessageQueueAndIgnoreIfInterrupted(); } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { mMainThreadHandler.post(() -> super.addView(child, index, params)); waitForMessageQueueAndIgnoreIfInterrupted(); } /** * Wait for the queue but just catch and print the exception if interrupted, since we can't * just add the exception to the overridden methods' signatures. */ private void waitForMessageQueueAndIgnoreIfInterrupted() { try { waitForLayoutMessageQueue(); } catch (InterruptedException e) { e.printStackTrace(); } } /** /** * Sets an end listener that will be called after the 'real' end listener that was already * Sets an end listener that will be called after the 'real' end listener that was already * set. * set. Loading