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

Commit cba1b858 authored by Mady Mellor's avatar Mady Mellor
Browse files

Shift bubbles for IME on large screens

* Incorporate IME state when calculating the x/y position
  of the bubbles -- if the IME is up, shift the bubbles
  so they don't overlap it (if possible).
* When the IME visibility changes:
    - positioner saves the state
    - stackview animates the bubbles to their new position

Test: manual - open the IME with bubbles on a large screen
             - check that the IME pushes the bubbles up
	       so they are not overlapping
             - check that IME on phone portrait & landscape
               works as it did before
Bug: 193911220
Change-Id: I3c93e472353cde5c938e33edf2d1ae7a4141db0e
parent 936f2884
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -144,7 +144,6 @@ public class BubbleController {

    private BubbleLogger mLogger;
    private BubbleData mBubbleData;
    private View mBubbleScrim;
    @Nullable private BubbleStackView mStackView;
    private BubbleIconFactory mBubbleIconFactory;
    private BubblePositioner mBubblePositioner;
@@ -1372,8 +1371,9 @@ public class BubbleController {
    private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedTaskListener {
        @Override
        public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
            mBubblePositioner.setImeVisible(imeVisible, imeHeight);
            if (mStackView != null) {
                mStackView.onImeVisibilityChanged(imeVisible, imeHeight);
                mStackView.animateForIme(imeVisible);
            }
        }
    }
+82 −10
Original line number Diff line number Diff line
@@ -70,13 +70,16 @@ public class BubblePositioner {

    private Context mContext;
    private WindowManager mWindowManager;
    private Rect mPositionRect;
    private Rect mScreenRect;
    private @Surface.Rotation int mRotation = Surface.ROTATION_0;
    private Insets mInsets;
    private boolean mImeVisible;
    private int mImeHeight;
    private boolean mIsLargeScreen;

    private Rect mPositionRect;
    private int mDefaultMaxBubbles;
    private int mMaxBubbles;

    private int mBubbleSize;
    private int mSpacingBetweenBubbles;

@@ -98,7 +101,6 @@ public class BubblePositioner {
    private PointF mRestingStackPosition;
    private int[] mPaddings = new int[4];

    private boolean mIsLargeScreen;
    private boolean mShowingInTaskbar;
    private @TaskbarPosition int mTaskbarPosition = TASKBAR_POSITION_NONE;
    private int mTaskbarIconSize;
@@ -302,6 +304,17 @@ public class BubblePositioner {
        return mMaxBubbles;
    }

    /** The height for the IME if it's visible. **/
    public int getImeHeight() {
        return mImeVisible ? mImeHeight : 0;
    }

    /** Sets whether the IME is visible. **/
    public void setImeVisible(boolean visible, int height) {
        mImeVisible = visible;
        mImeHeight = height;
    }

    /**
     * Calculates the padding for the bubble expanded view.
     *
@@ -357,7 +370,7 @@ public class BubblePositioner {
    }

    /** Gets the y position of the expanded view if it was top-aligned. */
    private float getExpandedViewYTopAligned() {
    public float getExpandedViewYTopAligned() {
        final int top = getAvailableRect().top;
        if (showBubblesVertically()) {
            return top - mPointerWidth + mExpandedViewPadding;
@@ -366,10 +379,6 @@ public class BubblePositioner {
        }
    }

    public float getExpandedBubblesY() {
        return getAvailableRect().top + mExpandedViewPadding;
    }

    /**
     * Calculate the maximum height the expanded view can be depending on where it's placed on
     * the screen and the size of the elements around it (e.g. padding, pointer, manage button).
@@ -464,6 +473,11 @@ public class BubblePositioner {
                : bubblePosition + (normalizedSize / 2f) - mPointerWidth;
    }

    private int getExpandedStackSize(int numberOfBubbles) {
        return (numberOfBubbles * mBubbleSize)
                + ((numberOfBubbles - 1) * mSpacingBetweenBubbles);
    }

    /**
     * Returns the position of the bubble on-screen when the stack is expanded.
     *
@@ -474,8 +488,7 @@ public class BubblePositioner {
     */
    public PointF getExpandedBubbleXY(int index, int numberOfBubbles, boolean onLeftEdge) {
        final float positionInRow = index * (mBubbleSize + mSpacingBetweenBubbles);
        final float expandedStackSize = (numberOfBubbles * mBubbleSize)
                + ((numberOfBubbles - 1) * mSpacingBetweenBubbles);
        final float expandedStackSize = getExpandedStackSize(numberOfBubbles);
        final float centerPosition = showBubblesVertically()
                ? mPositionRect.centerY()
                : mPositionRect.centerX();
@@ -498,9 +511,68 @@ public class BubblePositioner {
            y = mPositionRect.top + mExpandedViewPadding;
            x = rowStart + positionInRow;
        }

        if (showBubblesVertically() && mImeVisible) {
            return new PointF(x, getExpandedBubbleYForIme(index, numberOfBubbles));
        }
        return new PointF(x, y);
    }


    /**
     * Returns the position of the bubble on-screen when the stack is expanded and the IME
     * is showing.
     *
     * @param index the index of the bubble in the stack.
     * @param numberOfBubbles the total number of bubbles in the stack.
     * @return y position of the bubble on-screen when the stack is expanded.
     */
    private float getExpandedBubbleYForIme(int index, int numberOfBubbles) {
        final float top = getAvailableRect().top + mExpandedViewPadding;
        if (!showBubblesVertically()) {
            // Showing horizontally: align to top
            return top;
        }
        // Showing vertically: align to edges, check if there's overlap with the IME and adjust.
        final float expandedStackSize = getExpandedStackSize(numberOfBubbles);
        final float centerPosition = showBubblesVertically()
                ? mPositionRect.centerY()
                : mPositionRect.centerX();
        final float rowTop = centerPosition - (expandedStackSize / 2f);
        float rowTopAdjusted = Math.max(rowTop - getTranslationForIme(0, numberOfBubbles), top);
        return rowTopAdjusted + (index * (mBubbleSize + mSpacingBetweenBubbles));
    }

    private float getTranslationForIme(int selectedIndex, int numberOfBubbles) {
        if (!showBubblesVertically()) {
            // Showing at the top, no need to adjust for IME.
            return 0;
        }
        // Showing vertically: if there are enough bubbles, need to translate
        final float top = getAvailableRect().top + mExpandedViewPadding;
        final float bottomInset = getImeHeight() + mInsets.bottom - mExpandedViewPadding;
        final float expandedStackSize = getExpandedStackSize(numberOfBubbles);
        final float centerPosition = showBubblesVertically()
                ? mPositionRect.centerY()
                : mPositionRect.centerX();
        final float rowBottom = centerPosition + (expandedStackSize / 2f);
        final float rowTop = centerPosition - (expandedStackSize / 2f);

        if (rowBottom > bottomInset) {
            // We overlap with IME, must shift the bubbles
            float translationY = rowBottom - bottomInset;
            if (rowTop - translationY < top) {
                // Even if we shift the bubbles, they will still overlap with the IME.

                // TODO: in the case that the selected bubble is the one that overlaps with
                // the IME, we should allow the bubbles to shift further "up" and potentially
                // go offscreen so that the selected one is visible.
            }
            return translationY;
        }
        return 0;
    }

    /**
     * @return the width of the bubble flyout (message originating from the bubble).
     */
+37 −12
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@ import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RES

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
@@ -246,7 +248,6 @@ public class BubbleStackView extends FrameLayout
    private int mBubbleTouchPadding;
    private int mExpandedViewPadding;
    private int mCornerRadius;
    private int mImeOffset;
    @Nullable private BubbleViewProvider mExpandedBubble;
    private boolean mIsExpanded;

@@ -757,7 +758,6 @@ public class BubbleStackView extends FrameLayout
        mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
        mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
        mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
        mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);

        mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
        int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
@@ -1762,6 +1762,7 @@ public class BubbleStackView extends FrameLayout
     * not.
     */
    void hideCurrentInputMethod() {
        mPositioner.setImeVisible(false, 0);
        mBubbleController.hideCurrentInputMethod();
    }

@@ -2187,9 +2188,20 @@ public class BubbleStackView extends FrameLayout
        }
    }

    /** Moves the bubbles out of the way if they're going to be over the keyboard. */
    public void onImeVisibilityChanged(boolean visible, int height) {
        mStackAnimationController.setImeHeight(visible ? height + mImeOffset : 0);
    /**
     * Updates the stack based for IME changes. When collapsed it'll move the stack if it
     * overlaps where they IME would be. When expanded it'll shift the expanded bubbles
     * if they might overlap with the IME (this only happens for large screens).
     */
    public void animateForIme(boolean visible) {
        if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
            // This will update the animation so the bubbles move to position for the IME
            mExpandedAnimationController.expandFromStack(() -> {
                updatePointerPosition();
                afterExpandedViewAnimation();
            } /* after */);
            return;
        }

        if (!mIsExpanded && getBubbleCount() > 0) {
            final float stackDestinationY =
@@ -2208,9 +2220,21 @@ public class BubbleStackView extends FrameLayout
                                FLYOUT_IME_ANIMATION_SPRING_CONFIG)
                        .start();
            }
        } else if (mIsExpanded && mExpandedBubble != null
                && mExpandedBubble.getExpandedView() != null) {
        } else if (mPositioner.showBubblesVertically() && mIsExpanded
                && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
            mExpandedBubble.getExpandedView().setImeVisible(visible);
            final int count = mBubbleContainer.getChildCount();
            List<Animator> animList = new ArrayList();
            for (int i = 0; i < count; i++) {
                View child = mBubbleContainer.getChildAt(i);
                float transY = mPositioner.getExpandedBubbleXY(
                        i, count, mStackOnLeftOrWillBe).y;
                ObjectAnimator anim = ObjectAnimator.ofFloat(child, TRANSLATION_Y, transY);
                animList.add(anim);
            }
            AnimatorSet set = new AnimatorSet();
            set.playTogether(animList);
            set.start();
        }
    }

