From b1a796b155cf6f98395f33876f5ff14b1f29f0b8 Mon Sep 17 00:00:00 2001 From: Joshua Tsuji Date: Wed, 16 Jan 2019 15:43:12 -0800 Subject: [PATCH] Adds DynamicAnimation-based movement to the bubbles. See go/bubble-stack-design for a high level overview of these changes. This is a large CL, but required in order to allow continued development and team testing without breaking functionality over the course of multiple CL submissions. To integrate the new animations, the following changes have been made to existing code: * (BubbleStackView) The bubble container (and thus, the stack view) are MATCH_PARENT to allow the bubbles to independently translate anywhere on the screen. * (BubbleStackView) Start position is set by the stack controller, not BubbleStackView. * (BubbleStackView) Expand positon is set by the expansion controller, not BubbleStackView. * (BubbleStackView/BubbleTouchHandler) Added the methods onDragStart/onDragged/onDragFinish, and onBubbleDragStart/onBubbleDragged/onBubbleDragFinish, for cleaner dispatch of touch events to the appropriate animation controller. * (BubbleStackView/BubbleController) The stack view's getBoundsOnScreen returns the first bubble's bounds, if the stack is not expanded. * (BubbleStackView) applyCurrentState no longer manages translation of bubbles, or the expanded view, these are controlled by animation. * (BubbleMovementHelper) Deleted, no longer needed. * (Everywhere) Changed uses of Point to PointF, since translation values are floats anyway. Known issues to be fixed in subsequent, far smaller CLs: * (b/123022862) Bubble dragging out/dismissing is not animated, and the bubbles can be deposited anywhere. Tap outside the stack to collapse them back to normal. * (b/123023502) New bubbles added while the stack is expanded are not positioned properly. * (b/123022982) Expanded view arrow is sometimes in the wrong position. * (b/123023410) If the stack is expanded while animating, it collapses to its original position even if not along the edge of the screen. * (b/123023904) The expanded view doesn't animate out, it disappears instantly. * (b/123026584) Bounds in landscape are a bit wonky. Bug: 111236845 Test: atest SystemUITests Test: physics-animation-testing.md Change-Id: Icaca09e5db89c635c9bb7ca82d7d2714362e344e --- packages/SystemUI/Android.bp | 2 + ...hysics-animation-layout-config-methods.png | Bin 0 -> 106340 bytes ...ysics-animation-layout-control-methods.png | Bin 0 -> 70507 bytes .../SystemUI/docs/physics-animation-layout.md | 56 ++ .../docs/physics-animation-testing.md | 11 + packages/SystemUI/res/layout/bubble_view.xml | 4 +- packages/SystemUI/res/values/dimens.xml | 14 +- packages/SystemUI/res/values/ids.xml | 8 + packages/SystemUI/res/values/integers.xml | 6 + .../systemui/bubbles/BadgedImageView.java | 2 +- .../systemui/bubbles/BubbleController.java | 34 +- .../bubbles/BubbleMovementHelper.java | 326 ------------ .../systemui/bubbles/BubbleStackView.java | 392 +++++++++----- .../systemui/bubbles/BubbleTouchHandler.java | 69 ++- .../android/systemui/bubbles/BubbleView.java | 12 +- .../ExpandedAnimationController.java | 157 ++++++ .../bubbles/animation/OneTimeEndListener.java | 34 ++ .../animation/PhysicsAnimationLayout.java | 496 ++++++++++++++++++ .../animation/StackAnimationController.java | 447 ++++++++++++++++ .../bubbles/BubbleControllerTest.java | 2 +- .../ExpandedAnimationControllerTest.java | 103 ++++ .../animation/PhysicsAnimationLayoutTest.java | 471 +++++++++++++++++ .../PhysicsAnimationLayoutTestCase.java | 190 +++++++ .../StackAnimationControllerTest.java | 221 ++++++++ 24 files changed, 2504 insertions(+), 553 deletions(-) create mode 100644 packages/SystemUI/docs/physics-animation-layout-config-methods.png create mode 100644 packages/SystemUI/docs/physics-animation-layout-control-methods.png create mode 100644 packages/SystemUI/docs/physics-animation-layout.md create mode 100644 packages/SystemUI/docs/physics-animation-testing.md delete mode 100644 packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java create mode 100644 packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java create mode 100644 packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java create mode 100644 packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java create mode 100644 packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 8be67d9a7a51d..50a069d11de43 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -57,6 +57,7 @@ android_library { "androidx.slice_slice-builders", "androidx.arch.core_core-runtime", "androidx.lifecycle_lifecycle-extensions", + "androidx.dynamicanimation_dynamicanimation", "SystemUI-tags", "SystemUI-proto", "dagger2-2.19", @@ -108,6 +109,7 @@ android_library { "androidx.slice_slice-builders", "androidx.arch.core_core-runtime", "androidx.lifecycle_lifecycle-extensions", + "androidx.dynamicanimation_dynamicanimation", "SystemUI-tags", "SystemUI-proto", "metrics-helper-lib", diff --git a/packages/SystemUI/docs/physics-animation-layout-config-methods.png b/packages/SystemUI/docs/physics-animation-layout-config-methods.png new file mode 100644 index 0000000000000000000000000000000000000000..c3a45e294e797fb8ca270c203888e42a1c38055b GIT binary patch literal 106340 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 literal 0 HcmV?d00001 diff --git a/packages/SystemUI/docs/physics-animation-layout-control-methods.png b/packages/SystemUI/docs/physics-animation-layout-control-methods.png new file mode 100644 index 0000000000000000000000000000000000000000..e77c676bc13f35566acc1d783dce42d24f05ca8e GIT binary patch literal 70507 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

