Merge "Fix the accessibility floating menu overlap with the keyboard." into sc-dev

This commit is contained in:
PETER LIANG
2021-06-02 01:01:26 +00:00
committed by Android (Google) Code Review
2 changed files with 124 additions and 3 deletions

View File

@@ -18,6 +18,8 @@ package com.android.systemui.accessibility.floatingmenu;
import static android.util.MathUtils.constrain;
import static android.util.MathUtils.sq;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
import static java.util.Objects.requireNonNull;
@@ -29,6 +31,7 @@ import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -42,7 +45,9 @@ import android.view.Gravity;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.animation.Animation;
@@ -90,6 +95,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
private boolean mIsShowing;
private boolean mIsDownInEnlargedTouchArea;
private boolean mIsDragging = false;
private boolean mImeVisibility;
@Alignment
private int mAlignment = Alignment.RIGHT;
@SizeType
@@ -369,6 +375,8 @@ public class AccessibilityFloatingMenuView extends FrameLayout
mIsShowing = true;
mWindowManager.addView(this, mCurrentLayoutParams);
setOnApplyWindowInsetsListener((view, insets) -> onWindowInsetsApplied(insets));
setSystemGestureExclusion();
}
@@ -379,6 +387,8 @@ public class AccessibilityFloatingMenuView extends FrameLayout
mIsShowing = false;
mWindowManager.removeView(this);
setOnApplyWindowInsetsListener(null);
setSystemGestureExclusion();
}
@@ -565,6 +575,16 @@ public class AccessibilityFloatingMenuView extends FrameLayout
return mListView.dispatchTouchEvent(event);
}
private WindowInsets onWindowInsetsApplied(WindowInsets insets) {
final boolean currentImeVisibility = insets.isVisible(ime());
if (currentImeVisibility != mImeVisibility) {
mImeVisibility = currentImeVisibility;
updateLocationWith(mAlignment, mPercentageY);
}
return insets;
}
private boolean isMovingTowardsScreenEdge(@Alignment int side, int currentRawX, int downX) {
return (side == Alignment.RIGHT && currentRawX > downX)
|| (side == Alignment.LEFT && downX > currentRawX);
@@ -658,6 +678,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
PixelFormat.TRANSLUCENT);
params.receiveInsetsIgnoringZOrder = true;
params.windowAnimations = android.R.style.Animation_Translucent;
params.gravity = Gravity.START | Gravity.TOP;
params.x = getMaxWindowX();
@@ -728,10 +749,30 @@ public class AccessibilityFloatingMenuView extends FrameLayout
*/
private void updateLocationWith(@Alignment int side, float percentageCurrentY) {
mCurrentLayoutParams.x = (side == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX();
mCurrentLayoutParams.y = (int) (percentageCurrentY * getMaxWindowY());
final int currentLayoutY = (int) (percentageCurrentY * getMaxWindowY());
mCurrentLayoutParams.y = Math.max(MIN_WINDOW_Y, currentLayoutY - getInterval());
mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
}
/**
* Gets the moving interval to not overlap between the keyboard and menu view.
*
* @return the moving interval if they overlap each other, otherwise 0.
*/
private int getInterval() {
if (!mImeVisibility) {
return 0;
}
final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
final Insets imeInsets = windowMetrics.getWindowInsets().getInsets(
ime() | navigationBars());
final int imeY = mScreenHeight - imeInsets.bottom;
final int layoutBottomY = mCurrentLayoutParams.y + getWindowHeight();
return layoutBottomY > imeY ? (layoutBottomY - imeY) : 0;
}
private void updateOffsetWith(@ShapeType int shapeType, @Alignment int side) {
final float halfWidth = getLayoutWidth() / 2.0f;
final float offset = (shapeType == ShapeType.OVAL) ? 0 : halfWidth;

View File

@@ -18,6 +18,8 @@ package com.android.systemui.accessibility.floatingmenu;
import static android.view.View.OVER_SCROLL_ALWAYS;
import static android.view.View.OVER_SCROLL_NEVER;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
import static com.google.common.truth.Truth.assertThat;
@@ -31,9 +33,11 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
@@ -43,7 +47,9 @@ import android.testing.TestableLooper;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.annotation.NonNull;
@@ -79,12 +85,17 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
@Mock
private ViewPropertyAnimator mAnimator;
@Mock
private WindowMetrics mWindowMetrics;
private MotionEvent mInterceptMotionEvent;
private RecyclerView mListView;
private Rect mAvailableBounds = new Rect(100, 200, 300, 400);
private int mScreenHeight;
private int mMenuWindowHeight;
private int mMenuHalfWidth;
private int mMenuHalfHeight;
private int mScreenHalfWidth;
@@ -117,12 +128,13 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
final int menuWidth = padding * 2 + iconWidthHeight;
final int menuHeight = (padding + iconWidthHeight) * mTargets.size() + padding;
final int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels;
final int screenHeight = mContext.getResources().getDisplayMetrics().heightPixels;
mScreenHeight = mContext.getResources().getDisplayMetrics().heightPixels;
mMenuHalfWidth = menuWidth / 2;
mMenuHalfHeight = menuHeight / 2;
mScreenHalfWidth = screenWidth / 2;
mScreenHalfHeight = screenHeight / 2;
mScreenHalfHeight = mScreenHeight / 2;
mMaxWindowX = screenWidth - margin - menuWidth;
mMenuWindowHeight = menuHeight + margin * 2;
}
@Test
@@ -464,12 +476,80 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
assertThat(mListView.getOverScrollMode()).isEqualTo(OVER_SCROLL_NEVER);
}
@Test
public void showMenuView_insetsListener_overlapWithIme_menuViewShifted() {
final int offset = 200;
showMenuWithLatestStatus();
final WindowInsets imeInset = fakeImeInsetWith(offset);
when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset);
final int expectedLayoutY = mMenuView.mCurrentLayoutParams.y - offset;
mMenuView.dispatchApplyWindowInsets(imeInset);
assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(expectedLayoutY);
}
@Test
public void hideIme_onMenuViewShifted_menuViewMovedBack() {
final int offset = 200;
showMenuWithLatestStatus();
final WindowInsets imeInset = fakeImeInsetWith(offset);
when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset);
final int expectedLayoutY = mMenuView.mCurrentLayoutParams.y;
mMenuView.dispatchApplyWindowInsets(imeInset);
mMenuView.dispatchApplyWindowInsets(
new WindowInsets.Builder().setVisible(ime(), false).build());
assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(expectedLayoutY);
}
@Test
public void showMenuAndIme_withHigherIme_alignScreenTopEdge() {
final int offset = 99999;
showMenuWithLatestStatus();
final WindowInsets imeInset = fakeImeInsetWith(offset);
when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset);
mMenuView.dispatchApplyWindowInsets(imeInset);
assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(0);
}
@After
public void tearDown() {
mInterceptMotionEvent = null;
mMotionEventHelper.recycleEvents();
}
private void showMenuWithLatestStatus() {
mMenuView.show();
mMenuView.onTargetsChanged(mTargets);
mMenuView.setSizeType(0);
mMenuView.setShapeType(0);
}
/**
* Based on the current menu status, fake the ime inset component {@link WindowInsets} used
* for testing.
*
* @param offset is used for the y-axis position of ime higher than the y-axis position of menu.
* @return the ime inset
*/
private WindowInsets fakeImeInsetWith(int offset) {
// Ensure the keyboard has overlapped on the menu view.
final int fakeImeHeight =
mScreenHeight - (mMenuView.mCurrentLayoutParams.y + mMenuWindowHeight) + offset;
return new WindowInsets.Builder()
.setVisible(ime() | navigationBars(), true)
.setInsets(ime() | navigationBars(), Insets.of(0, 0, 0, fakeImeHeight))
.build();
}
private class TestAccessibilityFloatingMenu extends AccessibilityFloatingMenuView {
TestAccessibilityFloatingMenu(Context context, RecyclerView listView) {
super(context, listView);