Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 909abd8a authored by PETER LIANG's avatar PETER LIANG Committed by Android (Google) Code Review
Browse files

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

parents 2be32981 5fec1068
Loading
Loading
Loading
Loading
+42 −1
Original line number Diff line number Diff line
@@ -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;
+82 −2
Original line number Diff line number Diff line
@@ -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);