Loading packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java +42 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -369,6 +375,8 @@ public class AccessibilityFloatingMenuView extends FrameLayout mIsShowing = true; mWindowManager.addView(this, mCurrentLayoutParams); setOnApplyWindowInsetsListener((view, insets) -> onWindowInsetsApplied(insets)); setSystemGestureExclusion(); } Loading @@ -379,6 +387,8 @@ public class AccessibilityFloatingMenuView extends FrameLayout mIsShowing = false; mWindowManager.removeView(this); setOnApplyWindowInsetsListener(null); setSystemGestureExclusion(); } Loading Loading @@ -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); Loading Loading @@ -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(); Loading Loading @@ -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; Loading packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java +82 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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); Loading Loading
packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java +42 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -369,6 +375,8 @@ public class AccessibilityFloatingMenuView extends FrameLayout mIsShowing = true; mWindowManager.addView(this, mCurrentLayoutParams); setOnApplyWindowInsetsListener((view, insets) -> onWindowInsetsApplied(insets)); setSystemGestureExclusion(); } Loading @@ -379,6 +387,8 @@ public class AccessibilityFloatingMenuView extends FrameLayout mIsShowing = false; mWindowManager.removeView(this); setOnApplyWindowInsetsListener(null); setSystemGestureExclusion(); } Loading Loading @@ -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); Loading Loading @@ -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(); Loading Loading @@ -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; Loading
packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java +82 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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); Loading