@@ -2514,7 +2538,7 @@ public class BubbleStackView extends FrameLayout
            // Account for the IME in the touchable region so that the touchable region of the
            // Bubble window doesn't obscure the IME. The touchable region affects which areas
            // of the screen can be excluded by lower windows (IME is just above the embedded task)
            outRect.bottom -= (int) mStackAnimationController.getImeHeight();
            outRect.bottom -= mPositioner.getImeHeight();
        }

        if (mFlyout.getVisibility() == View.VISIBLE) {
@@ -2858,12 +2882,13 @@ public class BubbleStackView extends FrameLayout
        if (index == -1) {
            return;
        }
        PointF bubblePosition = mPositioner.getExpandedBubbleXY(index,
        PointF position = mPositioner.getExpandedBubbleXY(index,
                mBubbleContainer.getChildCount(),
                mStackOnLeftOrWillBe);
        mExpandedBubble.getExpandedView().setPointerPosition(mPositioner.showBubblesVertically()
                ? bubblePosition.y
                : bubblePosition.x,
        float bubblePosition = mPositioner.showBubblesVertically()
                ? position.y
                : position.x;
        mExpandedBubble.getExpandedView().setPointerPosition(bubblePosition,
                mStackOnLeftOrWillBe);
    }

+20 −32
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RES
import android.content.res.Resources;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.view.View;

import androidx.annotation.NonNull;
@@ -364,7 +363,7 @@ public class ExpandedAnimationController
            bubbleView.setTranslationY(y);
        }

        final float expandedY = mPositioner.getExpandedBubblesY();
        final float expandedY = mPositioner.getExpandedViewYTopAligned();
        final boolean draggedOutEnough =
                y > expandedY + mBubbleSizePx || y < expandedY - mBubbleSizePx;
        if (draggedOutEnough != mBubbleDraggedOutEnough) {
@@ -429,16 +428,6 @@ public class ExpandedAnimationController
        updateBubblePositions();
    }

    /**
     * Animates the bubbles to the y position. Used in response to IME showing.
     */
    public void updateYPosition(Runnable after) {
        if (mLayout == null) return;
        animationsForChildrenFromIndex(
                0, (i, anim) -> anim.translationY(mPositioner.getExpandedBubblesY()))
                .startAll(after);
    }

    /** Description of current animation controller state. */
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("ExpandedAnimationController state:");
@@ -504,22 +493,22 @@ public class ExpandedAnimationController
            } else {
                child.setTranslationX(p.x);
            }
            if (!mPreparingToCollapse) {
                // Only animate if we're not collapsing as that animation will handle placing the

            if (mPreparingToCollapse) {
                // Don't animate if we're collapsing, as that animation will handle placing the
                // new bubble in the stacked position.
                return;
            }

            if (mPositioner.showBubblesVertically()) {
                    Rect availableRect = mPositioner.getAvailableRect();
                float fromX = onLeft
                            ? -mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
                            : availableRect.right + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
                        ? p.x - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
                        : p.x + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
                animationForChild(child)
                        .translationX(fromX, p.y)
                        .start();
            } else {
                    // Only animate if we're not collapsing as that animation will handle placing
                    // the new bubble in the stacked position.
                    float fromY = mPositioner.getExpandedBubblesY() - mBubbleSizePx
                            * ANIMATE_TRANSLATION_FACTOR;
                float fromY = p.y - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
                animationForChild(child)
                        .translationY(fromY, p.y)
                        .start();
@@ -527,7 +516,6 @@ public class ExpandedAnimationController
            updateBubblePositions();
        }
    }
    }

    @Override
    void onChildRemoved(View child, int index, Runnable finishRemoval) {
+6 −16
Original line number Diff line number Diff line
@@ -127,9 +127,6 @@ public class StackAnimationController extends
    /** Whether or not the stack's start position has been set. */
    private boolean mStackMovedToStartPosition = false;

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

    /**
     * The Y position of the stack before the IME became visible, or {@link Float#MIN_VALUE} if the
     * IME is not visible or the user moved the stack since the IME became visible.
@@ -173,7 +170,7 @@ public class StackAnimationController extends
     */
    private boolean mSpringToTouchOnNextMotionEvent = false;

    /** Horizontal offset of bubbles in the stack. */
    /** Offset of bubbles in the stack (i.e. how much they overlap). */
    private float mStackOffset;
    /** Offset between stack y and animation y for bubble swap. */
    private float mSwapAnimationOffset;
@@ -521,16 +518,6 @@ public class StackAnimationController extends
        removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
    }

    /** Save the current IME height so that we know where the stack bounds should be. */
    public void setImeHeight(int imeHeight) {
        mImeHeight = imeHeight;
    }

    /** Returns the current IME height that the stack is offset by. */
    public float getImeHeight() {
        return mImeHeight;
    }

    /**
     * Animates the stack either away from the newly visible IME, or back to its original position
     * due to the IME going away.
@@ -589,11 +576,14 @@ public class StackAnimationController extends
     */
    public RectF getAllowableStackPositionRegion() {
        final RectF allowableRegion = new RectF(mPositioner.getAvailableRect());
        final int imeHeight = mPositioner.getImeHeight();
        final float bottomPadding = getBubbleCount() > 1
                ? mBubblePaddingTop + mStackOffset
                : mBubblePaddingTop;
        allowableRegion.left -= mBubbleOffscreen;
        allowableRegion.top += mBubblePaddingTop;
        allowableRegion.right += mBubbleOffscreen - mBubbleSize;
        allowableRegion.bottom -= mBubblePaddingTop + mBubbleSize
                + (mImeHeight != UNSET ? mImeHeight + mBubblePaddingTop : 0f);
        allowableRegion.bottom -= imeHeight + bottomPadding + mBubbleSize;
        return allowableRegion;
    }