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

Commit 875b0000 authored by Joshua Tsuji's avatar Joshua Tsuji
Browse files

Remember the stack position, including across configuration changes.

Bug: 160178651
Test: move bubbles a bunch while rotating
Test: move bubbles to bottom of screen in portrait, dismiss them, rotate to landscape, add bubbles again
Change-Id: I01dcd0c5ef5997553d5869488c5a2b8494171bba
parent 556f656b
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -82,6 +82,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
import com.android.systemui.bubbles.animation.StackAnimationController;
import com.android.systemui.bubbles.dagger.BubbleModule;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
@@ -168,6 +169,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
    @Nullable private BubbleStackView mStackView;
    private BubbleIconFactory mBubbleIconFactory;

    /**
     * The relative position of the stack when we removed it and nulled it out. If the stack is
     * re-created, it will re-appear at this position.
     */
    @Nullable private BubbleStackView.RelativeStackPosition mPositionFromRemovedStack;

    // Tracks the id of the current (foreground) user.
    private int mCurrentUserId;
    // Saves notification keys of active bubbles when users are switched.
@@ -718,6 +725,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                    mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
                    mSysUiState, this::onAllBubblesAnimatedOut, this::onImeVisibilityChanged,
                    this::hideCurrentInputMethod);
            mStackView.setStackStartPosition(mPositionFromRemovedStack);
            mStackView.addView(mBubbleScrim);
            if (mExpandListener != null) {
                mStackView.setExpandListener(mExpandListener);
@@ -788,6 +796,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
        try {
            mAddedToWindowManager = false;
            if (mStackView != null) {
                mPositionFromRemovedStack = mStackView.getRelativeStackPosition();
                mWindowManager.removeView(mStackView);
                mStackView.removeView(mBubbleScrim);
                mStackView = null;
+60 −19
Original line number Diff line number Diff line
@@ -253,13 +253,8 @@ public class BubbleStackView extends FrameLayout

    /** Layout change listener that moves the stack to the nearest valid position on rotation. */
    private OnLayoutChangeListener mOrientationChangedListener;
    /** Whether the stack was on the left side of the screen prior to rotation. */
    private boolean mWasOnLeftBeforeRotation = false;
    /**
     * How far down the screen the stack was before rotation, in terms of percentage of the way down
     * the allowable region. Defaults to -1 if not set.
     */
    private float mVerticalPosPercentBeforeRotation = -1;

    @Nullable private RelativeStackPosition mRelativeStackPositionBeforeRotation;

    private int mMaxBubbles;
    private int mBubbleSize;
@@ -940,9 +935,10 @@ public class BubbleStackView extends FrameLayout
                        mExpandedViewContainer.setTranslationY(getExpandedViewY());
                        mExpandedViewContainer.setAlpha(1f);
                    }
                    if (mVerticalPosPercentBeforeRotation >= 0) {
                        mStackAnimationController.moveStackToSimilarPositionAfterRotation(
                                mWasOnLeftBeforeRotation, mVerticalPosPercentBeforeRotation);
                    if (mRelativeStackPositionBeforeRotation != null) {
                        mStackAnimationController.setStackPosition(
                                mRelativeStackPositionBeforeRotation);
                        mRelativeStackPositionBeforeRotation = null;
                    }
                    removeOnLayoutChangeListener(mOrientationChangedListener);
                };
@@ -1190,13 +1186,7 @@ public class BubbleStackView extends FrameLayout
                com.android.internal.R.dimen.status_bar_height);
        mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);

        final RectF allowablePos = mStackAnimationController.getAllowableStackPositionRegion();
        mWasOnLeftBeforeRotation = mStackAnimationController.isStackOnLeftSide();
        mVerticalPosPercentBeforeRotation =
                (mStackAnimationController.getStackPosition().y - allowablePos.top)
                        / (allowablePos.bottom - allowablePos.top);
        mVerticalPosPercentBeforeRotation =
                Math.max(0f, Math.min(1f, mVerticalPosPercentBeforeRotation));
        mRelativeStackPositionBeforeRotation = mStackAnimationController.getRelativeStackPosition();
        addOnLayoutChangeListener(mOrientationChangedListener);
        hideFlyoutImmediate();

