Merge "Fix the accessibility floating menu overlap with the keyboard." into sc-dev
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user