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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user