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

Commit f7ee48d8 authored by Longbo Wei's avatar Longbo Wei
Browse files

A11y: autoclickScrollPanel - Add Button Hover listeners

* Add ScrollPanelControllerInterface to handle scroll operations.
* Add button hover listeners for direction buttons and exit buttons.

Video: http://shortn/_LfNmMSm8VC

Bug: b/388845488
Test: AutoclickControllerTest
Flag: com.android.server.accessibility.enable_autoclick_indicator
Change-Id: I50deaa83835d8943d37c19c1266549f0a0ff493e
parent de78dfa6
Loading
Loading
Loading
Loading
+18 −1
Original line number Diff line number Diff line
@@ -124,6 +124,22 @@ public class AutoclickController extends BaseEventStreamTransformation {
                }
            };

    @VisibleForTesting
    final AutoclickScrollPanel.ScrollPanelControllerInterface mScrollPanelController =
            new AutoclickScrollPanel.ScrollPanelControllerInterface() {
                @Override
                public void handleScroll(@AutoclickScrollPanel.ScrollDirection int direction) {
                    // TODO(b/388845721): Perform actual scroll.
                }

                @Override
                public void exitScrollMode() {
                    if (mAutoclickScrollPanel != null) {
                        mAutoclickScrollPanel.hide();
                    }
                }
            };

    public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) {
        mTrace = trace;
        mContext = context;
@@ -168,7 +184,8 @@ public class AutoclickController extends BaseEventStreamTransformation {
        mWindowManager = mContext.getSystemService(WindowManager.class);
        mAutoclickTypePanel =
                new AutoclickTypePanel(mContext, mWindowManager, mUserId, clickPanelController);
        mAutoclickScrollPanel = new AutoclickScrollPanel(mContext, mWindowManager);
        mAutoclickScrollPanel = new AutoclickScrollPanel(mContext, mWindowManager,
                mScrollPanelController);

        mAutoclickTypePanel.show();
        mWindowManager.addView(mAutoclickIndicatorView, mAutoclickIndicatorView.getLayoutParams());
+89 −1
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.accessibility.autoclick;

import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;

import android.annotation.IntDef;
import android.content.Context;
import android.graphics.PixelFormat;
import android.view.Gravity;
@@ -25,23 +26,97 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.widget.ImageButton;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.android.internal.R;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class AutoclickScrollPanel {
    public static final int DIRECTION_UP = 0;
    public static final int DIRECTION_DOWN = 1;
    public static final int DIRECTION_LEFT = 2;
    public static final int DIRECTION_RIGHT = 3;

    @IntDef({
            DIRECTION_UP,
            DIRECTION_DOWN,
            DIRECTION_LEFT,
            DIRECTION_RIGHT
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ScrollDirection {}

    private final Context mContext;
    private final View mContentView;
    private final WindowManager mWindowManager;
    private ScrollPanelControllerInterface mScrollPanelController;

    // Scroll panel buttons.
    private final ImageButton mUpButton;
    private final ImageButton mDownButton;
    private final ImageButton mLeftButton;
    private final ImageButton mRightButton;
    private final ImageButton mExitButton;

    private boolean mInScrollMode = false;

    public AutoclickScrollPanel(Context context, WindowManager windowManager) {
    /**
     * Interface for handling scroll operations.
     */
    public interface ScrollPanelControllerInterface {
        /**
         * Called when a scroll direction is hovered.
         *
         * @param direction The direction to scroll: one of {@link ScrollDirection} values.
         */
        void handleScroll(@ScrollDirection int direction);

        /**
         * Called when the exit button is hovered.
         */
        void exitScrollMode();
    }

    public AutoclickScrollPanel(Context context, WindowManager windowManager,
            ScrollPanelControllerInterface controller) {
        mContext = context;
        mWindowManager = windowManager;
        mScrollPanelController = controller;
        mContentView = LayoutInflater.from(context).inflate(
                R.layout.accessibility_autoclick_scroll_panel, null);

        // Initialize buttons.
        mUpButton = mContentView.findViewById(R.id.scroll_up);
        mLeftButton = mContentView.findViewById(R.id.scroll_left);
        mRightButton = mContentView.findViewById(R.id.scroll_right);
        mDownButton = mContentView.findViewById(R.id.scroll_down);
        mExitButton = mContentView.findViewById(R.id.scroll_exit);

        initializeButtonState();
    }

    /**
     * Sets up hover listeners for scroll panel buttons.
     */
    private void initializeButtonState() {
        // Set up hover listeners for direction buttons.
        setupHoverListenerForDirectionButton(mUpButton, DIRECTION_UP);
        setupHoverListenerForDirectionButton(mLeftButton, DIRECTION_LEFT);
        setupHoverListenerForDirectionButton(mRightButton, DIRECTION_RIGHT);
        setupHoverListenerForDirectionButton(mDownButton, DIRECTION_DOWN);

        // Set up hover listener for exit button.
        mExitButton.setOnHoverListener((v, event) -> {
            if (mScrollPanelController != null) {
                mScrollPanelController.exitScrollMode();
            }
            return true;
        });
    }

    /**
@@ -66,6 +141,19 @@ public class AutoclickScrollPanel {
        mInScrollMode = false;
    }

    /**
     * Sets up a hover listener for a direction button.
     */
    private void setupHoverListenerForDirectionButton(ImageButton button,
            @ScrollDirection int direction) {
        button.setOnHoverListener((v, event) -> {
            if (mScrollPanelController != null) {
                mScrollPanelController.handleScroll(direction);
            }
            return true;
        });
    }

    /**
     * Retrieves the layout params for AutoclickScrollPanel, used when it's added to the Window
     * Manager.
+76 −1
Original line number Diff line number Diff line
@@ -28,7 +28,12 @@ import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageButton;

import com.android.internal.R;

import org.junit.Before;
import org.junit.Rule;
@@ -49,12 +54,31 @@ public class AutoclickScrollPanelTest {
            new TestableContext(getInstrumentation().getContext());

    @Mock private WindowManager mMockWindowManager;
    @Mock private AutoclickScrollPanel.ScrollPanelControllerInterface mMockScrollPanelController;

    private AutoclickScrollPanel mScrollPanel;

    // Scroll panel buttons.
    private ImageButton mUpButton;
    private ImageButton mDownButton;
    private ImageButton mLeftButton;
    private ImageButton mRightButton;
    private ImageButton mExitButton;

    @Before
    public void setUp() {
        mTestableContext.addMockSystemService(Context.WINDOW_SERVICE, mMockWindowManager);
        mScrollPanel = new AutoclickScrollPanel(mTestableContext, mMockWindowManager);
        mScrollPanel = new AutoclickScrollPanel(mTestableContext, mMockWindowManager,
                mMockScrollPanelController);

        View contentView = mScrollPanel.getContentViewForTesting();

        // Initialize buttons.
        mUpButton = contentView.findViewById(R.id.scroll_up);
        mDownButton = contentView.findViewById(R.id.scroll_down);
        mLeftButton = contentView.findViewById(R.id.scroll_left);
        mRightButton = contentView.findViewById(R.id.scroll_right);
        mExitButton = contentView.findViewById(R.id.scroll_exit);
    }

    @Test
@@ -89,4 +113,55 @@ public class AutoclickScrollPanelTest {
        // Verify scroll panel is hidden.
        assertThat(mScrollPanel.isVisible()).isFalse();
    }

    @Test
    public void initialState_correctButtonVisibility() {
        // Verify all expected buttons exist in the view.
        assertThat(mUpButton.getVisibility()).isEqualTo(View.VISIBLE);
        assertThat(mDownButton.getVisibility()).isEqualTo(View.VISIBLE);
        assertThat(mLeftButton.getVisibility()).isEqualTo(View.VISIBLE);
        assertThat(mRightButton.getVisibility()).isEqualTo(View.VISIBLE);
        assertThat(mExitButton.getVisibility()).isEqualTo(View.VISIBLE);
    }

    @Test
    public void directionButtons_onHover_callsHandleScroll() {
        // Test up button.
        triggerHoverEvent(mUpButton);
        verify(mMockScrollPanelController).handleScroll(AutoclickScrollPanel.DIRECTION_UP);

        // Test down button.
        triggerHoverEvent(mDownButton);
        verify(mMockScrollPanelController).handleScroll(AutoclickScrollPanel.DIRECTION_DOWN);

        // Test left button.
        triggerHoverEvent(mLeftButton);
        verify(mMockScrollPanelController).handleScroll(AutoclickScrollPanel.DIRECTION_LEFT);

        // Test right button.
        triggerHoverEvent(mRightButton);
        verify(mMockScrollPanelController).handleScroll(AutoclickScrollPanel.DIRECTION_RIGHT);
    }

    @Test
    public void exitButton_onHover_callsExitScrollMode() {
        // Test exit button.
        triggerHoverEvent(mExitButton);
        verify(mMockScrollPanelController).exitScrollMode();
    }

    // Helper method to simulate a hover event on a view.
    private void triggerHoverEvent(View view) {
        MotionEvent event = MotionEvent.obtain(
                /* downTime= */ 0,
                /* eventTime= */ 0,
                /* action= */ MotionEvent.ACTION_HOVER_ENTER,
                /* x= */ 0,
                /* y= */ 0,
                /* metaState= */ 0);

        // Dispatch the event to the view's OnHoverListener.
        view.dispatchGenericMotionEvent(event);
        event.recycle();
    }
}