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

Commit 98b0b43e authored by “Longbo's avatar “Longbo Committed by Longbo Wei
Browse files

autoclick: Support auto-drag for the panel

Previously, users could only drag the autoclick type panel using direct
touch input, which was difficult for users with limited dexterity who
rely on autoclick functionality.

This change enables panel repositioning through the autoclick system.

Video: http://shortn/_hk0FUZo6BK

Bug: b/416050648
Test: atest
Flag: com.android.server.accessibility.enable_autoclick_indicator
Change-Id: Ic5d5fcde06cdeda4f330818673f792a58ccade57
parent 8d8c6bea
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -277,6 +277,11 @@ public class AutoclickController extends BaseEventStreamTransformation {
                        mAutoclickIndicatorScheduler);
            }

            if (mAutoclickTypePanel != null && mAutoclickTypePanel.getIsDragging()
                    && event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) {
                mAutoclickTypePanel.onDragMove(event);
            }

            if (!isPaused()) {
                scheduleClick(event, policyFlags);

@@ -449,6 +454,9 @@ public class AutoclickController extends BaseEventStreamTransformation {
        if (mAutoclickIndicatorScheduler != null) {
            mAutoclickIndicatorScheduler.cancel();
        }
        if (mAutoclickTypePanel != null && mAutoclickTypePanel.getIsDragging()) {
            mAutoclickTypePanel.onDragEnd();
        }
    }

    /**
@@ -1164,6 +1172,12 @@ public class AutoclickController extends BaseEventStreamTransformation {
                clearLongPressState();
            }

            if (mAutoclickTypePanel.isHoveringDraggableArea()
                    && !mAutoclickTypePanel.getIsDragging()) {
                mAutoclickTypePanel.onDragStart(mLastMotionEvent);
                return;
            }

            // Always triggers left-click when the cursor hovers over the autoclick type panel, to
            // always allow users to change a different click type. Otherwise, if one chooses the
            // right-click, this user won't be able to rely on autoclick to select other click
@@ -1216,6 +1230,12 @@ public class AutoclickController extends BaseEventStreamTransformation {
                    break;
            }
            sendMotionEventsForClick(actionButton);

            // End panel drag operation if one is active (autoclick triggered after user stopped
            // moving during drag).
            if (mAutoclickTypePanel != null && mAutoclickTypePanel.getIsDragging()) {
                mAutoclickTypePanel.onDragEnd();
            }
        }

        /**
+65 −29
Original line number Diff line number Diff line
@@ -214,16 +214,6 @@ public class AutoclickTypePanel {
        // Set up touch event handling for the panel to allow the user to drag and reposition the
        // panel by touching and moving it.
        mContentView.setOnTouchListener(this::onPanelTouch);

        // Set hover behavior for the panel, show grab when hovering.
        mContentView.setOnHoverListener((v, event) -> {
            mCurrentCursor = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_GRAB);
            v.setPointerIcon(mCurrentCursor);
            return false;
        });

        // Show default cursor when hovering over buttons.
        setDefaultCursorForButtons();
    }

    /**
@@ -263,7 +253,9 @@ public class AutoclickTypePanel {
        int yPosition = params.y;

        // Determine which half of the screen the panel is on.
        boolean isOnLeftHalf = params.x < screenWidth / 2;
        @Corner int visualCorner = getVisualCorner();
        boolean isOnLeftHalf =
                (visualCorner == CORNER_TOP_LEFT || visualCorner == CORNER_BOTTOM_LEFT);

        if (isOnLeftHalf) {
            // Snap to left edge. Set params.gravity to make sure x, y offsets from correct anchor.
@@ -313,6 +305,10 @@ public class AutoclickTypePanel {
        mPauseButton.setOnClickListener(v -> togglePause());

        setSelectedClickType(AUTOCLICK_TYPE_LEFT_CLICK);

        // Set up hover listeners on panel and buttons to dynamically change cursor icons.
        setupHoverListenersForCursor();

        // Remove spacing between buttons when initialized.
        adjustPanelSpacing(/* isExpanded= */ true);
    }
@@ -670,22 +666,6 @@ public class AutoclickTypePanel {
        }
    }

    private void setDefaultCursorForButtons() {
        View[] buttons = {
                mLeftClickButton, mRightClickButton, mDoubleClickButton,
                mScrollButton, mDragButton, mLongPressButton,
                mPauseButton, mPositionButton
        };

        for (View button : buttons) {
            button.setOnHoverListener((v, event) -> {
                mCurrentCursor = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW);
                v.setPointerIcon(mCurrentCursor);
                return false;
            });
        }
    }

    /**
     * Starts drag operation, capturing initial positions and updating cursor icon.
     */
