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

Commit 53afc0f4 authored by Longbo Wei's avatar Longbo Wei Committed by Android (Google) Code Review
Browse files

Merge "autoclick: Improve Scroll Panel Positioning" into main

parents 3ea45e96 1b54146d
Loading
Loading
Loading
Loading
+46 −18
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.android.internal.R;
import com.android.internal.policy.SystemBarUtils;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -89,6 +90,8 @@ public class AutoclickScrollPanel {
    private final ImageButton mRightButton;
    private final ImageButton mExitButton;

    private final int mStatusBarHeight;

    private boolean mInScrollMode = false;

    // Panel size determined after measuring.
@@ -122,6 +125,7 @@ public class AutoclickScrollPanel {
        mContentView = (AutoclickLinearLayout) LayoutInflater.from(context).inflate(
                R.layout.accessibility_autoclick_scroll_panel, null);
        mParams = getDefaultLayoutParams();
        mStatusBarHeight = SystemBarUtils.getStatusBarHeight(context);

        // Initialize buttons.
        mUpButton = mContentView.findViewById(R.id.scroll_up);
@@ -190,8 +194,7 @@ public class AutoclickScrollPanel {
    /**
     * Positions the panel at the bottom right of the cursor coordinates,
     * ensuring it stays within the screen boundaries.
     * If the panel would go off the right or bottom edge, it's repositioned
     * to the left or above the cursor, respectively.
     * If the panel would go off the right or bottom edge, tries other diagonal directions.
     * The panel's gravity is set to TOP|LEFT for absolute positioning.
     */
    protected void positionPanelAtCursor(float cursorX, float cursorY) {
@@ -204,24 +207,34 @@ public class AutoclickScrollPanel {
        int screenWidth = displayMetrics.widthPixels;
        int screenHeight = displayMetrics.heightPixels;

        // Calculate initial position.
        int panelX = (int) cursorX;
        int panelY = (int) cursorY;

        // Check if panel would go off right edge of screen.
        if (panelX + mPanelWidth > screenWidth - PANEL_EDGE_MARGIN) {
            // Place to the left of cursor instead if no space left for right edge.
            panelX = (int) cursorX - mPanelWidth;
        // Adjust Y for status bar height.
        float adjustedCursorY = cursorY - mStatusBarHeight;

        // Offset from cursor point to panel center.
        int margin = 10;
        int xOffset = mPanelWidth / 2 + margin;
        int yOffset = mPanelHeight / 2 + margin;

        // Try 4 diagonal positions: bottom-right, bottom-left, top-right, top-left.
        int[][] directions = {{+1, +1}, {-1, +1}, {+1, -1}, {-1, -1}};
        for (int[] dir : directions) {
            // (panelX, panelY) is the top-left point of the panel.
            int panelX = (int) (cursorX + dir[0] * xOffset - mPanelWidth / 2);
            int panelY = (int) (adjustedCursorY + dir[1] * yOffset - mPanelHeight / 2);
            if (isWithinBounds(panelX, panelY, screenWidth, screenHeight)) {
                mParams.x = panelX;
                mParams.y = panelY;
                return;
            }
        }

        // Check if panel would go off bottom edge of screen.
        if (panelY + mPanelHeight > screenHeight - PANEL_EDGE_MARGIN) {
            // Place above cursor instead if no space left for bottom edge.
            panelY = (int) cursorY - mPanelHeight;
    }

        mParams.x = panelX;
        mParams.y = panelY;
    /**
     * Returns true if the panel fits on screen with margin.
     */
    private boolean isWithinBounds(int x, int y, int screenWidth, int screenHeight) {
        return x > PANEL_EDGE_MARGIN && x + mPanelWidth + PANEL_EDGE_MARGIN < screenWidth
                && y > PANEL_EDGE_MARGIN && y + mPanelHeight + PANEL_EDGE_MARGIN < screenHeight;
    }

    /**
@@ -336,4 +349,19 @@ public class AutoclickScrollPanel {
    public WindowManager.LayoutParams getLayoutParamsForTesting() {
        return mParams;
    }

    @VisibleForTesting
    public int getPanelWidthForTesting() {
        return mPanelWidth;
    }

    @VisibleForTesting
    public int getPanelHeightForTesting() {
        return mPanelHeight;
    }

    @VisibleForTesting
    public int getStatusBarHeightForTesting() {
        return mStatusBarHeight;
    }
}
+40 −8
Original line number Diff line number Diff line
@@ -274,17 +274,25 @@ public class AutoclickScrollPanelTest {

    @Test
    public void showPanel_normalCase() {
        // Normal case, position at (10, 10).
        int cursorX = 10;
        int cursorY = 10;
        // Normal case, position at (100, 100).
        int cursorX = 100;
        int cursorY = 100;

        // Capture the current layout params before positioning.
        WindowManager.LayoutParams params = mScrollPanel.getLayoutParamsForTesting();
        mScrollPanel.positionPanelAtCursor(cursorX, cursorY);

        // Panel should be at cursor position (gravity is LEFT|TOP).
        assertThat(params.x).isEqualTo(cursorX);
        assertThat(params.y).isEqualTo(cursorY);
        // Calculate expected position for bottom-right placement.
        int margin = 10;
        int xOffset = mScrollPanel.getPanelWidthForTesting() / 2 + margin;
        int yOffset = mScrollPanel.getPanelHeightForTesting() / 2 + margin;
        int expectedX = cursorX + xOffset - mScrollPanel.getPanelWidthForTesting() / 2;
        int expectedY = (cursorY - mScrollPanel.getStatusBarHeightForTesting()) + yOffset
                - mScrollPanel.getPanelHeightForTesting() / 2;

        // Verify panel's position.
        assertThat(params.x).isEqualTo(expectedX);
        assertThat(params.y).isEqualTo(expectedY);
    }

    @Test
@@ -316,7 +324,7 @@ public class AutoclickScrollPanelTest {
        mScrollPanel.positionPanelAtCursor(cursorX, cursorY);

        // Panel should be above cursor.
        assertThat(params.y).isLessThan(cursorY);
        assertThat(params.y).isLessThan(cursorY - mScrollPanel.getStatusBarHeightForTesting());
    }

    @Test
@@ -333,7 +341,31 @@ public class AutoclickScrollPanelTest {

        // Panel should be left of and above cursor.
        assertThat(params.x).isLessThan(cursorX);
        assertThat(params.y).isLessThan(cursorY);
        assertThat(params.y).isLessThan(cursorY - mScrollPanel.getStatusBarHeightForTesting());
    }

    @Test
    public void showPanel_closeToEdge_withinBounds() {
        // Test edge case where cursor is very close to edge, panel should still be positioned
        // within PANEL_EDGE_MARGIN (15px).
        int edgeMargin = 15;

        // Near bottom-right corner case.
        // 10px from right edge.
        int cursorX = mScreenWidth - 10;
        // 10px from bottom edge.
        int cursorY = mScreenHeight - 10;

        WindowManager.LayoutParams params = mScrollPanel.getLayoutParamsForTesting();
        mScrollPanel.positionPanelAtCursor(cursorX, cursorY);

        // Verify panel is within bounds with margin.
        assertThat(params.x).isGreaterThan(edgeMargin);
        assertThat(params.y).isGreaterThan(edgeMargin);
        assertThat(params.x + mScrollPanel.getPanelWidthForTesting() + edgeMargin)
                .isLessThan(mScreenWidth);
        assertThat(params.y + mScrollPanel.getPanelHeightForTesting() + edgeMargin)
                .isLessThan(mScreenHeight);
    }

    @Test