jHoFj&s3`xIl-8?BA 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 XEj`oXal}A`2Yl?UNmh>tl*oSq?#oL9 literal 0 HcmV?d00001 diff --git a/packages/SystemUI/docs/physics-animation-layout.md b/packages/SystemUI/docs/physics-animation-layout.md new file mode 100644 index 0000000000000..a67b5e873b2ee --- /dev/null +++ b/packages/SystemUI/docs/physics-animation-layout.md @@ -0,0 +1,56 @@ +# Physics Animation Layout + +## Overview +**PhysicsAnimationLayout** works with an implementation of **PhysicsAnimationController** to construct and maintain physics animations for each of its child views. During the initial construction of the animations, the layout queries the controller for configuration settings such as which properties to animate, which animations to chain together, and what stiffness or bounciness to use. Once the animations are built to the controller’s specifications, the controller can then ask the layout to start, stop and manipulate them arbitrarily to achieve any desired animation effect. The controller is notified whenever children are added or removed from the layout, so that it can animate their entrance or exit, respectively. + +An example usage is Bubbles, which uses a PhysicsAnimationLayout for its stack of bubbles. Bubbles has controller subclasses including StackAnimationController and ExpansionAnimationController. StackAnimationController tells the layout to configure the translation animations to be chained (for the ‘following’ drag effect), and has methods such as ```moveStack(x, y)``` to animate the stack to a given point. ExpansionAnimationController asks for no animations to be chained, and exposes methods like ```expandStack()``` and ```collapseStack()```, which animate the bubbles to positions along the bottom of the screen. + +## PhysicsAnimationController +PhysicsAnimationController is a public abstract class in PhysicsAnimationLayout. Controller instances must override configuration methods, which are used by the layout while constructing the animations, and animation control methods, which are called to initiate animations in response to events. + +### Configuration Methods +![Diagram of how animations are configured using the controller's configuration methods.](physics-animation-layout-config-methods.png) +The controller must override the following methods: + +```Set getAnimatedProperties()``` +Returns the properties, such as TRANSLATION_X and TRANSLATION_Y, for which the layout should construct physics animations. + +```int getNextAnimationInChain(ViewProperty property, int index)``` +If the animation at the given index should update another animation whenever its value changes, return the index of the other animation. Otherwise, return NONE. This is used to chain animations together, so that when one animation moves, the other ‘follows’ closely behind. + +```float getOffsetForChainedPropertyAnimation(ViewProperty property)``` +Value to add every time chained animations update the subsequent animation in the chain. For example, returning TRANSLATION_X offset = 20px means that if the first animation in the chain is animated to 10px, the second will update to 30px, the third to 50px, etc. + +```SpringForce getSpringForce(ViewProperty property)``` +Returns a SpringForce instance to use for animations of the given property. This allows the controller to configure stiffness and bounciness values. Since the physics animations internally use SpringForce instances to hold inflight animation values, this method needs to return a new SpringForce instance each time - no constants allowed. + +### Animation Control Methods +![Diagram of how calls to animateValueForChildAtIndex dispatch to DynamicAnimations.](physics-animation-layout-control-methods.png) +Once the layout has used the controller’s configuration properties to build the animations, the controller can use them to actually run animations. This is done for two reasons - reacting to a view being added or removed, or responding to another class (such as a touch handler or broadcast receiver) requesting an animation. ```onChildAdded``` and ```onChildRemoved``` are called automatically by the layout, giving the controller the opportunity to animate the child in/out. Custom methods are called by anyone with access to the controller instance to do things like expand, collapse, or move the child views. + +In either case, the controller has access to the layout’s protected ```animateValueForChildAtIndex(ViewProperty property, int index, float value)``` method. This method is used to actually run an animation. + +For example, moving the first child view to *(100, 200)*: + +``` +animateValueForChildAtIndex(TRANSLATION_X, 0, 100); +animateValueForChildAtIndex(TRANSLATION_Y, 0, 200); +``` + +This would use the physics animations constructed by the layout to spring the view to *(100, 200)*. + +If the controller’s ```getNextAnimationInChain``` method set up the first child’s TRANSLATION_X/Y animations to be chained to the second child’s, this would result in the second child also springing towards (100, 200), plus any offset returned by ```getOffsetForChainedPropertyAnimation```. + +## PhysicsAnimationLayout +The layout itself is a FrameLayout descendant with a few extra methods: + +```setController(PhysicsAnimationController controller)``` +Attaches the layout to the controller, so that the controller can access the layout’s protected methods. It also constructs or reconfigures the physics animations according to the new controller’s configuration methods. + +```setEndListenerForProperty(ViewProperty property, AnimationEndListener endListener)``` +Sets an end listener that is called when all animations on the given property have ended. + +```setMaxRenderedChildren(int max)``` +Child views beyond this limit will be set to GONE, and won't be animated, for performance reasons. Defaults to **5**. + +It has one protected method, ```animateValueForChildAtIndex(ViewProperty property, int index, float value)```, which is visible to PhysicsAnimationController descendants. This method dispatches the given value to the appropriate animation. \ No newline at end of file diff --git a/packages/SystemUI/docs/physics-animation-testing.md b/packages/SystemUI/docs/physics-animation-testing.md new file mode 100644 index 0000000000000..47354d45fa331 --- /dev/null +++ b/packages/SystemUI/docs/physics-animation-testing.md @@ -0,0 +1,11 @@ +# Physics Animation Testing +Physics animations are notoriously difficult to test, since they’re essentially small simulations. They have no set duration, and they’re considered ‘finished’ only when the movements imparted by the animation are too small to be user-visible. Mid-states are not deterministic. + +For this reason, we only test the end state of animations. Manual testing should be sufficient to reveal flaws in the en-route animation visuals. In a worst-case failure case, as long as the end state is correct, usability will not be affected - animations might just look a bit off until the UI elements settle to their proper positions. + +## Waiting for Animations to End +Testing any kind of animation can be tricky, since animations need to run on the main thread, and they’re asynchronous - the test has to wait for the animation to finish before we can assert anything about its end state. For normal animations, we can invoke skipToEnd to avoid waiting. While this method is available for SpringAnimation, it’s not available for FlingAnimation since its end state is not initially known. A FlingAnimation’s ‘end’ is when the friction simulation reports that motion has slowed to an invisible level. For this reason, we have to actually run the physics animations. + +To accommodate this, all tests of the layout itself, as well as any animation controller subclasses, use **PhysicsAnimationLayoutTestCase**. The layout provided to controllers by the test case is a **TestablePhysicsAnimationLayout**, a subclass of PhysicsAnimationLayout whose animation-related methods have been overridden to force them to run on the main thread via a Handler. Animations will simply crash if they’re called directly from the test thread, so this is important. + +The test case also provides ```waitForPropertyAnimations```, which uses a **CountDownLatch** to wait for all animations on a given property to complete before continuing the test. This works since the test is not running on the same thread as the animation, so a blocking call to ```latch.await()``` does not affect the animations’ progress. The latch is initialized with a count equal to the number of properties we’re listening to. We then add end listeners to the layout for each property, which call ```latch.countDown()```. Once all of the properties’ animations have completed, the latch count reaches zero and the test’s call to ```await()``` returns, with the animations complete. \ No newline at end of file diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml index 204408cda81fc..13186fc6437c6 100644 --- a/packages/SystemUI/res/layout/bubble_view.xml +++ b/packages/SystemUI/res/layout/bubble_view.xml @@ -22,8 +22,8 @@ diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ab0bbe10c37c3..3caa9682c59e8 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -982,8 +982,8 @@ 0dp 8dp - - 56dp + + 56dp 16dp @@ -1000,10 +1000,20 @@ 48dp 24dp + + 100dp + + 500dp 144dp 32dp 4dp + + 5dp + + 5dp + + 100dp diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index bd34beac7fd67..2993d5c86813d 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -115,6 +115,14 @@ + + + + + + + + diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml index fd7a10500f36e..e8fabf5a07f10 100644 --- a/packages/SystemUI/res/values/integers.xml +++ b/packages/SystemUI/res/values/integers.xml @@ -21,4 +21,10 @@ 0) as we can allow the carrier text to stretch as far as needed in the QS footer. --> -2 0 + + + 5 + \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java index 92d3cc1ae34f7..36a813b914d55 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java @@ -57,7 +57,7 @@ public class BadgedImageView extends ImageView { int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); setScaleType(ScaleType.CENTER_CROP); - mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_size); + mIconSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size); mDotRenderer = new BadgeRenderer(mIconSize); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index a457deed7ba4c..b7bee30dc6402 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -18,9 +18,8 @@ package com.android.systemui.bubbles; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.notification.NotificationAlertingManager.alertAgain; @@ -229,10 +228,6 @@ public class BubbleController { } mStackView.stackDismissed(); - // Reset the position of the stack (TODO - or should we save / respect last user position?) - Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize); - mStackView.setPosition(startPoint.x, startPoint.y); - updateVisibility(); mNotificationEntryManager.updateNotifications(); } @@ -249,16 +244,14 @@ public class BubbleController { BubbleView bubble = mBubbles.get(notif.key); mStackView.updateBubble(bubble, notif, updatePosition); } else { - boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE; if (mStackView == null) { - setPosition = true; mStackView = new BubbleStackView(mContext); ViewGroup sbv = mStatusBarWindowController.getStatusBarView(); // XXX: Bug when you expand the shade on top of expanded bubble, there is no scrim // between bubble and the shade int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1; sbv.addView(mStackView, bubblePosition, - new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); } @@ -273,11 +266,6 @@ public class BubbleController { } mBubbles.put(bubble.getKey(), bubble); mStackView.addBubble(bubble); - if (setPosition) { - // Need to add the bubble to the stack before we can know the width - Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize); - mStackView.setPosition(startPoint.x, startPoint.y); - } } updateVisibility(); } @@ -423,24 +411,6 @@ public class BubbleController { return mStackView; } - // TODO: factor in PIP location / maybe last place user had it - /** - * Gets an appropriate starting point to position the bubble stack. - */ - private static Point getStartPoint(int size, Point displaySize) { - final int x = displaySize.x - size + EDGE_OVERLAP; - final int y = displaySize.y / 4; - return new Point(x, y); - } - - /** - * Gets an appropriate position for the bubble when the stack is expanded. - */ - static Point getExpandPoint(BubbleStackView view, int size, Point displaySize) { - // Same place for now.. - return new Point(EDGE_OVERLAP, size); - } - /** * Whether the notification has been developer configured to bubble and is allowed by the user. */ diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java deleted file mode 100644 index c1063fa54ff2e..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.bubbles; - -import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN; - -import android.animation.Animator.AnimatorListener; -import android.animation.AnimatorSet; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Point; -import android.view.View; -import android.view.WindowManager; - -import com.android.systemui.bubbles.BubbleTouchHandler.FloatingView; - -import java.util.Arrays; - -/** - * Math and animators to move bubbles around the screen. - * - * TODO: straight up copy paste from old prototype -- consider physics, see if bubble & pip - * movements can be unified maybe? - */ -public class BubbleMovementHelper { - - private static final int MAGNET_ANIM_TIME = 150; - public static final int EDGE_OVERLAP = 0; - - private Context mContext; - private Point mDisplaySize; - - public BubbleMovementHelper(Context context) { - mContext = context; - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - mDisplaySize = new Point(); - wm.getDefaultDisplay().getSize(mDisplaySize); - } - - /** - * @return the distance between the two provided points. - */ - static double distance(Point p1, Point p2) { - return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); - } - - /** - * @return the y value of a line defined by y = mx+b - */ - static float findY(float m, float b, float x) { - return (m * x) + b; - } - - /** - * @return the x value of a line defined by y = mx+b - */ - static float findX(float m, float b, float y) { - return (y - b) / m; - } - - /** - * Determines a point on the edge of the screen based on the velocity and position. - */ - public Point getPointOnEdge(View bv, Point p, float velX, float velY) { - // Find the slope and the y-intercept - velX = velX == 0 ? 1 : velX; - final float m = velY / velX; - final float b = p.y - m * p.x; - - // There are two lines it can intersect, find the two points - Point pointHoriz = new Point(); - Point pointVert = new Point(); - - if (velX > 0) { - // right - pointHoriz.x = mDisplaySize.x; - pointHoriz.y = (int) findY(m, b, mDisplaySize.x); - } else { - // left - pointHoriz.x = EDGE_OVERLAP; - pointHoriz.y = (int) findY(m, b, 0); - } - if (velY > 0) { - // bottom - pointVert.x = (int) findX(m, b, mDisplaySize.y); - pointVert.y = mDisplaySize.y - getNavBarHeight(); - } else { - // top - pointVert.x = (int) findX(m, b, 0); - pointVert.y = EDGE_OVERLAP; - } - - // Use the point that's closest to the start position - final double distanceToVertPoint = distance(p, pointVert); - final double distanceToHorizPoint = distance(p, pointHoriz); - boolean useVert = distanceToVertPoint < distanceToHorizPoint; - // Check if we're being flung along the current edge, use opposite point in this case - // XXX: on*Edge methods should actually use 'down' position of view and compare 'up' but - // this works well enough for now - if (onSideEdge(bv, p) && Math.abs(velY) > Math.abs(velX)) { - // Flinging along left or right edge, favor vert edge - useVert = true; - - } else if (onTopBotEdge(bv, p) && Math.abs(velX) > Math.abs(velY)) { - // Flinging along top or bottom edge - useVert = false; - } - - if (useVert) { - pointVert.x = capX(pointVert.x, bv); - pointVert.y = capY(pointVert.y, bv); - return pointVert; - - } - pointHoriz.x = capX(pointHoriz.x, bv); - pointHoriz.y = capY(pointHoriz.y, bv); - return pointHoriz; - } - - /** - * @return whether the view is on a side edge of the screen (i.e. left or right). - */ - public boolean onSideEdge(View fv, Point p) { - return p.x + fv.getWidth() + EDGE_OVERLAP <= mDisplaySize.x - - EDGE_OVERLAP - || p.x >= EDGE_OVERLAP; - } - - /** - * @return whether the view is on a top or bottom edge of the screen. - */ - public boolean onTopBotEdge(View bv, Point p) { - return p.y >= getStatusBarHeight() + EDGE_OVERLAP - || p.y + bv.getHeight() + EDGE_OVERLAP <= mDisplaySize.y - - EDGE_OVERLAP; - } - - /** - * @return constrained x value based on screen size and how much a view can overlap with a side - * edge. - */ - public int capX(float x, View bv) { - // Floating things can't stick to top or bottom edges, so figure out if it's closer to - // left or right and just use that side + the overlap. - final float centerX = x + bv.getWidth() / 2; - if (centerX > mDisplaySize.x / 2) { - // Right side - return mDisplaySize.x - bv.getWidth() - EDGE_OVERLAP; - } else { - // Left side - return EDGE_OVERLAP; - } - } - - /** - * @return constrained y value based on screen size and how much a view can overlap with a top - * or bottom edge. - */ - public int capY(float y, View bv) { - final int height = bv.getHeight(); - if (y < getStatusBarHeight() + EDGE_OVERLAP) { - return getStatusBarHeight() + EDGE_OVERLAP; - } - if (y + height + EDGE_OVERLAP > mDisplaySize.y - EDGE_OVERLAP) { - return mDisplaySize.y - height - EDGE_OVERLAP; - } - return (int) y; - } - - /** - * Animation to translate the provided view. - */ - public AnimatorSet animateMagnetTo(final BubbleStackView bv) { - Point pos = bv.getPosition(); - - // Find the distance to each edge - final int leftDistance = pos.x; - final int rightDistance = mDisplaySize.x - leftDistance; - final int topDistance = pos.y; - final int botDistance = mDisplaySize.y - topDistance; - - int smallest; - // Find the closest one - int[] distances = { - leftDistance, rightDistance, topDistance, botDistance - }; - Arrays.sort(distances); - smallest = distances[0]; - - // Animate to the closest edge - Point p = new Point(); - if (smallest == leftDistance) { - p.x = capX(EDGE_OVERLAP, bv); - p.y = capY(topDistance, bv); - } - if (smallest == rightDistance) { - p.x = capX(mDisplaySize.x, bv); - p.y = capY(topDistance, bv); - } - if (smallest == topDistance) { - p.x = capX(leftDistance, bv); - p.y = capY(0, bv); - } - if (smallest == botDistance) { - p.x = capX(leftDistance, bv); - p.y = capY(mDisplaySize.y, bv); - } - return getTranslateAnim(bv, p, MAGNET_ANIM_TIME); - } - - /** - * Animation to fling the provided view. - */ - public AnimatorSet animateFlingTo(final BubbleStackView bv, float velX, float velY) { - Point pos = bv.getPosition(); - Point endPos = getPointOnEdge(bv, pos, velX, velY); - endPos = new Point(capX(endPos.x, bv), capY(endPos.y, bv)); - final double distance = Math.sqrt(Math.pow(endPos.x - pos.x, 2) - + Math.pow(endPos.y - pos.y, 2)); - final float sumVel = Math.abs(velX) + Math.abs(velY); - final int duration = Math.max(Math.min(200, (int) (distance * 1000f / (sumVel / 2))), 50); - return getTranslateAnim(bv, endPos, duration); - } - - /** - * Animation to translate the provided view. - */ - public AnimatorSet getTranslateAnim(final FloatingView v, Point p, int duration) { - return getTranslateAnim(v, p, duration, 0); - } - - /** - * Animation to translate the provided view. - */ - public AnimatorSet getTranslateAnim(final FloatingView v, Point p, - int duration, int startDelay) { - return getTranslateAnim(v, p, duration, startDelay, null); - } - - /** - * Animation to translate the provided view. - * - * @param v the view to translate. - * @param p the point to translate to. - * @param duration the duration of the animation. - * @param startDelay the start delay of the animation. - * @param listener the listener to add to the animation. - * - * @return the animation. - */ - public static AnimatorSet getTranslateAnim(final FloatingView v, Point p, int duration, - int startDelay, AnimatorListener listener) { - Point curPos = v.getPosition(); - final ValueAnimator animX = ValueAnimator.ofFloat(curPos.x, p.x); - animX.setDuration(duration); - animX.setStartDelay(startDelay); - animX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (float) animation.getAnimatedValue(); - v.setPositionX((int) value); - } - }); - - final ValueAnimator animY = ValueAnimator.ofFloat(curPos.y, p.y); - animY.setDuration(duration); - animY.setStartDelay(startDelay); - animY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (float) animation.getAnimatedValue(); - v.setPositionY((int) value); - } - }); - if (listener != null) { - animY.addListener(listener); - } - - AnimatorSet set = new AnimatorSet(); - set.playTogether(animX, animY); - set.setInterpolator(FAST_OUT_SLOW_IN); - return set; - } - - - // TODO -- now that this is in system we should be able to get these better, but ultimately - // makes more sense to move to movement bounds style a la PIP - /** - * Returns the status bar height. - */ - public int getStatusBarHeight() { - Resources res = mContext.getResources(); - int resourceId = res.getIdentifier("status_bar_height", "dimen", "android"); - if (resourceId > 0) { - return res.getDimensionPixelSize(resourceId); - } - return 0; - } - - /** - * Returns the status bar height. - */ - public int getNavBarHeight() { - Resources res = mContext.getResources(); - int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android"); - if (resourceId > 0) { - return res.getDimensionPixelSize(resourceId); - } - return 0; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index dcd121bdb239a..8bf35cde7d625 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -16,59 +16,88 @@ package com.android.systemui.bubbles; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.app.ActivityView; import android.app.PendingIntent; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.view.ViewPropertyAnimator; import android.view.ViewTreeObserver; import android.view.WindowManager; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.OvershootInterpolator; import android.widget.FrameLayout; import android.widget.LinearLayout; import androidx.annotation.Nullable; +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.R; +import com.android.systemui.bubbles.animation.ExpandedAnimationController; +import com.android.systemui.bubbles.animation.PhysicsAnimationLayout; +import com.android.systemui.bubbles.animation.StackAnimationController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; -import com.android.systemui.statusbar.notification.stack.ViewState; /** * Renders bubbles in a stack and handles animating expanded and collapsed states. */ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.FloatingView { - private static final String TAG = "BubbleStackView"; + + /** + * 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 + * to the side without needing a spring. + */ + private static final float FLING_FRICTION_X = 1.15f; + private static final float FLING_FRICTION_Y = 1.5f; + + /** + * Damping ratio to use for the stack spring animation used to spring the stack to its final + * position after a fling. + */ + private static final float SPRING_DAMPING_RATIO = 0.85f; + + /** + * Minimum fling velocity required to trigger moving the stack from one side of the screen to + * the other. + */ + private static final float ESCAPE_VELOCITY = 750f; + private Point mDisplaySize; - private FrameLayout mBubbleContainer; + private final SpringAnimation mExpandedViewXAnim; + private final SpringAnimation mExpandedViewYAnim; + + private PhysicsAnimationLayout mBubbleContainer; + private StackAnimationController mStackAnimationController; + private ExpandedAnimationController mExpandedAnimationController; + private BubbleExpandedViewContainer mExpandedViewContainer; private int mBubbleSize; private int mBubblePadding; + private int mExpandedAnimateXDistance; + private int mExpandedAnimateYDistance; private boolean mIsExpanded; private int mExpandedBubbleHeight; private BubbleTouchHandler mTouchHandler; private BubbleView mExpandedBubble; - private Point mCollapsedPosition; private BubbleController.BubbleExpandListener mExpandListener; private boolean mViewUpdatedRequested = false; @@ -110,8 +139,12 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F setOnTouchListener(mTouchHandler); Resources res = getResources(); - mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size); + mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mExpandedAnimateXDistance = + res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance); + mExpandedAnimateYDistance = + res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_y_distance); mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height); mDisplaySize = new Point(); @@ -120,6 +153,19 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F int padding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding); int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); + + mStackAnimationController = new StackAnimationController(); + mExpandedAnimationController = new ExpandedAnimationController(); + + mBubbleContainer = new PhysicsAnimationLayout(context); + mBubbleContainer.setMaxRenderedChildren( + getResources().getInteger(R.integer.bubbles_max_rendered)); + mBubbleContainer.setController(mStackAnimationController); + mBubbleContainer.setElevation(elevation); + mBubbleContainer.setPadding(padding, 0, padding, 0); + mBubbleContainer.setClipChildren(false); + addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + mExpandedViewContainer = (BubbleExpandedViewContainer) LayoutInflater.from(context).inflate(R.layout.bubble_expanded_view, this /* parent */, false /* attachToRoot */); @@ -128,11 +174,19 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mExpandedViewContainer.setClipChildren(false); addView(mExpandedViewContainer); - mBubbleContainer = new FrameLayout(context); - mBubbleContainer.setElevation(elevation); - mBubbleContainer.setPadding(padding, 0, padding, 0); - mBubbleContainer.setClipChildren(false); - addView(mBubbleContainer); + mExpandedViewXAnim = + new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X); + mExpandedViewXAnim.setSpring( + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); + + mExpandedViewYAnim = + new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_Y); + mExpandedViewYAnim.setSpring( + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); setClipChildren(false); } @@ -143,38 +197,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F getViewTreeObserver().removeOnPreDrawListener(mViewUpdater); } - @Override - public void onMeasure(int widthSpec, int heightSpec) { - super.onMeasure(widthSpec, heightSpec); - - int bubbleHeightSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec), - MeasureSpec.UNSPECIFIED); - if (mIsExpanded) { - ViewGroup parent = (ViewGroup) getParent(); - int parentWidth = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(parent.getWidth()), MeasureSpec.EXACTLY); - int parentHeight = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(parent.getHeight()), MeasureSpec.EXACTLY); - measureChild(mBubbleContainer, parentWidth, bubbleHeightSpec); - - int expandedViewHeight = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec), - MeasureSpec.UNSPECIFIED); - measureChild(mExpandedViewContainer, parentWidth, expandedViewHeight); - setMeasuredDimension(widthSpec, parentHeight); - } else { - // Not expanded - measureChild(mExpandedViewContainer, 0, 0); - - // Bubbles are translated a little to stack on top of each other - widthSpec = MeasureSpec.makeMeasureSpec(getStackWidth(), MeasureSpec.EXACTLY); - measureChild(mBubbleContainer, widthSpec, bubbleHeightSpec); - - heightSpec = MeasureSpec.makeMeasureSpec(mBubbleContainer.getMeasuredHeight(), - MeasureSpec.EXACTLY); - setMeasuredDimension(widthSpec, heightSpec); - } - } - @Override public boolean onInterceptTouchEvent(MotionEvent ev) { float x = ev.getRawX(); @@ -293,9 +315,11 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F boolean updatePosition) { bubbleView.update(entry); if (updatePosition && !mIsExpanded) { - // If alerting it gets promoted to top of the stack - mBubbleContainer.removeView(bubbleView); - mBubbleContainer.addView(bubbleView, 0); + // If alerting it gets promoted to top of the stack. + if (mBubbleContainer.indexOfChild(bubbleView) != 0) { + mBubbleContainer.removeViewAndThen(bubbleView, + () -> mBubbleContainer.addView(bubbleView, 0)); + } requestUpdate(); } if (mIsExpanded && bubbleView.equals(mExpandedBubble)) { @@ -359,36 +383,51 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F if (mIsExpanded != shouldExpand) { mIsExpanded = shouldExpand; updateExpandedBubble(); + applyCurrentState(); + //requestUpdate(); + + mIsAnimating = true; + + Runnable updateAfter = () -> { + applyCurrentState(); + mIsAnimating = false; + requestUpdate(); + }; if (shouldExpand) { - // Save current position so that we might return there - savePosition(); + mBubbleContainer.setController(mExpandedAnimationController); + mExpandedAnimationController.expandFromStack( + mStackAnimationController.getStackPosition(), updateAfter); + } else { + mBubbleContainer.cancelAllAnimations(); + mExpandedAnimationController.collapseBackToStack( + () -> { + mBubbleContainer.setController(mStackAnimationController); + updateAfter.run(); + }); } - // Determine the translation for the stack - Point position = shouldExpand - ? BubbleController.getExpandPoint(this, mBubbleSize, mDisplaySize) - : mCollapsedPosition; - int delay = shouldExpand ? 0 : 100; - AnimatorSet translationAnim = BubbleMovementHelper.getTranslateAnim(this, position, - 200, delay, null); - if (!shouldExpand) { - // First collapse the stack, then translate, maybe should expand at same time? - animateStackExpansion(() -> translationAnim.start()); - } else { - // First translate, then expand - translationAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mIsAnimating = true; - } - @Override - public void onAnimationEnd(Animator animation) { - animateStackExpansion(() -> mIsAnimating = false); - } - }); - translationAnim.start(); + final float xStart = + mStackAnimationController.getStackPosition().x < getWidth() / 2 + ? -mExpandedAnimateXDistance + : mExpandedAnimateXDistance; + + final float yStart = Math.min( + mStackAnimationController.getStackPosition().y, + mExpandedAnimateYDistance); + final float yDest = getStatusBarHeight() + mExpandedBubble.getHeight() + mBubblePadding; + + if (shouldExpand) { + mExpandedViewContainer.setTranslationX(xStart); + mExpandedViewContainer.setTranslationY(yStart); + mExpandedViewContainer.setAlpha(0f); } + + mExpandedViewXAnim.animateToFinalPosition(shouldExpand ? 0f : xStart); + mExpandedViewYAnim.animateToFinalPosition(shouldExpand ? yDest : yStart); + mExpandedViewContainer.animate() + .setDuration(100) + .alpha(shouldExpand ? 1f : 0f); } } @@ -401,14 +440,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F + mBubbleContainer.getPaddingStart(); } - /** - * Saves the current position of the stack, used to save user placement of the stack to - * return to after an animation. - */ - private void savePosition() { - mCollapsedPosition = getPosition(); - } - private void notifyExpansionChanged(BubbleView bubbleView, boolean expanded) { if (mExpandListener != null) { NotificationEntry entry = bubbleView != null ? bubbleView.getEntry() : null; @@ -420,31 +451,151 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F return getBubbleAt(0); } - private BubbleView getBubbleAt(int i) { + /** Return the BubbleView at the given index from the bubble container. */ + public BubbleView getBubbleAt(int i) { return mBubbleContainer.getChildCount() > i ? (BubbleView) mBubbleContainer.getChildAt(i) : null; } @Override - public void setPosition(int x, int y) { - setPositionX(x); - setPositionY(y); + public void setPosition(float x, float y) { + mStackAnimationController.moveFirstBubbleWithStackFollowing(x, y); } @Override - public void setPositionX(int x) { - setTranslationX(x); + public void setPositionX(float x) { + // Unsupported, use setPosition(x, y). } @Override - public void setPositionY(int y) { - setTranslationY(y); + public void setPositionY(float y) { + // Unsupported, use setPosition(x, y). } @Override - public Point getPosition() { - return new Point((int) getTranslationX(), (int) getTranslationY()); + public PointF getPosition() { + return mStackAnimationController.getStackPosition(); + } + + /** Called when a drag operation on an individual bubble has started. */ + public void onBubbleDragStart(BubbleView bubble) { + // TODO: Save position and snap back if not dismissed. + } + + /** Called with the coordinates to which an individual bubble has been dragged. */ + public void onBubbleDragged(BubbleView bubble, float x, float y) { + bubble.setTranslationX(x); + bubble.setTranslationY(y); + } + + /** Called when a drag operation on an individual bubble has finished. */ + public void onBubbleDragFinish(BubbleView bubble, float x, float y, float velX, float velY) { + // TODO: Add fling to bottom to dismiss. + } + + void onDragStart() { + if (mIsExpanded) { + return; + } + + mStackAnimationController.cancelStackPositionAnimations(); + mBubbleContainer.setController(mStackAnimationController); + mIsAnimating = false; + } + + void onDragged(float x, float y) { + // TODO: We can drag if animating - just need to reroute inflight anims to drag point. + if (mIsExpanded) { + return; + } + + mStackAnimationController.moveFirstBubbleWithStackFollowing(x, y); + } + + void onDragFinish(float x, float y, float velX, float velY) { + // TODO: Add fling to bottom to dismiss. + + if (mIsExpanded || mIsAnimating) { + return; + } + + final boolean stackOnLeftSide = x + - mBubbleContainer.getChildAt(0).getWidth() / 2 + < mDisplaySize.x / 2; + + final boolean stackShouldFlingLeft = stackOnLeftSide + ? velX < ESCAPE_VELOCITY + : velX < -ESCAPE_VELOCITY; + + final RectF stackBounds = mStackAnimationController.getAllowableStackPositionRegion(); + + // Target X translation (either the left or right side of the screen). + final float destinationRelativeX = stackShouldFlingLeft + ? stackBounds.left : stackBounds.right; + + // Minimum velocity required for the stack to make it to the side of the screen. + final float escapeVelocity = getMinXVelocity( + x, + destinationRelativeX, + FLING_FRICTION_X); + + // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity so + // that it'll make it all the way to the side of the screen. + final float startXVelocity = stackShouldFlingLeft + ? Math.min(escapeVelocity, velX) + : Math.max(escapeVelocity, velX); + + mStackAnimationController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_X, + startXVelocity, + FLING_FRICTION_X, + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SPRING_DAMPING_RATIO), + destinationRelativeX); + + mStackAnimationController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_Y, + velY, + FLING_FRICTION_Y, + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SPRING_DAMPING_RATIO), + /* destination */ null); + } + + /** + * Minimum velocity, in pixels/second, required to get from x to destX while being slowed by a + * given frictional force. + * + * This is not derived using real math, I just made it up because the math in FlingAnimation + * looks hard and this seems to work. It doesn't actually matter because if it doesn't make it + * to the edge via Fling, it'll get Spring'd there anyway. + * + * TODO(tsuji, or someone who likes math): Figure out math. + */ + private float getMinXVelocity(float x, float destX, float friction) { + return (destX - x) * (friction * 5) + ESCAPE_VELOCITY; + } + + @Override + public void getBoundsOnScreen(Rect outRect) { + if (!mIsExpanded) { + mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect); + } else { + mBubbleContainer.getBoundsOnScreen(outRect); + } + } + + private int getStatusBarHeight() { + if (getRootWindowInsets() != null) { + return Math.max( + getRootWindowInsets().getSystemWindowInsetTop(), + getRootWindowInsets().getDisplayCutout().getSafeInsetTop()); + } + + return 0; } private boolean isIntersecting(View view, float x, float y) { @@ -510,9 +661,8 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mExpandedViewContainer.setHeaderText(null); } - int pointerPosition = mExpandedBubble.getPosition().x - + (mExpandedBubble.getWidth() / 2); - mExpandedViewContainer.setPointerPosition(pointerPosition); + float pointerPosition = mExpandedBubble.getPosition().x + (mExpandedBubble.getWidth() / 2); + mExpandedViewContainer.setPointerPosition((int) pointerPosition); } private void applyCurrentState() { @@ -522,7 +672,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F if (!mIsExpanded) { mExpandedViewContainer.setExpandedView(null); } else { - mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight()); View expandedView = mExpandedViewContainer.getExpandedView(); if (expandedView instanceof ActivityView) { if (expandedView.isAttachedToWindow()) { @@ -537,53 +686,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i); bv.updateDotVisibility(); bv.setZ(bubbsCount - i); - - int transX = mIsExpanded ? (bv.getWidth() + mBubblePadding) * i : mBubblePadding * i; - ViewState viewState = new ViewState(); - viewState.initFrom(bv); - viewState.xTranslation = transX; - viewState.applyToView(bv); - - if (mIsExpanded) { - // Save the position so we can magnet back, tag is retrieved in BubbleTouchHandler - bv.setTag(new Point(transX, 0)); - } - } - } - - private void animateStackExpansion(Runnable endRunnable) { - int childCount = mBubbleContainer.getChildCount(); - for (int i = 0; i < childCount; i++) { - BubbleView child = (BubbleView) mBubbleContainer.getChildAt(i); - int transX = mIsExpanded ? (mBubbleSize + mBubblePadding) * i : mBubblePadding * i; - int duration = childCount > 1 ? 200 : 0; - if (mIsExpanded) { - // Save the position so we can magnet back, tag is retrieved in BubbleTouchHandler - child.setTag(new Point(transX, 0)); - } - ViewPropertyAnimator anim = child - .animate() - .setStartDelay(15 * i) - .setDuration(duration) - .setInterpolator(mIsExpanded - ? new OvershootInterpolator() - : new AccelerateInterpolator()) - .translationY(0) - .translationX(transX); - final int fi = i; - // Probably want this choreographed with translation somehow / make it snappier - anim.withStartAction(() -> mIsAnimating = true); - anim.withEndAction(() -> { - if (endRunnable != null) { - endRunnable.run(); - } - if (fi == mBubbleContainer.getChildCount() - 1) { - applyCurrentState(); - mIsAnimating = false; - requestUpdate(); - } - }); - anim.start(); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java index 97784b0f4f936..22cd2fcc3e72f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java @@ -19,7 +19,7 @@ package com.android.systemui.bubbles; import static com.android.systemui.pip.phone.PipDismissViewController.SHOW_TARGET_DELAY; import android.content.Context; -import android.graphics.Point; +import android.graphics.PointF; import android.os.Handler; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -37,18 +37,16 @@ class BubbleTouchHandler implements View.OnTouchListener { private BubbleController mController = Dependency.get(BubbleController.class); private PipDismissViewController mDismissViewController; - private BubbleMovementHelper mMovementHelper; // The position of the bubble on down event - private int mBubbleDownPosX; - private int mBubbleDownPosY; + private float mBubbleDownPosX; + private float mBubbleDownPosY; // The touch position on down event - private int mDownX = -1; - private int mDownY = -1; + private float mDownX = -1; + private float mDownY = -1; private boolean mMovedEnough; private int mTouchSlopSquared; - private float mMinFlingVelocity; private VelocityTracker mVelocityTracker; private boolean mInDismissTarget; @@ -71,32 +69,27 @@ class BubbleTouchHandler implements View.OnTouchListener { /** * Sets the position of the view. */ - void setPosition(int x, int y); + void setPosition(float x, float y); /** * Sets the x position of the view. */ - void setPositionX(int x); + void setPositionX(float x); /** * Sets the y position of the view. */ - void setPositionY(int y); + void setPositionY(float y); /** * @return the position of the view. */ - Point getPosition(); + PointF getPosition(); } public BubbleTouchHandler(Context context) { final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mTouchSlopSquared = touchSlop * touchSlop; - - // Multiply by 3 for better fling - mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity() * 3; - - mMovementHelper = new BubbleMovementHelper(context); mDismissViewController = new PipDismissViewController(context); } @@ -119,9 +112,11 @@ class BubbleTouchHandler implements View.OnTouchListener { FloatingView floatingView = (FloatingView) targetView; boolean isBubbleStack = floatingView instanceof BubbleStackView; - Point startPos = floatingView.getPosition(); - int rawX = (int) event.getRawX(); - int rawY = (int) event.getRawY(); + PointF startPos = floatingView.getPosition(); + float rawX = event.getRawX(); + float rawY = event.getRawY(); + float x = mBubbleDownPosX + rawX - mDownX; + float y = mBubbleDownPosY + rawY - mDownY; switch (action) { case MotionEvent.ACTION_DOWN: trackMovement(event); @@ -134,6 +129,13 @@ class BubbleTouchHandler implements View.OnTouchListener { mDownX = rawX; mDownY = rawY; mMovedEnough = false; + + if (isBubbleStack) { + stack.onDragStart(); + } else { + stack.onBubbleDragStart((BubbleView) floatingView); + } + break; case MotionEvent.ACTION_MOVE: @@ -145,22 +147,23 @@ class BubbleTouchHandler implements View.OnTouchListener { mDownX = rawX; mDownY = rawY; } - final int deltaX = rawX - mDownX; - final int deltaY = rawY - mDownY; + final float deltaX = rawX - mDownX; + final float deltaY = rawY - mDownY; if ((deltaX * deltaX) + (deltaY * deltaY) > mTouchSlopSquared && !mMovedEnough) { mMovedEnough = true; } - int x = mBubbleDownPosX + rawX - mDownX; - int y = mBubbleDownPosY + rawY - mDownY; if (mMovedEnough) { - if (floatingView instanceof BubbleView && mBubbleDraggingOut == null) { + if (floatingView instanceof BubbleView) { mBubbleDraggingOut = ((BubbleView) floatingView); + stack.onBubbleDragged(mBubbleDraggingOut, x, y); + } else { + stack.onDragged(x, y); } - floatingView.setPosition(x, y); } // TODO - when we're in the target stick to it / animate in some way? - mInDismissTarget = mDismissViewController.updateTarget((View) floatingView); + mInDismissTarget = mDismissViewController.updateTarget( + isBubbleStack ? stack.getBubbleAt(0) : (View) floatingView); break; case MotionEvent.ACTION_CANCEL: @@ -181,19 +184,9 @@ class BubbleTouchHandler implements View.OnTouchListener { final float velX = mVelocityTracker.getXVelocity(); final float velY = mVelocityTracker.getYVelocity(); if (isBubbleStack) { - if ((Math.abs(velY) > mMinFlingVelocity) - || (Math.abs(velX) > mMinFlingVelocity)) { - // It's being flung somewhere - mMovementHelper.animateFlingTo(stack, velX, velY).start(); - } else { - // Magnet back to nearest edge - mMovementHelper.animateMagnetTo(stack).start(); - } + stack.onDragFinish(x, y, velX, velY); } else { - // Individual bubble got dragged but not dismissed.. lets animate it back - // into position - Point toGoTo = (Point) ((View) floatingView).getTag(); - mMovementHelper.getTranslateAnim(floatingView, toGoTo, 100, 0).start(); + stack.onBubbleDragFinish(mBubbleDraggingOut, x, y, velX, velY); } } else if (floatingView.equals(stack.getExpandedBubble())) { stack.collapseStack(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java index 7b6e79be64db6..4601939349815 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java @@ -22,7 +22,7 @@ import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.graphics.Color; -import android.graphics.Point; +import android.graphics.PointF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; @@ -266,24 +266,24 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati } @Override - public void setPosition(int x, int y) { + public void setPosition(float x, float y) { setPositionX(x); setPositionY(y); } @Override - public void setPositionX(int x) { + public void setPositionX(float x) { setTranslationX(x); } @Override - public void setPositionY(int y) { + public void setPositionY(float y) { setTranslationY(y); } @Override - public Point getPosition() { - return new Point((int) getTranslationX(), (int) getTranslationY()); + public PointF getPosition() { + return new PointF(getTranslationX(), getTranslationY()); } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java new file mode 100644 index 0000000000000..f3ca9386c3125 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import android.graphics.PointF; +import android.view.View; +import android.view.WindowInsets; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.systemui.R; + +import com.google.android.collect.Sets; + +import java.util.Set; + +/** + * Animation controller for bubbles when they're in their expanded state, or animating to/from the + * expanded state. This controls the expansion animation as well as bubbles 'dragging out' to be + * dismissed. + */ +public class ExpandedAnimationController + extends PhysicsAnimationLayout.PhysicsAnimationController { + + /** + * The stack position from which the bubbles were expanded. Saved in {@link #expandFromStack} + * and used to return to stack form in {@link #collapseBackToStack}. + */ + private PointF mExpandedFrom; + + /** Horizontal offset between bubbles, which we need to know to re-stack them. */ + private float mStackOffsetPx; + /** Spacing between bubbles in the expanded state. */ + private float mBubblePaddingPx; + /** Size of each bubble. */ + private float mBubbleSizePx; + + @Override + protected void setLayout(PhysicsAnimationLayout layout) { + super.setLayout(layout); + mStackOffsetPx = layout.getResources().getDimensionPixelSize( + R.dimen.bubble_stack_offset); + mBubblePaddingPx = layout.getResources().getDimensionPixelSize( + R.dimen.bubble_padding); + mBubbleSizePx = layout.getResources().getDimensionPixelSize( + R.dimen.individual_bubble_size); + } + + /** + * Animates expanding the bubbles into a row along the top of the screen. + * + * @return The y-value to which the bubbles were expanded, in case that's useful. + */ + public float expandFromStack(PointF expandedFrom, Runnable after) { + mExpandedFrom = expandedFrom; + + // How much to translate the next bubble, so that it is not overlapping the previous one. + float translateNextBubbleXBy = mBubblePaddingPx; + for (int i = 0; i < mLayout.getChildCount(); i++) { + mLayout.animatePositionForChildAtIndex(i, translateNextBubbleXBy, getExpandedY()); + translateNextBubbleXBy += mBubbleSizePx + mBubblePaddingPx; + } + + runAfterTranslationsEnd(after); + return getExpandedY(); + } + + /** Animate collapsing the bubbles back to their stacked position. */ + public void collapseBackToStack(Runnable after) { + // Stack to the left if we're going to the left, or right if not. + final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mExpandedFrom.x) ? -1 : 1; + for (int i = 0; i < mLayout.getChildCount(); i++) { + mLayout.animatePositionForChildAtIndex( + i, mExpandedFrom.x + (sideMultiplier * i * mStackOffsetPx), mExpandedFrom.y); + } + + runAfterTranslationsEnd(after); + } + + /** The Y value of the row of expanded bubbles. */ + private float getExpandedY() { + final WindowInsets insets = mLayout.getRootWindowInsets(); + if (insets != null) { + return mBubblePaddingPx + Math.max( + insets.getSystemWindowInsetTop(), + insets.getDisplayCutout().getSafeInsetTop()); + } + + return mBubblePaddingPx; + } + + /** Runs the given Runnable after all translation-related animations have ended. */ + private void runAfterTranslationsEnd(Runnable after) { + DynamicAnimation.OnAnimationEndListener allEndedListener = + (animation, canceled, value, velocity) -> { + if (!mLayout.arePropertiesAnimating( + DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y)) { + after.run(); + } + }; + + mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_X); + mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_Y); + } + + @Override + Set getAnimatedProperties() { + return Sets.newHashSet( + DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + } + + @Override + int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) { + return NONE; + } + + @Override + float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) { + return 0; + } + + @Override + SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) { + return new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY); + } + + @Override + void onChildAdded(View child, int index) { + // TODO: Animate the new bubble into the row, and push the other bubbles out of the way. + child.setTranslationY(getExpandedY()); + } + + @Override + void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { + // TODO: Animate the bubble out, and pull the other bubbles into its position. + actuallyRemove.run(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java new file mode 100644 index 0000000000000..4e0abc8009b43 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import androidx.dynamicanimation.animation.DynamicAnimation; + +/** + * End listener that removes itself from its animation when called for the first time. Useful since + * anonymous OnAnimationEndListener instances can't pass themselves to + * {@link DynamicAnimation#removeEndListener}, but can call through to this superclass + * implementation. + */ +public class OneTimeEndListener implements DynamicAnimation.OnAnimationEndListener { + + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + animation.removeEndListener(this); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java new file mode 100644 index 0000000000000..1ced3a4daac68 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java @@ -0,0 +1,496 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.systemui.R; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Set; + +/** + * Layout that constructs physics-based animations for each of its children, which behave according + * to settings provided by a {@link PhysicsAnimationController} instance. + * + * See physics-animation-layout.md. + */ +public class PhysicsAnimationLayout extends FrameLayout { + private static final String TAG = "Bubbs.PAL"; + + /** + * Controls the construction, configuration, and use of the physics animations supplied by this + * layout. + */ + abstract static class PhysicsAnimationController { + + /** + * Constant to return from {@link #getNextAnimationInChain} if the animation should not be + * chained at all. + */ + protected static final int NONE = -1; + + /** Set of properties for which the layout should construct physics animations. */ + abstract Set getAnimatedProperties(); + + /** + * Returns the index of the next animation after the given index in the animation chain, or + * {@link #NONE} if it should not be chained, or if the chain should end at the given index. + * + * If a next index is returned, an update listener will be added to the animation at the + * given index that dispatches value updates to the animation at the next index. This + * creates a 'following' effect. + * + * Typical implementations of this method will return either index + 1, or index - 1, to + * create forward or backward chains between adjacent child views, but this is not required. + */ + abstract int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index); + + /** + * Offsets to be added to the value that chained animations of the given property dispatch + * to subsequent child animations. + * + * This is used for things like maintaining the 'stack' effect in Bubbles, where bubbles + * stack off to the left or right side slightly. + */ + abstract float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property); + + /** + * Returns the SpringForce to be used for the given child view's property animation. Despite + * these usually being similar or identical across properties and views, {@link SpringForce} + * also contains the SpringAnimation's final position, so we have to construct a new one for + * each animation rather than using a constant. + */ + abstract SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view); + + /** + * Called when a new child is added at the specified index. Controllers can use this + * opportunity to animate in the new view. + */ + abstract void onChildAdded(View child, int index); + + /** + * Called when a child is to be removed from the layout. Controllers can use this + * opportunity to animate out the new view before calling the provided callback to actually + * remove it. + * + * Controllers should be careful to ensure that actuallyRemove is called on all code paths + * or child views will never be removed. + */ + abstract void onChildToBeRemoved(View child, int index, Runnable actuallyRemove); + + protected PhysicsAnimationLayout mLayout; + + PhysicsAnimationController() { } + + protected void setLayout(PhysicsAnimationLayout layout) { + this.mLayout = layout; + } + + protected PhysicsAnimationLayout getLayout() { + return mLayout; + } + } + + /** + * End listeners that are called when every child's animation of the given property has + * finished. + */ + protected final HashMap + mEndListenerForProperty = new HashMap<>(); + + /** + * List of views that were passed to removeView, but are currently being animated out. These + * views will be actually removed by the controller (via super.removeView) once they're done + * animating out. + */ + private final List mViewsToBeActuallyRemoved = new ArrayList<>(); + + /** The currently active animation controller. */ + private PhysicsAnimationController mController; + + /** + * The maximum number of children to render and animate at a time. See + * {@link #setMaxRenderedChildren}. + */ + private int mMaxRenderedChildren = 5; + + public PhysicsAnimationLayout(Context context) { + super(context); + } + + /** + * The maximum number of children to render and animate at a time. Any child views added beyond + * this limit will be set to {@link View#GONE}. If any animations attempt to run on the view, + * the corresponding property will be set with no animation. + */ + public void setMaxRenderedChildren(int max) { + this.mMaxRenderedChildren = max; + } + + /** + * Sets the animation controller and constructs or reconfigures the layout's physics animations + * to meet the controller's specifications. + */ + public void setController(PhysicsAnimationController controller) { + cancelAllAnimations(); + mEndListenerForProperty.clear(); + + this.mController = controller; + mController.setLayout(this); + + // Set up animations for this controller's animated properties. + for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { + setUpAnimationsForProperty(property); + } + } + + /** + * Sets an end listener that will be called when all child animations for a given property have + * stopped running. + */ + public void setEndListenerForProperty( + DynamicAnimation.OnAnimationEndListener listener, + DynamicAnimation.ViewProperty property) { + mEndListenerForProperty.put(property, listener); + } + + /** + * Removes the end listener that would have been called when all child animations for a given + * property stopped running. + */ + public void removeEndListenerForProperty(DynamicAnimation.ViewProperty 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 + public void addView(View child, int index, ViewGroup.LayoutParams params) { + super.addView(child, index, params); + setChildrenVisibility(); + + // 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 setController is called. + if (mController != null) { + for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { + setUpAnimationForChild(property, child, index); + } + + mController.onChildAdded(child, index); + } + } + + @Override + public void removeView(View view) { + removeViewAndThen(view, /* callback */ null); + } + + /** + * 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); + // Remove the view only if it exists in this layout, and we're not already working on + // animating its removal. + if (index > -1 && !mViewsToBeActuallyRemoved.contains(view)) { + mViewsToBeActuallyRemoved.add(view); + setChildrenVisibility(); + + // Tell the controller to animate this view out, and call the callback when it wants + // to actually remove the view. + mController.onChildToBeRemoved(view, index, () -> { + removeViewImmediateAndThen(view, callback); + mViewsToBeActuallyRemoved.remove(view); + }); + } + } else { + // Without a controller, nobody will animate this view out, so it gets an unceremonious + // departure. + removeViewImmediateAndThen(view, callback); + } + } + + /** Checks whether any animations of the given properties are still running. */ + public boolean arePropertiesAnimating(DynamicAnimation.ViewProperty... properties) { + for (int i = 0; i < getChildCount(); i++) { + for (DynamicAnimation.ViewProperty property : properties) { + if (getAnimationAtIndex(property, i).isRunning()) { + return true; + } + } + } + + return false; + } + + /** Cancels all animations that are running on all child views, for all properties. */ + public void cancelAllAnimations() { + if (mController == null) { + return; + } + + for (int i = 0; i < getChildCount(); i++) { + for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { + getAnimationAtIndex(property, i).cancel(); + } + } + } + + /** + * 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) { + if (index < getChildCount()) { + final SpringAnimation animation = getAnimationAtIndex(property, index); + if (after != null) { + animation.addEndListener(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, + float value, float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + after.run(); + } + }); + } + + if (startVel != Float.MAX_VALUE) { + animation.setStartVelocity(startVel); + } + + animation.animateToFinalPosition(value); + } + } + + /** Shortcut to animate a value with a callback, but no start velocity. */ + protected void animateValueForChildAtIndex( + DynamicAnimation.ViewProperty property, + int index, + float value, + Runnable after) { + animateValueForChildAtIndex(property, index, value, Float.MAX_VALUE, after); + } + + /** Shortcut to animate a value with a start velocity, but no callback. */ + protected void animateValueForChildAtIndex( + DynamicAnimation.ViewProperty property, + int index, + float value, + float startVel) { + animateValueForChildAtIndex(property, index, value, startVel, /* callback */ null); + } + + /** Shortcut to animate a value without changing the velocity or providing a callback. */ + protected void animateValueForChildAtIndex( + DynamicAnimation.ViewProperty property, + int index, + float value) { + animateValueForChildAtIndex(property, index, value, Float.MAX_VALUE, /* callback */ null); + } + + /** Shortcut to animate a child view's TRANSLATION_X and TRANSLATION_Y values. */ + protected void animatePositionForChildAtIndex(int index, float x, float y) { + animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_X, index, x); + animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_Y, index, y); + } + + /** Whether the first child would be left of center if translated to the given x value. */ + protected boolean isFirstChildXLeftOfCenter(float x) { + if (getChildCount() > 0) { + return x + (getChildAt(0).getWidth() / 2) < getWidth() / 2; + } else { + return false; // If there's no first child, really anything is correct, right? + } + } + + /** ViewProperty's toString is useless, this returns a readable name for debug logging. */ + protected static String getReadablePropertyName(DynamicAnimation.ViewProperty property) { + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + return "TRANSLATION_X"; + } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) { + return "TRANSLATION_Y"; + } else if (property.equals(DynamicAnimation.SCALE_X)) { + return "SCALE_X"; + } else if (property.equals(DynamicAnimation.SCALE_Y)) { + return "SCALE_Y"; + } else if (property.equals(DynamicAnimation.ALPHA)) { + return "ALPHA"; + } else { + return "Unknown animation property."; + } + } + + + /** 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 + * tag system. + */ + private SpringAnimation getAnimationAtIndex( + DynamicAnimation.ViewProperty property, int index) { + return (SpringAnimation) getChildAt(index).getTag(getTagIdForProperty(property)); + } + + /** Sets up SpringAnimations of the given property for each child view in the layout. */ + private void setUpAnimationsForProperty(DynamicAnimation.ViewProperty property) { + for (int i = 0; i < getChildCount(); i++) { + setUpAnimationForChild(property, getChildAt(i), i); + } + } + + /** Constructs a SpringAnimation of the given property for a child view. */ + private void setUpAnimationForChild( + DynamicAnimation.ViewProperty property, View child, int index) { + SpringAnimation newAnim = new SpringAnimation(child, property); + newAnim.addUpdateListener((animation, value, velocity) -> { + final int nextAnimInChain = + mController.getNextAnimationInChain(property, indexOfChild(child)); + if (nextAnimInChain == PhysicsAnimationController.NONE) { + return; + } + + final int animIndex = indexOfChild(child); + final float offset = + mController.getOffsetForChainedPropertyAnimation(property); + + // 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 + // this animation's new value (plus the offset). + if (nextAnimInChain < Math.min( + getChildCount(), + mMaxRenderedChildren + mViewsToBeActuallyRemoved.size())) { + getAnimationAtIndex(property, animIndex + 1) + .animateToFinalPosition(value + offset); + } else if (nextAnimInChain < getChildCount()) { + // If the next child view is not rendered, update the property directly without + // animating it, so that the view is still in the correct state if it later + // becomes visible. + for (int i = nextAnimInChain; i < getChildCount(); i++) { + // 'value' here is the value of the last child within the rendering limit, + // not the first child's value - so we want to subtract the last child's + // index when calculating the offset. + property.setValue(getChildAt(i), value + offset * (i - animIndex)); + } + } + }); + + newAnim.setSpring(mController.getSpringForce(property, child)); + newAnim.addEndListener(new AllAnimationsForPropertyFinishedEndListener(property)); + child.setTag(getTagIdForProperty(property), newAnim); + } + + /** Hides children beyond the max rendering count. */ + private void setChildrenVisibility() { + for (int i = 0; i < getChildCount(); i++) { + getChildAt(i).setVisibility( + // 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 + // and will soon be removed, render up to 9 views temporarily. + i < (mMaxRenderedChildren + mViewsToBeActuallyRemoved.size()) + ? View.VISIBLE + : View.GONE); + } + } + + /** Return a stable ID to use as a tag key for the given property's animations. */ + private int getTagIdForProperty(DynamicAnimation.ViewProperty property) { + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + return R.id.translation_x_dynamicanimation_tag; + } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) { + return R.id.translation_y_dynamicanimation_tag; + } else if (property.equals(DynamicAnimation.SCALE_X)) { + return R.id.scale_x_dynamicanimation_tag; + } else if (property.equals(DynamicAnimation.SCALE_Y)) { + return R.id.scale_y_dynamicanimation_tag; + } else if (property.equals(DynamicAnimation.ALPHA)) { + return R.id.alpha_dynamicanimation_tag; + } + + return -1; + } + + /** + * End listener that is added to each individual DynamicAnimation, which dispatches to a single + * listener when every other animation of the given property is no longer running. + * + * This is required since chained DynamicAnimations can stop and start again due to changes in + * upstream animations. This means that adding an end listener to just the last animation is not + * sufficient. By firing only when every other animation on the property has stopped running, we + * ensure that no animation will be restarted after the single end listener is called. + */ + protected class AllAnimationsForPropertyFinishedEndListener + implements DynamicAnimation.OnAnimationEndListener { + private DynamicAnimation.ViewProperty mProperty; + + AllAnimationsForPropertyFinishedEndListener(DynamicAnimation.ViewProperty property) { + this.mProperty = property; + } + + @Override + public void onAnimationEnd( + DynamicAnimation anim, boolean canceled, float value, float velocity) { + if (!arePropertiesAnimating(mProperty)) { + if (mEndListenerForProperty.containsKey(mProperty)) { + mEndListenerForProperty.get(mProperty).onAnimationEnd(anim, canceled, value, + velocity); + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java new file mode 100644 index 0000000000000..a113a630dfd8c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.RectF; +import android.util.Log; +import android.view.View; +import android.view.WindowInsets; +import android.view.WindowManager; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.FlingAnimation; +import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.systemui.R; + +import com.google.android.collect.Sets; + +import java.util.HashMap; +import java.util.Set; + +/** + * Animation controller for bubbles when they're in their stacked state. Stacked bubbles sit atop + * each other with a slight offset to the left or right (depending on which side of the screen they + * are on). Bubbles 'follow' each other when dragged, and can be flung to the left or right sides of + * the screen. + */ +public class StackAnimationController extends + PhysicsAnimationLayout.PhysicsAnimationController { + + private static final String TAG = "Bubbs.StackCtrl"; + + /** Scale factor to use initially for new bubbles being animated in. */ + private static final float ANIMATE_IN_STARTING_SCALE = 1.15f; + + /** Translation factor (multiplied by stack offset) to use for new bubbles being animated in. */ + private static final int ANIMATE_IN_TRANSLATION_FACTOR = 4; + + /** + * Values to use for the default {@link SpringForce} provided to the physics animation layout. + */ + private static final float DEFAULT_STIFFNESS = 2500f; + private static final float DEFAULT_BOUNCINESS = 0.85f; + + /** + * The canonical position of the stack. This is typically the position of the first bubble, but + * we need to keep track of it separately from the first bubble's translation in case there are + * no bubbles, or the first bubble was just added and being animated to its new position. + */ + private PointF mStackPosition = new PointF(); + + /** + * Animations on the stack position itself, which would have been started in + * {@link #flingThenSpringFirstBubbleWithStackFollowing}. These animations dispatch to + * {@link #moveFirstBubbleWithStackFollowing} to move the entire stack (with 'following' effect) + * to a legal position on the side of the screen. + */ + private HashMap mStackPositionAnimations = + new HashMap<>(); + + /** Horizontal offset of bubbles in the stack. */ + private float mStackOffset; + /** Diameter of the bubbles themselves. */ + private int mIndividualBubbleSize; + /** Size of spacing around the bubbles, separating it from the edge of the screen. */ + private int mBubblePadding; + /** How far offscreen the stack rests. */ + private int mBubbleOffscreen; + /** How far down the screen the stack starts, when there is no pre-existing location. */ + private int mStackStartingVerticalOffset; + + private Point mDisplaySize; + private RectF mAllowableStackPositionRegion; + + @Override + protected void setLayout(PhysicsAnimationLayout layout) { + super.setLayout(layout); + + Resources res = layout.getResources(); + mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); + mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen); + mStackStartingVerticalOffset = + res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y); + + mDisplaySize = new Point(); + WindowManager wm = + (WindowManager) layout.getContext().getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getSize(mDisplaySize); + } + + /** + * Instantly move the first bubble to the given point, and animate the rest of the stack behind + * it with the 'following' effect. + */ + public void moveFirstBubbleWithStackFollowing(float x, float y) { + moveFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X, x); + moveFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_Y, y); + } + + /** + * The position of the stack - typically the position of the first bubble; if no bubbles have + * been added yet, it will be where the first bubble will go when added. + */ + public PointF getStackPosition() { + return mStackPosition; + } + + /** + * Flings the first bubble along the given property's axis, using the provided configuration + * values. When the animation ends - either by hitting the min/max, or by friction sufficiently + * reducing momentum - a SpringAnimation takes over to snap the bubble to the given final + * position. + */ + public void flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.ViewProperty property, + float vel, + float friction, + SpringForce spring, + Float finalPosition) { + Log.d(TAG, String.format("Flinging %s.", + PhysicsAnimationLayout.getReadablePropertyName(property))); + + StackPositionProperty firstBubbleProperty = new StackPositionProperty(property); + final float currentValue = firstBubbleProperty.getValue(this); + final RectF bounds = getAllowableStackPositionRegion(); + final float min = + property.equals(DynamicAnimation.TRANSLATION_X) + ? bounds.left + : bounds.top; + final float max = + property.equals(DynamicAnimation.TRANSLATION_X) + ? bounds.right + : bounds.bottom; + + FlingAnimation flingAnimation = new FlingAnimation(this, firstBubbleProperty); + flingAnimation.setFriction(friction) + .setStartVelocity(vel) + + // If the bubble's property value starts beyond the desired min/max, use that value + // instead so that the animation won't immediately end. If, for example, the user + // drags the bubbles into the navigation bar, but then flings them upward, we want + // the fling to occur despite temporarily having a value outside of the min/max. If + // the bubbles are out of bounds and flung even farther out of bounds, the fling + // animation will halt immediately and the SpringAnimation will take over, springing + // it in reverse to the (legal) final position. + .setMinValue(Math.min(currentValue, min)) + .setMaxValue(Math.max(currentValue, max)) + + .addEndListener((animation, canceled, endValue, endVelocity) -> { + if (!canceled) { + springFirstBubbleWithStackFollowing(property, spring, endVelocity, + finalPosition != null + ? finalPosition + : Math.max(min, Math.min(max, endValue))); + } + }); + + cancelStackPositionAnimation(property); + mStackPositionAnimations.put(property, flingAnimation); + flingAnimation.start(); + } + + /** + * Cancel any stack position animations that were started by calling + * @link #flingThenSpringFirstBubbleWithStackFollowing}, and remove any corresponding end + * listeners. + */ + public void cancelStackPositionAnimations() { + cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_X); + cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_Y); + + mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_X); + mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_Y); + } + + /** + * Returns the region within which the stack is allowed to rest. This goes slightly off the left + * and right sides of the screen, below the status bar/cutout and above the navigation bar. + * While the stack is not allowed to rest outside of these bounds, it can temporarily be + * animated or dragged beyond them. + */ + public RectF getAllowableStackPositionRegion() { + final WindowInsets insets = mLayout.getRootWindowInsets(); + mAllowableStackPositionRegion = new RectF(); + + if (insets != null) { + mAllowableStackPositionRegion.left = + -mBubbleOffscreen + - mBubblePadding + + Math.max( + insets.getSystemWindowInsetLeft(), + insets.getDisplayCutout().getSafeInsetLeft()); + mAllowableStackPositionRegion.right = + mLayout.getWidth() + - mIndividualBubbleSize + + mBubbleOffscreen + - mBubblePadding + - Math.max( + insets.getSystemWindowInsetRight(), + insets.getDisplayCutout().getSafeInsetRight()); + + mAllowableStackPositionRegion.top = + mBubblePadding + + Math.max( + insets.getSystemWindowInsetTop(), + insets.getDisplayCutout().getSafeInsetTop()); + mAllowableStackPositionRegion.bottom = + mLayout.getHeight() + - mIndividualBubbleSize + - mBubblePadding + - Math.max( + insets.getSystemWindowInsetBottom(), + insets.getDisplayCutout().getSafeInsetBottom()); + } + + return mAllowableStackPositionRegion; + } + + @Override + Set getAnimatedProperties() { + return Sets.newHashSet( + DynamicAnimation.TRANSLATION_X, // For positioning. + DynamicAnimation.TRANSLATION_Y, + DynamicAnimation.ALPHA, // For fading in new bubbles. + DynamicAnimation.SCALE_X, // For 'popping in' new bubbles. + DynamicAnimation.SCALE_Y); + } + + @Override + int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) { + if (property.equals(DynamicAnimation.TRANSLATION_X) + || property.equals(DynamicAnimation.TRANSLATION_Y)) { + return index + 1; // Just chain them linearly. + } else { + return NONE; + } + } + + + @Override + float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) { + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + // Offset to the left if we're on the left, or the right otherwise. + return mLayout.isFirstChildXLeftOfCenter(mStackPosition.x) + ? -mStackOffset : mStackOffset; + } else { + return 0f; + } + } + + @Override + SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) { + return new SpringForce() + .setDampingRatio(DEFAULT_BOUNCINESS) + .setStiffness(DEFAULT_STIFFNESS); + } + + @Override + void onChildAdded(View child, int index) { + // If this is the first child added, position the stack in its starting position. + if (mLayout.getChildCount() == 1) { + moveStackToStartPosition(); + } + + if (mLayout.indexOfChild(child) == 0) { + child.setTranslationY(mStackPosition.y); + + // Pop in the new bubble. + child.setScaleX(ANIMATE_IN_STARTING_SCALE); + child.setScaleY(ANIMATE_IN_STARTING_SCALE); + mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_X, 0, 1f); + mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_Y, 0, 1f); + + // Fade in the new bubble. + child.setAlpha(0); + mLayout.animateValueForChildAtIndex(DynamicAnimation.ALPHA, 0, 1f); + + // Start the new bubble 4x the normal offset distance in the opposite direction. We'll + // 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. + float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); + child.setTranslationX(mStackPosition.x - (ANIMATE_IN_TRANSLATION_FACTOR * xOffset)); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x); + } + } + + @Override + void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { + // Animate the child out, actually removing it once its alpha is zero. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.ALPHA, index, 0f, () -> { + actuallyRemove.run(); + }); + 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(); + if (hasPrecedingChild) { + final int precedingViewIndex = mLayout.getPrecedingNonRemovedViewIndex(index); + if (precedingViewIndex >= 0) { + final float offsetX = + getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); + mLayout.animatePositionForChildAtIndex( + precedingViewIndex, + mStackPosition.x + (index * offsetX), + mStackPosition.y); + } + } + } + + /** Moves the stack, without any animation, to the starting position. */ + private void moveStackToStartPosition() { + mLayout.post(() -> setStackPosition( + getAllowableStackPositionRegion().right, + getAllowableStackPositionRegion().top + mStackStartingVerticalOffset)); + } + + /** + * Moves the first bubble instantly to the given X or Y translation, and instructs subsequent + * bubbles to animate 'following' to the new location. + */ + private void moveFirstBubbleWithStackFollowing( + DynamicAnimation.ViewProperty property, float value) { + + // Update the canonical stack position. + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + mStackPosition.x = value; + } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) { + mStackPosition.y = value; + } + + if (mLayout.getChildCount() > 0) { + property.setValue(mLayout.getChildAt(0), value); + mLayout.animateValueForChildAtIndex( + property, + /* index */ 1, + value + getOffsetForChainedPropertyAnimation(property)); + } + } + + /** Moves the stack to a position instantly, with no animation. */ + private void setStackPosition(float x, float y) { + Log.d(TAG, String.format("Setting position to (%f, %f).", x, y)); + mStackPosition.set(x, y); + + cancelStackPositionAnimations(); + + // Since we're not using the chained animations, apply the offsets manually. + final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); + final float yOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_Y); + for (int i = 0; i < mLayout.getChildCount(); i++) { + mLayout.getChildAt(i).setTranslationX(x + (i * xOffset)); + mLayout.getChildAt(i).setTranslationY(y + (i * yOffset)); + } + } + + /** + * Springs the first bubble to the given final position, with the rest of the stack 'following'. + */ + private void springFirstBubbleWithStackFollowing( + DynamicAnimation.ViewProperty property, SpringForce spring, + float vel, float finalPosition) { + + Log.d(TAG, String.format("Springing %s to final position %f.", + PhysicsAnimationLayout.getReadablePropertyName(property), + finalPosition)); + + StackPositionProperty firstBubbleProperty = new StackPositionProperty(property); + SpringAnimation springAnimation = + new SpringAnimation(this, firstBubbleProperty) + .setSpring(spring) + .setStartVelocity(vel); + + cancelStackPositionAnimation(property); + mStackPositionAnimations.put(property, springAnimation); + springAnimation.animateToFinalPosition(finalPosition); + } + + /** + * Cancels any outstanding first bubble property animations that are running. This does not + * affect the SpringAnimations controlling the individual bubbles' 'following' effect - it only + * cancels animations started from {@link #springFirstBubbleWithStackFollowing} and + * {@link #flingThenSpringFirstBubbleWithStackFollowing}. + */ + private void cancelStackPositionAnimation(DynamicAnimation.ViewProperty property) { + if (mStackPositionAnimations.containsKey(property)) { + mStackPositionAnimations.get(property).cancel(); + } + } + + /** + * FloatProperty that uses {@link #moveFirstBubbleWithStackFollowing} to set the first bubble's + * translation and animate the rest of the stack with it. A DynamicAnimation can animate this + * property directly to move the first bubble and cause the stack to 'follow' to the new + * location. + * + * This could also be achieved by simply animating the first bubble view and adding an update + * listener to dispatch movement to the rest of the stack. However, this would require + * duplication of logic in that update handler - it's simpler to keep all logic contained in the + * {@link #moveFirstBubbleWithStackFollowing} method. + */ + private class StackPositionProperty + extends FloatPropertyCompat { + private final DynamicAnimation.ViewProperty mProperty; + + private StackPositionProperty(DynamicAnimation.ViewProperty property) { + super(property.toString()); + mProperty = property; + } + + @Override + public float getValue(StackAnimationController controller) { + return mProperty.getValue(mLayout.getChildAt(0)); + } + + @Override + public void setValue(StackAnimationController controller, float value) { + moveFirstBubbleWithStackFollowing(mProperty, value); + } + } +} + diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 60a20cf15cc07..e80275793b28e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -61,7 +61,6 @@ public class BubbleControllerTest extends SysuiTestCase { private IActivityManager mActivityManager; @Mock private DozeParameters mDozeParameters; - @Mock private FrameLayout mStatusBarView; @Captor private ArgumentCaptor mEntryListenerCaptor; @@ -80,6 +79,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mStatusBarView = new FrameLayout(mContext); mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager); // Bubbles get added to status bar window view 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 new file mode 100644 index 0000000000000..1bb7ef4a657b0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import static org.junit.Assert.assertEquals; + +import android.content.res.Resources; +import android.graphics.PointF; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; + +import androidx.dynamicanimation.animation.DynamicAnimation; + +import com.android.systemui.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestCase { + + @Spy + private ExpandedAnimationController mExpandedController = new ExpandedAnimationController(); + + private int mStackOffset; + private float mBubblePadding; + private float mBubbleSize; + + private PointF mExpansionPoint; + + @Before + public void setUp() throws Exception { + super.setUp(); + addOneMoreThanRenderLimitBubbles(); + mLayout.setController(mExpandedController); + Resources res = mLayout.getResources(); + mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); + } + + @Test + public void testExpansionAndCollapse() throws InterruptedException { + mExpansionPoint = new PointF(100, 100); + Runnable afterExpand = Mockito.mock(Runnable.class); + mExpandedController.expandFromStack(mExpansionPoint, afterExpand); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + testExpanded(); + Mockito.verify(afterExpand).run(); + + Runnable afterCollapse = Mockito.mock(Runnable.class); + mExpandedController.collapseBackToStack(afterCollapse); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1); + Mockito.verify(afterExpand).run(); + } + + /** Check that children are in the correct positions for being stacked. */ + private void testStackedAtPosition(float x, float y, int offsetMultiplier) { + // Make sure the rest of the stack moved again, including the first bubble not moving, and + // is stacked to the right now that we're on the right side of the screen. + for (int i = 0; i < mLayout.getChildCount(); i++) { + assertEquals(x + i * offsetMultiplier * mStackOffset, + mViews.get(i).getTranslationX(), 2f); + assertEquals(y, mViews.get(i).getTranslationY(), 2f); + } + } + + /** Check that children are in the correct positions for being expanded. */ + private void testExpanded() { + // Make sure the rest of the stack moved again, including the first bubble not moving, and + // is stacked to the right now that we're on the right side of the screen. + for (int i = 0; i < mLayout.getChildCount(); i++) { + assertEquals(mBubblePadding + (i * (mBubbleSize + mBubblePadding)), + mViews.get(i).getTranslationX(), + 2f); + assertEquals(mBubblePadding + mCutoutInsetSize, + mViews.get(i).getTranslationY(), 2f); + } + } +} 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 new file mode 100644 index 0000000000000..bfc02d902416d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; + +import android.os.SystemClock; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.google.android.collect.Sets; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.mockito.Spy; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +/** Tests the PhysicsAnimationLayout itself, with a basic test animation controller. */ +public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { + static final float TEST_TRANSLATION_X_OFFSET = 15f; + + @Spy + private TestableAnimationController mTestableController = new TestableAnimationController(); + + @Before + public void setUp() throws Exception { + super.setUp(); + + // By default, use translation animations, chain the X animations with the default + // offset, and don't actually remove views immediately (since most implementations will wait + // to animate child views out before actually removing them). + mTestableController.setAnimatedProperties(Sets.newHashSet( + DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)); + mTestableController.setChainedProperties(Sets.newHashSet(DynamicAnimation.TRANSLATION_X)); + mTestableController.setOffsetForProperty( + DynamicAnimation.TRANSLATION_X, TEST_TRANSLATION_X_OFFSET); + mTestableController.setRemoveImmediately(false); + } + + @Test + public void testRenderVisibility() { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + // The last child should be GONE, the rest VISIBLE. + for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { + assertEquals(i == mMaxRenderedBubbles ? View.GONE : View.VISIBLE, + mLayout.getChildAt(i).getVisibility()); + } + } + + @Test + public void testHierarchyChanges() { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + // Make sure the controller was notified of all the views we added. + for (View mView : mViews) { + Mockito.verify(mTestableController).onChildAdded(mView, 0); + } + + // Remove some views and ensure the controller was notified, with the proper indices. + mTestableController.setRemoveImmediately(true); + mLayout.removeView(mViews.get(1)); + mLayout.removeView(mViews.get(2)); + Mockito.verify(mTestableController).onChildToBeRemoved( + eq(mViews.get(1)), eq(1), any()); + Mockito.verify(mTestableController).onChildToBeRemoved( + eq(mViews.get(2)), eq(1), any()); + + // Make sure we still get view added notifications after doing some removals. + final View newBubble = new FrameLayout(mContext); + mLayout.addView(newBubble, 0); + Mockito.verify(mTestableController).onChildAdded(newBubble, 0); + } + + @Test + public void testUpdateValueNotChained() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + // Don't chain any values. + mTestableController.setChainedProperties(Sets.newHashSet()); + + // Child views should not be translated. + assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f); + + // Animate the first child's translation X. + final CountDownLatch animLatch = new CountDownLatch(1); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100, + animLatch::countDown); + animLatch.await(1, TimeUnit.SECONDS); + + // Ensure that the first view has been translated, but not the second one. + assertEquals(100, mLayout.getChildAt(0).getTranslationX(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f); + } + + @Test + public void testUpdateValueXChained() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + testChainedTranslationAnimations(); + } + + @Test + public void testSetEndListeners() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + mTestableController.setChainedProperties(Sets.newHashSet()); + + final CountDownLatch xLatch = new CountDownLatch(1); + OneTimeEndListener xEndListener = Mockito.spy(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + xLatch.countDown(); + } + }); + + final CountDownLatch yLatch = new CountDownLatch(1); + final OneTimeEndListener yEndListener = Mockito.spy(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + yLatch.countDown(); + } + }); + + // Set end listeners for both x and y. + mLayout.setEndListenerForProperty(xEndListener, DynamicAnimation.TRANSLATION_X); + mLayout.setEndListenerForProperty(yEndListener, DynamicAnimation.TRANSLATION_Y); + + // Animate x, and wait for it to finish. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100); + xLatch.await(); + yLatch.await(1, TimeUnit.SECONDS); + + // Make sure the x end listener was called only one time, and the y listener was never + // called since we didn't animate y. Wait 1 second after the original animation end trigger + // to make sure it doesn't get called again. + Mockito.verify(xEndListener, Mockito.after(1000).times(1)) + .onAnimationEnd( + any(), + eq(false), + eq(100f), + anyFloat()); + Mockito.verify(yEndListener, Mockito.after(1000).never()) + .onAnimationEnd(any(), anyBoolean(), anyFloat(), anyFloat()); + } + + @Test + public void testRemoveEndListeners() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + mTestableController.setChainedProperties(Sets.newHashSet()); + + final CountDownLatch xLatch = new CountDownLatch(1); + OneTimeEndListener xEndListener = Mockito.spy(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + xLatch.countDown(); + } + }); + + // Set the end listener. + mLayout.setEndListenerForProperty(xEndListener, DynamicAnimation.TRANSLATION_X); + + // Animate x, and wait for it to finish. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100); + xLatch.await(); + + InOrder endListenerCalls = inOrder(xEndListener); + endListenerCalls.verify(xEndListener, Mockito.times(1)) + .onAnimationEnd( + any(), + eq(false), + eq(100f), + anyFloat()); + + // Animate X again, remove the end listener. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 1000); + mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_X); + xLatch.await(1, TimeUnit.SECONDS); + + // Make sure the end listener was not called. + 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 + public void testSetController() throws InterruptedException { + // Add the bubbles, then set the controller, to make sure that a controller added to an + // already-initialized view works correctly. + addOneMoreThanRenderLimitBubbles(); + mLayout.setController(mTestableController); + testChainedTranslationAnimations(); + + TestableAnimationController secondController = + Mockito.spy(new TestableAnimationController()); + secondController.setAnimatedProperties(Sets.newHashSet( + DynamicAnimation.SCALE_X, DynamicAnimation.SCALE_Y)); + secondController.setChainedProperties(Sets.newHashSet( + DynamicAnimation.SCALE_X)); + secondController.setOffsetForProperty( + DynamicAnimation.SCALE_X, 10f); + secondController.setRemoveImmediately(true); + + mLayout.setController(secondController); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.SCALE_X, + 0, + 1.5f); + + waitForPropertyAnimations(DynamicAnimation.SCALE_X); + + // Make sure we never asked the original controller about any SCALE animations, that would + // mean the controller wasn't switched over properly. + Mockito.verify(mTestableController, Mockito.never()) + .getNextAnimationInChain(eq(DynamicAnimation.SCALE_X), anyInt()); + Mockito.verify(mTestableController, Mockito.never()) + .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X)); + + // Make sure we asked the new controller about its animated properties, and configuration + // options. + Mockito.verify(secondController, Mockito.atLeastOnce()) + .getAnimatedProperties(); + Mockito.verify(secondController, Mockito.atLeastOnce()) + .getNextAnimationInChain(eq(DynamicAnimation.SCALE_X), anyInt()); + Mockito.verify(secondController, Mockito.atLeastOnce()) + .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X)); + + mLayout.setController(mTestableController); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100f); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X); + + // Make sure we never asked the second controller about the TRANSLATION_X animation. + Mockito.verify(secondController, Mockito.never()) + .getNextAnimationInChain(eq(DynamicAnimation.TRANSLATION_X), anyInt()); + Mockito.verify(secondController, Mockito.never()) + .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.TRANSLATION_X)); + + } + + @Test + public void testArePropertiesAnimating() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + assertFalse(mLayout.arePropertiesAnimating( + DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)); + + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100); + + // Wait for the animations to get underway. + SystemClock.sleep(50); + + assertTrue(mLayout.arePropertiesAnimating(DynamicAnimation.TRANSLATION_X)); + assertFalse(mLayout.arePropertiesAnimating(DynamicAnimation.TRANSLATION_Y)); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X); + + assertFalse(mLayout.arePropertiesAnimating( + DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)); + } + + @Test + public void testCancelAllAnimations() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 1000); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_Y, + 0, + 1000); + + mLayout.cancelAllAnimations(); + + waitForLayoutMessageQueue(); + + // Animations should be somewhere before their end point. + assertTrue(mViews.get(0).getTranslationX() < 1000); + assertTrue(mViews.get(0).getTranslationY() < 1000); + } + + + /** Standard test of chained translation animations. */ + private void testChainedTranslationAnimations() throws InterruptedException { + assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f); + + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X); + + // Since we enabled chaining, animating the first view to 100 should animate the second to + // 115 (since we set the offset to 15) and the third to 130, etc. Despite the sixth bubble + // not being visible, or animated, make sure that it has the appropriate chained + // translation. + for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { + assertEquals( + 100 + i * TEST_TRANSLATION_X_OFFSET, + mLayout.getChildAt(i).getTranslationX(), .1f); + } + + // Ensure that the Y translations were unaffected. + assertEquals(0, mLayout.getChildAt(0).getTranslationY(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationY(), .1f); + + // Animate the first child's Y translation. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_Y, + 0, + 100); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_Y); + + // Ensure that only the first view's Y translation chained, since we only chained X + // translations. + assertEquals(100, mLayout.getChildAt(0).getTranslationY(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationY(), .1f); + } + + /** + * Animation controller with configuration methods whose return values can be set by individual + * tests. + */ + private class TestableAnimationController + extends PhysicsAnimationLayout.PhysicsAnimationController { + private Set mAnimatedProperties = new HashSet<>(); + private Set mChainedProperties = new HashSet<>(); + private HashMap mOffsetForProperty = new HashMap<>(); + private boolean mRemoveImmediately = false; + + void setAnimatedProperties( + Set animatedProperties) { + mAnimatedProperties = animatedProperties; + } + + void setChainedProperties( + Set chainedProperties) { + mChainedProperties = chainedProperties; + } + + void setOffsetForProperty( + DynamicAnimation.ViewProperty property, float offset) { + mOffsetForProperty.put(property, offset); + } + + public void setRemoveImmediately(boolean removeImmediately) { + mRemoveImmediately = removeImmediately; + } + + @Override + Set getAnimatedProperties() { + return mAnimatedProperties; + } + + @Override + int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) { + return mChainedProperties.contains(property) ? index + 1 : NONE; + } + + @Override + float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) { + return mOffsetForProperty.getOrDefault(property, 0f); + } + + @Override + SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) { + return new SpringForce(); + } + + @Override + void onChildAdded(View child, int index) {} + + @Override + void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { + if (mRemoveImmediately) { + actuallyRemove.run(); + } + } + } +} 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 new file mode 100644 index 0000000000000..186a76219536a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.view.DisplayCutout; +import android.view.View; +import android.view.WindowInsets; +import android.widget.FrameLayout; + +import androidx.dynamicanimation.animation.DynamicAnimation; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Test case for tests that involve the {@link PhysicsAnimationLayout}. This test case constructs a + * testable version of the layout, and provides some helpful methods to add views to the layout and + * wait for physics animations to finish running. + * + * See physics-animation-testing.md. + */ +public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { + TestablePhysicsAnimationLayout mLayout; + List mViews = new ArrayList<>(); + + Handler mMainThreadHandler; + + int mMaxRenderedBubbles; + int mSystemWindowInsetSize = 50; + int mCutoutInsetSize = 100; + + int mWidth = 1000; + int mHeight = 1000; + + @Mock + private WindowInsets mWindowInsets; + + @Mock + private DisplayCutout mCutout; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mLayout = new TestablePhysicsAnimationLayout(mContext); + mLayout.setLeft(0); + mLayout.setRight(mWidth); + mLayout.setTop(0); + mLayout.setBottom(mHeight); + + mMaxRenderedBubbles = + getContext().getResources().getInteger(R.integer.bubbles_max_rendered); + mMainThreadHandler = new Handler(Looper.getMainLooper()); + + when(mWindowInsets.getSystemWindowInsetTop()).thenReturn(mSystemWindowInsetSize); + when(mWindowInsets.getSystemWindowInsetBottom()).thenReturn(mSystemWindowInsetSize); + when(mWindowInsets.getSystemWindowInsetLeft()).thenReturn(mSystemWindowInsetSize); + when(mWindowInsets.getSystemWindowInsetRight()).thenReturn(mSystemWindowInsetSize); + + when(mWindowInsets.getDisplayCutout()).thenReturn(mCutout); + when(mCutout.getSafeInsetTop()).thenReturn(mCutoutInsetSize); + when(mCutout.getSafeInsetBottom()).thenReturn(mCutoutInsetSize); + when(mCutout.getSafeInsetLeft()).thenReturn(mCutoutInsetSize); + when(mCutout.getSafeInsetRight()).thenReturn(mCutoutInsetSize); + } + + /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */ + void addOneMoreThanRenderLimitBubbles() { + for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { + final View newView = new FrameLayout(mContext); + mLayout.addView(newView, 0); + mViews.add(0, newView); + + newView.setTranslationX(0); + newView.setTranslationY(0); + } + } + + /** + * Uses a {@link java.util.concurrent.CountDownLatch} to wait for the given properties' + * animations to finish before allowing the test to proceed. + */ + void waitForPropertyAnimations(DynamicAnimation.ViewProperty... properties) + throws InterruptedException { + final CountDownLatch animLatch = new CountDownLatch(properties.length); + for (DynamicAnimation.ViewProperty property : properties) { + mLayout.setTestEndListenerForProperty(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, + float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + animLatch.countDown(); + } + }, property); + } + animLatch.await(1, TimeUnit.SECONDS); + } + + /** Uses a latch to wait for the message queue to finish. */ + void waitForLayoutMessageQueue() throws InterruptedException { + // Wait for layout, then the view should be actually removed. + CountDownLatch layoutLatch = new CountDownLatch(1); + mLayout.post(layoutLatch::countDown); + layoutLatch.await(1, TimeUnit.SECONDS); + } + + /** + * Testable subclass of the PhysicsAnimationLayout that ensures methods that trigger animations + * are run on the main thread, which is a requirement of DynamicAnimation. + */ + protected class TestablePhysicsAnimationLayout extends PhysicsAnimationLayout { + public TestablePhysicsAnimationLayout(Context context) { + super(context); + } + + @Override + public void setController(PhysicsAnimationController controller) { + mMainThreadHandler.post(() -> super.setController(controller)); + try { + waitForLayoutMessageQueue(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Override + public void cancelAllAnimations() { + mMainThreadHandler.post(super::cancelAllAnimations); + } + + @Override + protected void animateValueForChildAtIndex(DynamicAnimation.ViewProperty property, + int index, float value, float startVel, Runnable after) { + mMainThreadHandler.post(() -> + super.animateValueForChildAtIndex(property, index, value, startVel, after)); + } + + @Override + public WindowInsets getRootWindowInsets() { + return mWindowInsets; + } + + /** + * Sets an end listener that will be called after the 'real' end listener that was already + * set. + */ + private void setTestEndListenerForProperty(DynamicAnimation.OnAnimationEndListener listener, + DynamicAnimation.ViewProperty property) { + final DynamicAnimation.OnAnimationEndListener realEndListener = + mEndListenerForProperty.get(property); + + setEndListenerForProperty((animation, canceled, value, velocity) -> { + if (realEndListener != null) { + realEndListener.onAnimationEnd(animation, canceled, value, velocity); + } + + listener.onAnimationEnd(animation, canceled, value, velocity); + }, property); + } + } +} 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 new file mode 100644 index 0000000000000..0f686df87ca5d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import static org.junit.Assert.assertEquals; + +import android.graphics.PointF; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.systemui.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Spy; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase { + + @Spy + private TestableStackController mStackController = new TestableStackController(); + + private int mStackOffset; + + @Before + public void setUp() throws Exception { + super.setUp(); + addOneMoreThanRenderLimitBubbles(); + mLayout.setController(mStackController); + mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset); + } + + /** + * Test moving around the stack, and make sure the position is updated correctly, and the stack + * direction is correct. + */ + @Test + public void testMoveFirstBubbleWithStackFollowing() throws InterruptedException { + mStackController.moveFirstBubbleWithStackFollowing(200, 100); + + // The first bubble should have moved instantly, the rest should be waiting for animation. + assertEquals(200, mViews.get(0).getTranslationX(), .1f); + assertEquals(100, mViews.get(0).getTranslationY(), .1f); + assertEquals(0, mViews.get(1).getTranslationX(), .1f); + assertEquals(0, mViews.get(1).getTranslationY(), .1f); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + // Make sure the rest of the stack got moved to the right place and is stacked to the left. + testStackedAtPosition(200, 100, -1); + assertEquals(new PointF(200, 100), mStackController.getStackPosition()); + + mStackController.moveFirstBubbleWithStackFollowing(1000, 500); + + // The first bubble again should have moved instantly while the rest remained where they + // were until the animation takes over. + assertEquals(1000, mViews.get(0).getTranslationX(), .1f); + assertEquals(500, mViews.get(0).getTranslationY(), .1f); + assertEquals(200 + -mStackOffset, mViews.get(1).getTranslationX(), .1f); + assertEquals(100, mViews.get(1).getTranslationY(), .1f); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + // Make sure the rest of the stack moved again, including the first bubble not moving, and + // is stacked to the right now that we're on the right side of the screen. + testStackedAtPosition(1000, 500, 1); + assertEquals(new PointF(1000, 500), mStackController.getStackPosition()); + } + + @Test + public void testFlingSideways() throws InterruptedException { + // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much + // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top + // but should bounce back down. + mStackController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_X, + 5000f, 1.15f, new SpringForce(), mWidth * 1f); + mStackController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_Y, + 0f, 1.15f, new SpringForce(), 0f); + + // Nothing should move initially since the animations haven't begun, including the first + // view. + assertEquals(0f, mViews.get(0).getTranslationX(), 1f); + assertEquals(0f, mViews.get(0).getTranslationY(), 1f); + + // Wait for the flinging. + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + + // Wait for the springing. + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + + // Once the dust has settled, we should have flung all the way to the right side, with the + // stack stacked off to the right now. + testStackedAtPosition(mWidth * 1f, 0f, 1); + } + + @Test + public void testFlingUpFromBelowBottomCenter() throws InterruptedException { + // Move to the center of the screen, just past the bottom. + mStackController.moveFirstBubbleWithStackFollowing(mWidth / 2f, mHeight + 100); + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much + // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top + // but should bounce back down. + mStackController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_X, + 0, 1.15f, new SpringForce(), 27f); + mStackController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_Y, + 5000f, 1.15f, new SpringForce(), 27f); + + // Nothing should move initially since the animations haven't begun. + assertEquals(mWidth / 2f, mViews.get(0).getTranslationX(), .1f); + assertEquals(mHeight + 100, mViews.get(0).getTranslationY(), .1f); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + + // Once the dust has settled, we should have flung a bit but then sprung to the final + // destination which is (27, 27). + testStackedAtPosition(27, 27, -1); + } + + @Test + public void testChildAdded() throws InterruptedException { + // Move the stack to y = 500. + mStackController.moveFirstBubbleWithStackFollowing(0f, 500f); + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + + final View newView = new FrameLayout(mContext); + mLayout.addView( + newView, + 0, + new FrameLayout.LayoutParams(50, 50)); + + waitForPropertyAnimations( + DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y, + DynamicAnimation.SCALE_X, + DynamicAnimation.SCALE_Y); + + // The new view should be at the top of the stack, in the correct position. + assertEquals(0f, newView.getTranslationX(), .1f); + assertEquals(500f, newView.getTranslationY(), .1f); + assertEquals(1f, newView.getScaleX(), .1f); + assertEquals(1f, newView.getScaleY(), .1f); + assertEquals(1f, newView.getAlpha(), .1f); + } + + @Test + public void testChildRemoved() throws InterruptedException { + final View firstView = mLayout.getChildAt(0); + mLayout.removeView(firstView); + + // The view should still be there, since the controller is animating it out and hasn't yet + // actually removed it from the parent view. + assertEquals(0, mLayout.indexOfChild(firstView)); + + waitForPropertyAnimations(DynamicAnimation.ALPHA); + waitForLayoutMessageQueue(); + + assertEquals(-1, mLayout.indexOfChild(firstView)); + + // The subsequent view should have been translated over to 0, not stacked off to the left. + assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f); + } + + /** + * Checks every child view to make sure it's stacked at the given coordinates, off to the left + * or right side depending on offset multiplier. + */ + private void testStackedAtPosition(float x, float y, int offsetMultiplier) { + // Make sure the rest of the stack moved again, including the first bubble not moving, and + // is stacked to the right now that we're on the right side of the screen. + for (int i = 0; i < mLayout.getChildCount(); i++) { + assertEquals(x + i * offsetMultiplier * mStackOffset, + mViews.get(i).getTranslationX(), 2f); + assertEquals(y, mViews.get(i).getTranslationY(), 2f); + } + } + + /** + * Testable version of the stack controller that dispatches its animations on the main thread. + */ + private class TestableStackController extends StackAnimationController { + @Override + public void flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.ViewProperty property, float vel, float friction, + SpringForce spring, Float finalPosition) { + mMainThreadHandler.post(() -> + super.flingThenSpringFirstBubbleWithStackFollowing( + property, vel, friction, spring, finalPosition)); + } + } +}