@@ -754,6 +734,63 @@ public class AutoclickTypePanel {
        }
    }

    /**
     * Returns true if cursor is over content view but not over any buttons.
     */
    public boolean isHoveringDraggableArea() {
        if (!mContentView.isHovered()) {
            return false;
        }

        View[] buttons = {mLeftClickButton, mRightClickButton, mDoubleClickButton,
                mScrollButton, mDragButton, mLongPressButton, mPauseButton, mPositionButton};
        for (View button : buttons) {
            if (button.isHovered()) {
                return false;
            }
        }
        return true;
    }

    /**
     * Sets up hover listeners to update cursor icons (grab for draggable areas, arrow for buttons).
     */
    private void setupHoverListenersForCursor() {
        View[] mAllButtons = new View[]{
                mLeftClickButton, mRightClickButton, mDoubleClickButton,
                mScrollButton, mDragButton, mLongPressButton,
                mPauseButton, mPositionButton
        };

        // Set hover behavior for the panel.
        mContentView.setOnHoverListener((v, event) -> {
            updateCursorIcon();
            return false;
        });

        // Set hover behavior for all buttons.
        for (View button : mAllButtons) {
            button.setOnHoverListener((v, event) -> {
                updateCursorIcon();
                return false;
            });
        }
    }

    /**
     * Updates cursor based on hover state: grab for draggable areas, arrow for buttons.
     */
    private void updateCursorIcon() {
        // Don't update cursor icon while dragging to avoid overriding the grabbing cursor during
        // drag.
        if (mIsDragging) {
            return;
        }
        int cursorType = isHoveringDraggableArea() ? PointerIcon.TYPE_GRAB : PointerIcon.TYPE_ARROW;
        mCurrentCursor = PointerIcon.getSystemIcon(mContext, cursorType);
        mContentView.setPointerIcon(mCurrentCursor);
    }

    @VisibleForTesting
    boolean getExpansionStateForTesting() {
        return mExpanded;
@@ -776,8 +813,7 @@ public class AutoclickTypePanel {
        return mParams;
    }

    @VisibleForTesting
    boolean getIsDraggingForTesting() {
    boolean getIsDragging() {
        return mIsDragging;
    }

+58 −0
Original line number Diff line number Diff line
@@ -41,9 +41,11 @@ import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;

@@ -1434,6 +1436,62 @@ public class AutoclickControllerTest {
        assertThat(scrollCaptor.eventCount).isEqualTo(countBeforeRunnable);
    }

    @Test
    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
    public void typePanelDrag_completeLifeCycle() {
        injectFakeMouseActionHoverMoveEvent();

        // Store initial position for comparison.
        WindowManager.LayoutParams initialParams =
                mController.mAutoclickTypePanel.getLayoutParamsForTesting();
        int initialX = initialParams.x;
        int initialY = initialParams.y;

        // Test onDragStart - should enable dragging and change cursor.
        MotionEvent dragStartEvent = MotionEvent.obtain(
                /* downTime= */ 0, /* eventTime= */ 0, MotionEvent.ACTION_DOWN,
                /* x= */ 100f, /* y= */ 100f, /* metaState= */ 0);
        mController.mAutoclickTypePanel.onDragStart(dragStartEvent);
        assertThat(mController.mAutoclickTypePanel.getIsDragging()).isTrue();
        assertThat(mController.mAutoclickTypePanel.getCurrentCursorForTesting().getType())
                .isEqualTo(PointerIcon.TYPE_GRABBING);

        // Test onDragMove - should update position and maintain drag state.
        MotionEvent dragMoveEvent = MotionEvent.obtain(
                /* downTime= */ 0, /* eventTime= */ 50, MotionEvent.ACTION_MOVE,
                /* x= */ 150f, /* y= */ 150f, /* metaState= */ 0);
        mController.mAutoclickTypePanel.onDragMove(dragMoveEvent);

        // Verify drag state maintained and gravity changed to absolute positioning
        assertThat(mController.mAutoclickTypePanel.getIsDragging()).isTrue();
        assertThat(mController.mAutoclickTypePanel.getCurrentCursorForTesting().getType())
                .isEqualTo(PointerIcon.TYPE_GRABBING);
        assertThat(mController.mAutoclickTypePanel.getLayoutParamsForTesting().gravity)
                .isEqualTo(Gravity.LEFT | Gravity.TOP);

        // Verify position coordinates actually changed from drag movement.
        WindowManager.LayoutParams dragParams =
                mController.mAutoclickTypePanel.getLayoutParamsForTesting();
        assertThat(dragParams.x).isNotEqualTo(initialX);
        assertThat(dragParams.y).isNotEqualTo(initialY);

        // Test onDragEnd - should reset state, change cursor, and snap to edge.
        mController.mAutoclickTypePanel.onDragEnd();
        assertThat(mController.mAutoclickTypePanel.getIsDragging()).isFalse();
        assertThat(mController.mAutoclickTypePanel.getCurrentCursorForTesting().getType())
                .isEqualTo(PointerIcon.TYPE_GRAB);

        // Verify panel snapped to edge.
        WindowManager.LayoutParams finalParams =
                mController.mAutoclickTypePanel.getLayoutParamsForTesting();
        boolean snappedToLeftEdge = (finalParams.gravity & Gravity.START) == Gravity.START;
        boolean snappedToRightEdge = (finalParams.gravity & Gravity.END) == Gravity.END;
        assertThat(snappedToLeftEdge || snappedToRightEdge).isTrue();

        dragStartEvent.recycle();
        dragMoveEvent.recycle();
    }

    @Test
    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
    public void exitButton_exitsScrollMode() {
+4 −1
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ import androidx.annotation.NonNull;
import com.android.internal.R;

import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -345,7 +346,7 @@ public class AutoclickTypePanelTest {
        contentView.dispatchTouchEvent(moveEvent);

        // Verify position update.
        assertThat(mAutoclickTypePanel.getIsDraggingForTesting()).isTrue();
        assertThat(mAutoclickTypePanel.getIsDragging()).isTrue();
        assertThat(params.gravity).isEqualTo(Gravity.LEFT | Gravity.TOP);
        assertThat(params.x).isEqualTo(panelLocation[0] + delta);
        assertThat(params.y).isEqualTo(
@@ -354,6 +355,7 @@ public class AutoclickTypePanelTest {
    }

    @Test
    @Ignore ("b/424594372")
    public void dragAndEndAtRight_snapsToRightSide() {
        View contentView = mAutoclickTypePanel.getContentViewForTesting();
        WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParamsForTesting();
@@ -377,6 +379,7 @@ public class AutoclickTypePanelTest {
    }

    @Test
    @Ignore ("b/424594372")
    public void dragAndEndAtLeft_snapsToLeftSide() {
        View contentView = mAutoclickTypePanel.getContentViewForTesting();
        WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParamsForTesting();