Loading services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java +18 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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()); Loading services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java +89 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; }); } /** Loading @@ -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. Loading services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java +76 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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(); } } Loading
services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java +18 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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()); Loading
services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java +89 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; }); } /** Loading @@ -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. Loading
services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java +76 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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(); } }