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
This commit is contained in:
Mady Mellor
2021-08-13 17:07:18 -07:00
parent 936f28842e
commit cba1b858d5
5 changed files with 147 additions and 72 deletions

View File

@@ -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);
}
}
}

View File

@@ -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).
*/

View File

@@ -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);
}

View File

@@ -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,28 +493,27 @@ 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.
if (mPositioner.showBubblesVertically()) {
Rect availableRect = mPositioner.getAvailableRect();
float fromX = onLeft
? -mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
: availableRect.right + 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;
animationForChild(child)
.translationY(fromY, p.y)
.start();
}
updateBubblePositions();
return;
}
if (mPositioner.showBubblesVertically()) {
float fromX = onLeft
? p.x - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
: p.x + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
animationForChild(child)
.translationX(fromX, p.y)
.start();
} else {
float fromY = p.y - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
animationForChild(child)
.translationY(fromY, p.y)
.start();
}
updateBubblePositions();
}
}

View File

@@ -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;
}