diff options
author | 2025-03-06 22:24:01 -0800 | |
---|---|---|
committer | 2025-03-06 22:24:01 -0800 | |
commit | a6cc743a2f2ffa174b2a1314c413cf2fab0f0f77 (patch) | |
tree | 7703757a4efaaee51c56a2c13d4a82c8abb8bf0a | |
parent | 3ee821a4b67062df4b7265ce0445b76fa446bd98 (diff) | |
parent | f7ee48d8f7aa4bc7b1b70047857708fee4326828 (diff) |
Merge "A11y: autoclickScrollPanel - Add Button Hover listeners" into main
3 files changed, 183 insertions, 3 deletions
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java index cc93d0887d89..a71224a68125 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java @@ -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()); diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java index 86f79a83ea28..e79be502a6fc 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java @@ -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; + }); } /** @@ -67,6 +142,19 @@ public class AutoclickScrollPanel { } /** + * 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. */ diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java index f445b50c7d9c..02361ff259c2 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java @@ -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(); + } } |