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

Commit 7deab1d6 authored by Gavin Williams's avatar Gavin Williams
Browse files

a11y: Fix autoclicking when hovering panel

Attempting to click a button on the autoclick panel should always
peform a normal left click. This did not work while in the two click
modes: Dragging & Scrolling.

To resolve this, at the beginning of the click execution explicitly set
the click type to AUTOCLICK_TYPE_LEFT_CLICK if the autoclick panel is
hovered.

Also reformat tests to use common initializer function.

Bug: b/395094903
Test: AutoclickControllerTest
Flag: com.android.server.accessibility.enable_autoclick_indicator
Change-Id: I292c3496fd926ce2405ca9af59ffbcbd1f14a842
parent 2a2f062d
Loading
Loading
Loading
Loading
+23 −25
Original line number Diff line number Diff line
@@ -136,6 +136,10 @@ public class AutoclickController extends BaseEventStreamTransformation {
                    if (clickType != AUTOCLICK_TYPE_SCROLL && mAutoclickScrollPanel != null) {
                        mAutoclickScrollPanel.hide();
                    }

                    if (clickType != AUTOCLICK_TYPE_DRAG && mDragModeIsDragging) {
                        mClickScheduler.clearDraggingState();
                    }
                }

                @Override
@@ -936,6 +940,12 @@ public class AutoclickController extends BaseEventStreamTransformation {
                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
            // types.
            int selectedClickType = mHoveredState ? AUTOCLICK_TYPE_LEFT_CLICK : mActiveClickType;

            // Handle scroll-specific click behavior.
            if (handleScrollClick()) {
                return;
@@ -959,17 +969,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
            final long now = SystemClock.uptimeMillis();

            int actionButton = BUTTON_PRIMARY;
            if (mHoveredState) {
                // 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 types.
                actionButton = BUTTON_PRIMARY;
            } else {
                switch (mActiveClickType) {
                    case AUTOCLICK_TYPE_LEFT_CLICK:
                        actionButton = BUTTON_PRIMARY;
                        break;
            switch (selectedClickType) {
                case AUTOCLICK_TYPE_RIGHT_CLICK:
                    actionButton = BUTTON_SECONDARY;
                    break;
@@ -989,8 +989,6 @@ public class AutoclickController extends BaseEventStreamTransformation {
                default:
                    break;
            }
            }

            sendMotionEvent(actionButton, now);
        }

+105 −84
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ public class AutoclickControllerTest {
    @Mock private AccessibilityTraceManager mMockTrace;
    @Mock private WindowManager mMockWindowManager;
    private AutoclickController mController;
    private MotionEventCaptor mMotionEventCaptor;

    private static class MotionEventCaptor extends BaseEventStreamTransformation {
        public MotionEvent downEvent;
@@ -677,31 +678,21 @@ public class AutoclickControllerTest {

    @Test
    public void sendClick_clickType_leftClick() {
        MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
        mController.setNext(motionEventCaptor);

        injectFakeMouseActionHoverMoveEvent();
        // Set delay to zero so click is scheduled to run immediately.
        mController.mClickScheduler.updateDelay(0);
        initializeAutoclick();

        // Send hover move event.
        injectFakeMouseMoveEvent(/* x= */ 30f, /* y= */ 0, MotionEvent.ACTION_HOVER_MOVE);
        mTestableLooper.processAllMessages();

        // Verify left click sent.
        assertThat(motionEventCaptor.downEvent).isNotNull();
        assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo(
        assertThat(mMotionEventCaptor.downEvent).isNotNull();
        assertThat(mMotionEventCaptor.downEvent.getButtonState()).isEqualTo(
                MotionEvent.BUTTON_PRIMARY);
    }

    @Test
    public void sendClick_clickType_rightClick() {
        MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
        mController.setNext(motionEventCaptor);

        injectFakeMouseActionHoverMoveEvent();
        // Set delay to zero so click is scheduled to run immediately.
        mController.mClickScheduler.updateDelay(0);
        initializeAutoclick();

        // Set click type to right click.
        mController.clickPanelController.handleAutoclickTypeChange(
@@ -714,20 +705,15 @@ public class AutoclickControllerTest {
        mTestableLooper.processAllMessages();

        // Verify right click sent.
        assertThat(motionEventCaptor.downEvent).isNotNull();
        assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo(
        assertThat(mMotionEventCaptor.downEvent).isNotNull();
        assertThat(mMotionEventCaptor.downEvent.getButtonState()).isEqualTo(
                MotionEvent.BUTTON_SECONDARY);
    }

    @Test
    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
    public void sendClick_clickType_scroll_showsScrollPanelOnlyOnce() {
        MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
        mController.setNext(motionEventCaptor);

        injectFakeMouseActionHoverMoveEvent();
        // Set delay to zero so click is scheduled to run immediately.
        mController.mClickScheduler.updateDelay(0);
        initializeAutoclick();

        // Set click type to scroll.
        mController.clickPanelController.handleAutoclickTypeChange(
@@ -743,7 +729,7 @@ public class AutoclickControllerTest {

        // Verify scroll panel is shown once.
        verify(mockScrollPanel, times(1)).show(anyFloat(), anyFloat());
        assertThat(motionEventCaptor.downEvent).isNull();
        assertThat(mMotionEventCaptor.downEvent).isNull();

        // Second significant hover move event to trigger another autoclick.
        injectFakeMouseMoveEvent(/* x= */ 100f, /* y= */ 100f, MotionEvent.ACTION_HOVER_MOVE);
@@ -751,7 +737,7 @@ public class AutoclickControllerTest {

        // Verify scroll panel is still only shown once (not called again).
        verify(mockScrollPanel, times(1)).show(anyFloat(), anyFloat());
        assertThat(motionEventCaptor.downEvent).isNull();
        assertThat(mMotionEventCaptor.downEvent).isNull();
    }

    @Test
@@ -774,12 +760,7 @@ public class AutoclickControllerTest {
    @Test
    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
    public void hoverOnAutoclickPanel_rightClickType_forceTriggerLeftClick() {
        MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
        mController.setNext(motionEventCaptor);

        injectFakeMouseActionHoverMoveEvent();
        // Set delay to zero so click is scheduled to run immediately.
        mController.mClickScheduler.updateDelay(0);
        initializeAutoclick();

        // Set click type to right click.
        mController.clickPanelController.handleAutoclickTypeChange(
@@ -794,9 +775,60 @@ public class AutoclickControllerTest {
        mTestableLooper.processAllMessages();

        // Verify left click is sent due to the mouse hovering the panel.
        assertThat(motionEventCaptor.downEvent).isNotNull();
        assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo(
        assertThat(mMotionEventCaptor.downEvent).isNotNull();
        assertThat(mMotionEventCaptor.downEvent.getButtonState()).isEqualTo(
                MotionEvent.BUTTON_PRIMARY);
    }

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

        // Set click type to right click.
        mController.clickPanelController.handleAutoclickTypeChange(
                AutoclickTypePanel.AUTOCLICK_TYPE_DRAG);
        // Set mouse to hover panel.
        AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
        when(mockAutoclickTypePanel.isHovered()).thenReturn(true);
        mController.mAutoclickTypePanel = mockAutoclickTypePanel;

        // Send hover move event.
        injectFakeMouseMoveEvent(/* x= */ 30f, /* y= */ 100f, MotionEvent.ACTION_HOVER_MOVE);
        mTestableLooper.processAllMessages();

        // Verify both the down and up left click events are sent due to the mouse hovering the
        // panel.
        assertThat(mMotionEventCaptor.downEvent).isNotNull();
        assertThat(mMotionEventCaptor.downEvent.getButtonState()).isEqualTo(
                MotionEvent.BUTTON_PRIMARY);
        assertThat(mMotionEventCaptor.upEvent).isNotNull();
        assertThat(mController.isDraggingForTesting()).isFalse();
    }

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

        // Set click type to scrolling click.
        mController.clickPanelController.handleAutoclickTypeChange(
                AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL);
        // Set mouse to hover panel.
        AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
        when(mockAutoclickTypePanel.isHovered()).thenReturn(true);
        mController.mAutoclickTypePanel = mockAutoclickTypePanel;

        // Send hover move event.
        injectFakeMouseMoveEvent(/* x= */ 30f, /* y= */ 100f, MotionEvent.ACTION_HOVER_MOVE);
        mTestableLooper.processAllMessages();

        // Verify left click is sent and the scroll panel is hidden due to the mouse hovering the
        // panel.
        assertThat(mMotionEventCaptor.downEvent).isNotNull();
        assertThat(mMotionEventCaptor.downEvent.getButtonState()).isEqualTo(
                MotionEvent.BUTTON_PRIMARY);
        assertThat(mController.mAutoclickScrollPanel.isVisible()).isFalse();
    }

    @Test
@@ -992,12 +1024,7 @@ public class AutoclickControllerTest {
    @Test
    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
    public void sendClick_clickType_doubleclick_triggerClickTwice() {
        MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
        mController.setNext(motionEventCaptor);

        injectFakeMouseActionHoverMoveEvent();
        // Set delay to zero so click is scheduled to run immediately.
        mController.mClickScheduler.updateDelay(0);
        initializeAutoclick();

        // Set click type to double click.
        mController.clickPanelController.handleAutoclickTypeChange(
@@ -1010,22 +1037,17 @@ public class AutoclickControllerTest {
        mTestableLooper.processAllMessages();

        // Verify left click sent.
        assertThat(motionEventCaptor.downEvent).isNotNull();
        assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo(
        assertThat(mMotionEventCaptor.downEvent).isNotNull();
        assertThat(mMotionEventCaptor.downEvent.getButtonState()).isEqualTo(
                MotionEvent.BUTTON_PRIMARY);
        assertThat(motionEventCaptor.eventCount).isEqualTo(
        assertThat(mMotionEventCaptor.eventCount).isEqualTo(
                getNumEventsExpectedFromClick(/* numClicks= */ 2));
    }

    @Test
    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
    public void sendClick_clickType_drag_simulateDragging() {
        MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
        mController.setNext(motionEventCaptor);

        injectFakeMouseActionHoverMoveEvent();
        // Set delay to zero so click is scheduled to run immediately.
        mController.mClickScheduler.updateDelay(0);
        initializeAutoclick();

        // Set click type to drag click.
        mController.clickPanelController.handleAutoclickTypeChange(
@@ -1035,55 +1057,50 @@ public class AutoclickControllerTest {
        mTestableLooper.processAllMessages();

        // Verify only two motion events were sent.
        assertThat(motionEventCaptor.eventCount).isEqualTo(2);
        assertThat(mMotionEventCaptor.eventCount).isEqualTo(2);

        // Verify both events have the same down time.
        assertThat(motionEventCaptor.downEvent).isNotNull();
        assertThat(motionEventCaptor.buttonPressEvent).isNotNull();
        assertThat(motionEventCaptor.downEvent.getDownTime()).isEqualTo(
                motionEventCaptor.buttonPressEvent.getDownTime());
        assertThat(mMotionEventCaptor.downEvent).isNotNull();
        assertThat(mMotionEventCaptor.buttonPressEvent).isNotNull();
        assertThat(mMotionEventCaptor.downEvent.getDownTime()).isEqualTo(
                mMotionEventCaptor.buttonPressEvent.getDownTime());

        // Move the mouse again to simulate dragging and verify the new mouse event is
        // transformed to a MOVE action and its down time matches the drag initiating click's
        // down time.
        injectFakeMouseMoveEvent(/* x= */ 40, /* y= */ 0, MotionEvent.ACTION_HOVER_MOVE);
        mTestableLooper.processAllMessages();
        assertThat(motionEventCaptor.eventCount).isEqualTo(3);
        assertThat(motionEventCaptor.moveEvent).isNotNull();
        assertThat(motionEventCaptor.moveEvent.getDownTime()).isEqualTo(
                motionEventCaptor.downEvent.getDownTime());
        assertThat(mMotionEventCaptor.eventCount).isEqualTo(3);
        assertThat(mMotionEventCaptor.moveEvent).isNotNull();
        assertThat(mMotionEventCaptor.moveEvent.getDownTime()).isEqualTo(
                mMotionEventCaptor.downEvent.getDownTime());

        // Move the mouse again further now to simulate ending the drag session.
        motionEventCaptor.moveEvent = null;
        motionEventCaptor.eventCount = 0;
        mMotionEventCaptor.moveEvent = null;
        mMotionEventCaptor.eventCount = 0;
        injectFakeMouseMoveEvent(/* x= */ 300, /* y= */ 300, MotionEvent.ACTION_HOVER_MOVE);
        mTestableLooper.processAllMessages();

        // Verify the final 3 clicks were sent: the 1 move event + 2 up type events to end the drag.
        assertThat(motionEventCaptor.eventCount).isEqualTo(3);
        assertThat(mMotionEventCaptor.eventCount).isEqualTo(3);

        // Verify each event matches the same down time as the initiating drag click.
        assertThat(motionEventCaptor.moveEvent).isNotNull();
        assertThat(motionEventCaptor.moveEvent.getDownTime()).isEqualTo(
                motionEventCaptor.downEvent.getDownTime());
        assertThat(motionEventCaptor.buttonReleaseEvent).isNotNull();
        assertThat(motionEventCaptor.buttonReleaseEvent.getDownTime()).isEqualTo(
                motionEventCaptor.downEvent.getDownTime());
        assertThat(motionEventCaptor.upEvent).isNotNull();
        assertThat(motionEventCaptor.upEvent.getDownTime()).isEqualTo(
                motionEventCaptor.downEvent.getDownTime());
        assertThat(mMotionEventCaptor.moveEvent).isNotNull();
        assertThat(mMotionEventCaptor.moveEvent.getDownTime()).isEqualTo(
                mMotionEventCaptor.downEvent.getDownTime());
        assertThat(mMotionEventCaptor.buttonReleaseEvent).isNotNull();
        assertThat(mMotionEventCaptor.buttonReleaseEvent.getDownTime()).isEqualTo(
                mMotionEventCaptor.downEvent.getDownTime());
        assertThat(mMotionEventCaptor.upEvent).isNotNull();
        assertThat(mMotionEventCaptor.upEvent.getDownTime()).isEqualTo(
                mMotionEventCaptor.downEvent.getDownTime());
    }


    @Test
    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
    public void sendClick_clickType_drag_keyEventCancelsDrag() {
        MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
        mController.setNext(motionEventCaptor);

        injectFakeMouseActionHoverMoveEvent();
        // Set delay to zero so click is scheduled to run immediately.
        mController.mClickScheduler.updateDelay(0);
        initializeAutoclick();

        // Set click type to drag click.
        mController.clickPanelController.handleAutoclickTypeChange(
@@ -1104,21 +1121,16 @@ public class AutoclickControllerTest {
        assertThat(mController.isDraggingForTesting()).isFalse();

        // Verify the ACTION_UP was sent for alerting the system that dragging has ended.
        assertThat(motionEventCaptor.upEvent).isNotNull();
        assertThat(motionEventCaptor.downEvent).isNotNull();
        assertThat(motionEventCaptor.upEvent.getDownTime()).isEqualTo(
                motionEventCaptor.downEvent.getDownTime());
        assertThat(mMotionEventCaptor.upEvent).isNotNull();
        assertThat(mMotionEventCaptor.downEvent).isNotNull();
        assertThat(mMotionEventCaptor.upEvent.getDownTime()).isEqualTo(
                mMotionEventCaptor.downEvent.getDownTime());
    }

    @Test
    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
    public void sendClick_clickType_drag_clickTypeDoesNotRevertAfterFirstClick() {
        MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
        mController.setNext(motionEventCaptor);

        injectFakeMouseActionHoverMoveEvent();
        // Set delay to zero so click is scheduled to run immediately.
        mController.mClickScheduler.updateDelay(0);
        initializeAutoclick();

        // Set ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK to true.
        Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
@@ -1206,4 +1218,13 @@ public class AutoclickControllerTest {
    private int getNumEventsExpectedFromClick(int numClicks) {
        return numClicks * 4;
    }

    private void initializeAutoclick() {
        mMotionEventCaptor = new MotionEventCaptor();
        mController.setNext(mMotionEventCaptor);

        injectFakeMouseActionHoverMoveEvent();
        // Set delay to zero so click is scheduled to run immediately.
        mController.mClickScheduler.updateDelay(0);
    }
}