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

Commit 3098d632 authored by Longbo Wei's avatar Longbo Wei
Browse files

a11y: Perform acutal scroll function

* Updates cursor position when sending clicks
* Implement handleScroll function, this function will perform scrolling operations at that cursor location.

Video: http://go/scrcast/NTE5MTI0MDM5NDk5Nzc2MHwyMzI4MmQ1Mi0zZQ

Bug: b/393559560
Test: AutoclickControllerTest
Flag: com.android.server.accessibility.enable_autoclick_indicator
Change-Id: Ifd8c54459534b00d3c8a63246564e6d8a5547918
parent f1c5361d
Loading
Loading
Loading
Loading
+65 −3
Original line number Diff line number Diff line
@@ -81,10 +81,16 @@ import com.android.server.accessibility.Flags;
public class AutoclickController extends BaseEventStreamTransformation {

    private static final String LOG_TAG = AutoclickController.class.getSimpleName();
    // TODO(b/393559560): Finalize scroll amount.
    private static final float SCROLL_AMOUNT = 1.0f;

    private final AccessibilityTraceManager mTrace;
    private final Context mContext;
    private final int mUserId;
    @VisibleForTesting
    float mLastCursorX;
    @VisibleForTesting
    float mLastCursorY;

    // Lazily created on the first mouse motion event.
    @VisibleForTesting ClickScheduler mClickScheduler;
@@ -315,8 +321,58 @@ public class AutoclickController extends BaseEventStreamTransformation {
    /**
     * Handles scroll operations in the specified direction.
     */
    public void handleScroll(@AutoclickScrollPanel.ScrollDirection int direction) {
        // TODO(b/388845721): Perform actual scroll.
    private void handleScroll(@AutoclickScrollPanel.ScrollDirection int direction) {
        final long now = SystemClock.uptimeMillis();

        // Create pointer properties.
        PointerProperties[] pointerProps = new PointerProperties[1];
        pointerProps[0] = new PointerProperties();
        pointerProps[0].id = 0;
        pointerProps[0].toolType = MotionEvent.TOOL_TYPE_MOUSE;

        // Create pointer coordinates at the last cursor position.
        PointerCoords[] pointerCoords = new PointerCoords[1];
        pointerCoords[0] = new PointerCoords();
        pointerCoords[0].x = mLastCursorX;
        pointerCoords[0].y = mLastCursorY;

        // Set scroll values based on direction.
        switch (direction) {
            case AutoclickScrollPanel.DIRECTION_UP:
                pointerCoords[0].setAxisValue(MotionEvent.AXIS_VSCROLL, SCROLL_AMOUNT);
                break;
            case AutoclickScrollPanel.DIRECTION_DOWN:
                pointerCoords[0].setAxisValue(MotionEvent.AXIS_VSCROLL, -SCROLL_AMOUNT);
                break;
            case AutoclickScrollPanel.DIRECTION_LEFT:
                pointerCoords[0].setAxisValue(MotionEvent.AXIS_HSCROLL, SCROLL_AMOUNT);
                break;
            case AutoclickScrollPanel.DIRECTION_RIGHT:
                pointerCoords[0].setAxisValue(MotionEvent.AXIS_HSCROLL, -SCROLL_AMOUNT);
                break;
            case AutoclickScrollPanel.DIRECTION_EXIT:
            case AutoclickScrollPanel.DIRECTION_NONE:
            default:
                return;
        }

        // Get device ID from last motion event if possible.
        int deviceId = mClickScheduler != null && mClickScheduler.mLastMotionEvent != null
                ? mClickScheduler.mLastMotionEvent.getDeviceId() : 0;

        // Create a scroll event.
        MotionEvent scrollEvent = MotionEvent.obtain(
                /* downTime= */ now, /* eventTime= */ now,
                MotionEvent.ACTION_SCROLL, /* pointerCount= */ 1, pointerProps,
                pointerCoords, /* metaState= */ 0, /* actionButton= */ 0, /* xPrecision= */
                1.0f, /* yPrecision= */ 1.0f, deviceId, /* edgeFlags= */ 0,
                InputDevice.SOURCE_MOUSE, /* flags= */ 0);

        // Send the scroll event.
        super.onMotionEvent(scrollEvent, scrollEvent, mClickScheduler.mEventPolicyFlags);

        // Clean up.
        scrollEvent.recycle();
    }

    /**
@@ -823,13 +879,19 @@ public class AutoclickController extends BaseEventStreamTransformation {
                // If exit button is hovered, exit scroll mode after countdown and return early.
                if (mHoveredDirection == AutoclickScrollPanel.DIRECTION_EXIT) {
                    exitScrollMode();
                }
                    return;
                }
            }

            // Handle scroll type specially, show scroll panel instead of sending click events.
            if (mActiveClickType == AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL) {
                if (mAutoclickScrollPanel != null) {
                    // Save the last cursor position at the moment when sendClick() is called.
                    if (mClickScheduler != null && mClickScheduler.mLastMotionEvent != null) {
                        final int pointerIndex = mClickScheduler.mLastMotionEvent.getActionIndex();
                        mLastCursorX = mClickScheduler.mLastMotionEvent.getX(pointerIndex);
                        mLastCursorY = mClickScheduler.mLastMotionEvent.getY(pointerIndex);
                    }
                    mAutoclickScrollPanel.show();
                }
                return;
+1 −1
Original line number Diff line number Diff line
@@ -179,7 +179,7 @@ public class AutoclickScrollPanel {
    private WindowManager.LayoutParams getLayoutParams() {
        final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
        layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        layoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
        layoutParams.setFitInsetsTypes(WindowInsets.Type.statusBars());
        layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+121 −0
Original line number Diff line number Diff line
@@ -88,6 +88,23 @@ public class AutoclickControllerTest {
        }
    }

    public static class ScrollEventCaptor extends BaseEventStreamTransformation {
        public MotionEvent scrollEvent;
        public int eventCount = 0;

        @Override
        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
            if (event.getAction() == MotionEvent.ACTION_SCROLL) {
                if (scrollEvent != null) {
                    scrollEvent.recycle();
                }
                scrollEvent = MotionEvent.obtain(event);
                eventCount++;
            }
            super.onMotionEvent(event, rawEvent, policyFlags);
        }
    }

    @Before
    public void setUp() {
        mTestableLooper = TestableLooper.get(this);
@@ -918,6 +935,110 @@ public class AutoclickControllerTest {
                MotionEvent.BUTTON_PRIMARY);
    }

    @Test
    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
    public void sendClick_updateLastCursorAndScrollAtThatLocation() {
        // Set up event capturer to track scroll events.
        ScrollEventCaptor scrollCaptor = new ScrollEventCaptor();
        mController.setNext(scrollCaptor);

        // Initialize controller with mouse event.
        injectFakeMouseActionHoverMoveEvent();

        // Mock the scroll panel.
        AutoclickScrollPanel mockScrollPanel = mock(AutoclickScrollPanel.class);
        mController.mAutoclickScrollPanel = mockScrollPanel;

        // Set click type to scroll.
        mController.clickPanelController.handleAutoclickTypeChange(
                AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL);

        // Set cursor position.
        float expectedX = 75f;
        float expectedY = 125f;
        mController.mLastCursorX = expectedX;
        mController.mLastCursorY = expectedY;

        // Trigger scroll action in up direction.
        mController.mScrollPanelController.onHoverButtonChange(
                AutoclickScrollPanel.DIRECTION_UP, true);

        // Verify scroll event happens at last cursor location.
        assertThat(scrollCaptor.scrollEvent).isNotNull();
        assertThat(scrollCaptor.scrollEvent.getX()).isEqualTo(expectedX);
        assertThat(scrollCaptor.scrollEvent.getY()).isEqualTo(expectedY);
    }

    @Test
    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
    public void handleScroll_generatesCorrectScrollEvents() {
        ScrollEventCaptor scrollCaptor = new ScrollEventCaptor();
        mController.setNext(scrollCaptor);

        // Initialize controller.
        injectFakeMouseActionHoverMoveEvent();

        // Set cursor position.
        final float expectedX = 100f;
        final float expectedY = 200f;
        mController.mLastCursorX = expectedX;
        mController.mLastCursorY = expectedY;

        // Test UP direction.
        mController.mScrollPanelController.onHoverButtonChange(
                AutoclickScrollPanel.DIRECTION_UP, true);

        // Verify basic event properties.
        assertThat(scrollCaptor.eventCount).isEqualTo(1);
        assertThat(scrollCaptor.scrollEvent).isNotNull();
        assertThat(scrollCaptor.scrollEvent.getAction()).isEqualTo(MotionEvent.ACTION_SCROLL);
        assertThat(scrollCaptor.scrollEvent.getX()).isEqualTo(expectedX);
        assertThat(scrollCaptor.scrollEvent.getY()).isEqualTo(expectedY);

        // Verify UP direction uses correct axis values.
        float vScrollUp = scrollCaptor.scrollEvent.getAxisValue(MotionEvent.AXIS_VSCROLL);
        float hScrollUp = scrollCaptor.scrollEvent.getAxisValue(MotionEvent.AXIS_HSCROLL);
        assertThat(vScrollUp).isGreaterThan(0);
        assertThat(hScrollUp).isEqualTo(0);

        // Test DOWN direction.
        mController.mScrollPanelController.onHoverButtonChange(
                AutoclickScrollPanel.DIRECTION_DOWN, true);

        // Verify DOWN direction uses correct axis values.
        assertThat(scrollCaptor.eventCount).isEqualTo(2);
        float vScrollDown = scrollCaptor.scrollEvent.getAxisValue(MotionEvent.AXIS_VSCROLL);
        float hScrollDown = scrollCaptor.scrollEvent.getAxisValue(MotionEvent.AXIS_HSCROLL);
        assertThat(vScrollDown).isLessThan(0);
        assertThat(hScrollDown).isEqualTo(0);

        // Test LEFT direction.
        mController.mScrollPanelController.onHoverButtonChange(
                AutoclickScrollPanel.DIRECTION_LEFT, true);

        // Verify LEFT direction uses correct axis values.
        assertThat(scrollCaptor.eventCount).isEqualTo(3);
        float vScrollLeft = scrollCaptor.scrollEvent.getAxisValue(MotionEvent.AXIS_VSCROLL);
        float hScrollLeft = scrollCaptor.scrollEvent.getAxisValue(MotionEvent.AXIS_HSCROLL);
        assertThat(hScrollLeft).isGreaterThan(0);
        assertThat(vScrollLeft).isEqualTo(0);

        // Test RIGHT direction.
        mController.mScrollPanelController.onHoverButtonChange(
                AutoclickScrollPanel.DIRECTION_RIGHT, true);

        // Verify RIGHT direction uses correct axis values.
        assertThat(scrollCaptor.eventCount).isEqualTo(4);
        float vScrollRight = scrollCaptor.scrollEvent.getAxisValue(MotionEvent.AXIS_VSCROLL);
        float hScrollRight = scrollCaptor.scrollEvent.getAxisValue(MotionEvent.AXIS_HSCROLL);
        assertThat(hScrollRight).isLessThan(0);
        assertThat(vScrollRight).isEqualTo(0);

        // Verify scroll cursor position is preserved.
        assertThat(scrollCaptor.scrollEvent.getX()).isEqualTo(expectedX);
        assertThat(scrollCaptor.scrollEvent.getY()).isEqualTo(expectedY);
    }

    private void injectFakeMouseActionHoverMoveEvent() {
        MotionEvent event = getFakeMotionHoverMoveEvent();
        event.setSource(InputDevice.SOURCE_MOUSE);