@@ -1460,7 +1450,7 @@ public class BubbleStackView extends FrameLayout
        if (getBubbleCount() == 0 && mShouldShowUserEducation) {
            // Override the default stack position if we're showing user education.
            mStackAnimationController.setStackPosition(
                    mStackAnimationController.getDefaultStartPosition());
                    mStackAnimationController.getStartPosition());
        }

        if (getBubbleCount() == 0) {
@@ -1675,7 +1665,7 @@ public class BubbleStackView extends FrameLayout
            // Post so we have height of mUserEducationView
            mUserEducationView.post(() -> {
                final int viewHeight = mUserEducationView.getHeight();
                PointF stackPosition = mStackAnimationController.getDefaultStartPosition();
                PointF stackPosition = mStackAnimationController.getStartPosition();
                final float translationY = stackPosition.y + (mBubbleSize / 2) - (viewHeight / 2);
                mUserEducationView.setTranslationY(translationY);
                mUserEducationView.animate()
@@ -2771,10 +2761,18 @@ public class BubbleStackView extends FrameLayout
                .floatValue();
    }

    public void setStackStartPosition(RelativeStackPosition position) {
        mStackAnimationController.setStackStartPosition(position);
    }

    public PointF getStackPosition() {
        return mStackAnimationController.getStackPosition();
    }

    public RelativeStackPosition getRelativeStackPosition() {
        return mStackAnimationController.getRelativeStackPosition();
    }

    /**
     * Logs the bubble UI event.
     *
@@ -2828,4 +2826,47 @@ public class BubbleStackView extends FrameLayout
        }
        return bubbles;
    }

    /**
     * Representation of stack position that uses relative properties rather than absolute
     * coordinates. This is used to maintain similar stack positions across configuration changes.
     */
    public static class RelativeStackPosition {
        /** Whether to place the stack at the leftmost allowed position. */
        private boolean mOnLeft;

        /**
         * How far down the vertically allowed region to place the stack. For example, if the stack
         * allowed region is between y = 100 and y = 1100 and this is 0.2f, we'll place the stack at
         * 100 + (0.2f * 1000) = 300.
         */
        private float mVerticalOffsetPercent;

        public RelativeStackPosition(boolean onLeft, float verticalOffsetPercent) {
            mOnLeft = onLeft;
            mVerticalOffsetPercent = clampVerticalOffsetPercent(verticalOffsetPercent);
        }

        /** Constructs a relative position given a region and a point in that region. */
        public RelativeStackPosition(PointF position, RectF region) {
            mOnLeft = position.x < region.width() / 2;
            mVerticalOffsetPercent =
                    clampVerticalOffsetPercent((position.y - region.top) / region.height());
        }

        /** Ensures that the offset percent is between 0f and 1f. */
        private float clampVerticalOffsetPercent(float offsetPercent) {
            return Math.max(0f, Math.min(1f, offsetPercent));
        }

        /**
         * Given an allowable stack position region, returns the point within that region
         * represented by this relative position.
         */
        public PointF getAbsolutePositionInRegion(RectF region) {
            return new PointF(
                    mOnLeft ? region.left : region.right,
                    region.top + mVerticalOffsetPercent * region.height());
        }
    }
}
+47 −26
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;

import com.android.systemui.R;
import com.android.systemui.bubbles.BubbleStackView;
import com.android.systemui.util.FloatingContentCoordinator;
import com.android.systemui.util.animation.PhysicsAnimator;
import com.android.systemui.util.magnetictarget.MagnetizedObject;
@@ -125,6 +126,9 @@ public class StackAnimationController extends
     */
    private Rect mAnimatingToBounds = new Rect();

    /** Initial starting location for the stack. */
    @Nullable private BubbleStackView.RelativeStackPosition mStackStartPosition;

    /** Whether or not the stack's start position has been set. */
    private boolean mStackMovedToStartPosition = false;

@@ -431,21 +435,6 @@ public class StackAnimationController extends
        return stackPos;
    }

    /**
     * Moves the stack in response to rotation. We keep it in the most similar position by keeping
     * it on the same side, and positioning it the same percentage of the way down the screen
     * (taking status bar/nav bar into account by using the allowable region's height).
     */
    public void moveStackToSimilarPositionAfterRotation(boolean wasOnLeft, float verticalPercent) {
        final RectF allowablePos = getAllowableStackPositionRegion();
        final float allowableRegionHeight = allowablePos.bottom - allowablePos.top;

        final float x = wasOnLeft ? allowablePos.left : allowablePos.right;
        final float y = (allowableRegionHeight * verticalPercent) + allowablePos.top;

        setStackPosition(new PointF(x, y));
    }

    /** Description of current animation controller state. */
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("StackAnimationController state:");
@@ -815,7 +804,7 @@ public class StackAnimationController extends
        } else {
            // When all children are removed ensure stack position is sane
            setStackPosition(mRestingStackPosition == null
                    ? getDefaultStartPosition()
                    ? getStartPosition()
                    : mRestingStackPosition);

            // Remove the stack from the coordinator since we don't have any bubbles and aren't
@@ -868,7 +857,7 @@ public class StackAnimationController extends
        mLayout.setVisibility(View.INVISIBLE);
        mLayout.post(() -> {
            setStackPosition(mRestingStackPosition == null
                    ? getDefaultStartPosition()
                    ? getStartPosition()
                    : mRestingStackPosition);
            mStackMovedToStartPosition = true;
            mLayout.setVisibility(View.VISIBLE);
@@ -938,15 +927,47 @@ public class StackAnimationController extends
        }
    }

    /** Returns the default stack position, which is on the top left. */
    public PointF getDefaultStartPosition() {
        boolean isRtl = mLayout != null
                && mLayout.getResources().getConfiguration().getLayoutDirection()
                == View.LAYOUT_DIRECTION_RTL;
        return new PointF(isRtl
                        ? getAllowableStackPositionRegion().right
                        : getAllowableStackPositionRegion().left,
                getAllowableStackPositionRegion().top + mStackStartingVerticalOffset);
    public void setStackPosition(BubbleStackView.RelativeStackPosition position) {
        setStackPosition(position.getAbsolutePositionInRegion(getAllowableStackPositionRegion()));
    }

    public BubbleStackView.RelativeStackPosition getRelativeStackPosition() {
        return new BubbleStackView.RelativeStackPosition(
                mStackPosition, getAllowableStackPositionRegion());
    }

    /**
     * Sets the starting position for the stack, where it will be located when the first bubble is
     * added.
     */
    public void setStackStartPosition(BubbleStackView.RelativeStackPosition position) {
        mStackStartPosition = position;
    }

    /**
     * Returns the starting stack position. If {@link #setStackStartPosition} was called, this will
     * return that position - otherwise, a reasonable default will be returned.
     */
    @Nullable public PointF getStartPosition() {
        if (mLayout == null) {
            return null;
        }

        if (mStackStartPosition == null) {
            // Start on the left if we're in LTR, right otherwise.
            final boolean startOnLeft =
                    mLayout.getResources().getConfiguration().getLayoutDirection()
                            != View.LAYOUT_DIRECTION_RTL;

            final float startingVerticalOffset = mLayout.getResources().getDimensionPixelOffset(
                    R.dimen.bubble_stack_starting_offset_y);

            mStackStartPosition = new BubbleStackView.RelativeStackPosition(
                    startOnLeft,
                    startingVerticalOffset / getAllowableStackPositionRegion().height());
        }

        return mStackStartPosition.getAbsolutePositionInRegion(getAllowableStackPositionRegion());
    }

    private boolean isStackPositionSet() {