Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e13b4fce authored by Joshua Tsuji's avatar Joshua Tsuji
Browse files

Save the stack position on dismiss, so new bubbles appear in the original position.

Also, moves the fling/spring logic into the StackAnimationController. It logically belongs there anyway, and also it makes it easier to save the resting position when the fling/spring animations are complete.

Fixes: 126726949
Test: atest SystemUITests
Change-Id: If788e08d1401016bb1135416d495fbb5abf82cbb
parent 4348a4d7
Loading
Loading
Loading
Loading
+1 −78
Original line number Diff line number Diff line
@@ -63,26 +63,6 @@ import java.math.RoundingMode;
public class BubbleStackView extends FrameLayout {
    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 final SpringAnimation mExpandedViewXAnim;
@@ -653,50 +633,7 @@ public class BubbleStackView extends FrameLayout {
            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);

        mStackAnimationController.flingStackThenSpringToEdge(x, velX, velY);
        logBubbleEvent(null /* no bubble associated with bubble stack move */,
                StatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
    }
@@ -741,20 +678,6 @@ public class BubbleStackView extends FrameLayout {
        }
    }

    /**
     * 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) {
+113 −10
Original line number Diff line number Diff line
@@ -60,6 +60,26 @@ public class StackAnimationController extends
    private static final float DEFAULT_STIFFNESS = 2500f;
    private static final float DEFAULT_BOUNCINESS = 0.85f;

    /**
     * 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;

    /**
     * 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
@@ -67,6 +87,9 @@ public class StackAnimationController extends
     */
    private PointF mStackPosition = new PointF();

    /** The most recent position in which the stack was resting on the edge of the screen. */
    private PointF mRestingStackPosition;

    /** The height of the most recently visible IME. */
    private float mImeHeight = 0f;

@@ -134,6 +157,77 @@ public class StackAnimationController extends
        return mStackPosition;
    }

    /**
     * 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;
    }

    /**
     * Flings the stack starting with the given velocities, springing it to the nearest edge
     * afterward.
     */
    public void flingStackThenSpringToEdge(float x, float velX, float velY) {
        final boolean stackOnLeftSide = x - mIndividualBubbleSize / 2 < mLayout.getWidth() / 2;

        final boolean stackShouldFlingLeft = stackOnLeftSide
                ? velX < ESCAPE_VELOCITY
                : velX < -ESCAPE_VELOCITY;

        final RectF stackBounds = 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);

        flingThenSpringFirstBubbleWithStackFollowing(
                DynamicAnimation.TRANSLATION_X,
                startXVelocity,
                FLING_FRICTION_X,
                new SpringForce()
                        .setStiffness(SpringForce.STIFFNESS_LOW)
                        .setDampingRatio(SPRING_DAMPING_RATIO),
                destinationRelativeX);

        flingThenSpringFirstBubbleWithStackFollowing(
                DynamicAnimation.TRANSLATION_Y,
                velY,
                FLING_FRICTION_Y,
                new SpringForce()
                        .setStiffness(SpringForce.STIFFNESS_LOW)
                        .setDampingRatio(SPRING_DAMPING_RATIO),
                /* destination */ null);

        mLayout.setEndListenerForProperties(
                (animation, canceled, value, velocity) -> {
                    mRestingStackPosition = new PointF();
                    mRestingStackPosition.set(mStackPosition);
                    mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_X);
                    mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_Y);
                },
                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
    }

    /**
     * Where the stack would be if it were snapped to the nearest horizontal edge (left or right).
     */
@@ -152,7 +246,7 @@ public class StackAnimationController extends
     * reducing momentum - a SpringAnimation takes over to snap the bubble to the given final
     * position.
     */
    public void flingThenSpringFirstBubbleWithStackFollowing(
    protected void flingThenSpringFirstBubbleWithStackFollowing(
            DynamicAnimation.ViewProperty property,
            float vel,
            float friction,
@@ -360,8 +454,7 @@ public class StackAnimationController extends
    @Override
    void onChildRemoved(View child, int index, Runnable finishRemoval) {
        // Animate the child out, actually removing it once its alpha is zero.
        mLayout.animateValueForChild(
                DynamicAnimation.ALPHA, child, 0f, finishRemoval);
        mLayout.animateValueForChild(DynamicAnimation.ALPHA, child, 0f, finishRemoval);
        mLayout.animateValueForChild(DynamicAnimation.SCALE_X, child, ANIMATE_IN_STARTING_SCALE);
        mLayout.animateValueForChild(DynamicAnimation.SCALE_Y, child, ANIMATE_IN_STARTING_SCALE);

@@ -378,10 +471,13 @@ public class StackAnimationController extends
    /** Moves the stack, without any animation, to the starting position. */
    private void moveStackToStartPosition(Runnable after) {
        // Post to ensure that the layout's width and height have been calculated.
        mLayout.setVisibility(View.INVISIBLE);
        mLayout.post(() -> {
            setStackPosition(
                    getAllowableStackPositionRegion().right,
                    getAllowableStackPositionRegion().top + mStackStartingVerticalOffset);
                    mRestingStackPosition == null
                            ? getDefaultStartPosition()
                            : mRestingStackPosition);
            mLayout.setVisibility(View.VISIBLE);
            after.run();
        });
    }
@@ -410,9 +506,9 @@ public class StackAnimationController extends
    }

    /** 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);
    private void setStackPosition(PointF pos) {
        Log.d(TAG, String.format("Setting position to (%f, %f).", pos.x, pos.y));
        mStackPosition.set(pos.x, pos.y);

        cancelStackPositionAnimations();

@@ -420,9 +516,16 @@ public class StackAnimationController extends
        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));
            mLayout.getChildAt(i).setTranslationX(pos.x + (i * xOffset));
            mLayout.getChildAt(i).setTranslationY(pos.y + (i * yOffset));
        }
    }

    /** Returns the default stack position, which is on the top right. */
    private PointF getDefaultStartPosition() {
        return new PointF(
                getAllowableStackPositionRegion().right,
                getAllowableStackPositionRegion().top + mStackStartingVerticalOffset);
    }

    /** Animates in the given bubble. */
+21 −1
Original line number Diff line number Diff line
@@ -197,6 +197,26 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase
        assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f);
    }

    @Test
    public void testRestoredAtRestingPosition() throws InterruptedException {
        mStackController.flingStackThenSpringToEdge(0, 5000, 5000);

        waitForPropertyAnimations(
                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
        waitForLayoutMessageQueue();

        final PointF prevStackPos = mStackController.getStackPosition();

        mLayout.removeAllViews();
        mLayout.addView(new FrameLayout(getContext()));

        waitForLayoutMessageQueue();
        waitForPropertyAnimations(
                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);

        assertEquals(prevStackPos, mStackController.getStackPosition());
    }

    /**
     * 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.
@@ -216,7 +236,7 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase
     */
    private class TestableStackController extends StackAnimationController {
        @Override
        public void flingThenSpringFirstBubbleWithStackFollowing(
        protected void flingThenSpringFirstBubbleWithStackFollowing(
                DynamicAnimation.ViewProperty property, float vel, float friction,
                SpringForce spring, Float finalPosition) {
            mMainThreadHandler.post(() ->