Loading core/res/res/layout/accessibility_autoclick_scroll_panel.xml +2 −2 Original line number Diff line number Diff line Loading @@ -14,7 +14,7 @@ limitations under the License. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <com.android.server.accessibility.autoclick.AutoclickLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/accessibility_autoclick_scroll_panel" android:layout_width="wrap_content" android:layout_height="wrap_content" Loading Loading @@ -89,4 +89,4 @@ android:src="@drawable/accessibility_autoclick_scroll_down" /> </LinearLayout> </LinearLayout> </com.android.server.accessibility.autoclick.AutoclickLinearLayout> services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java +55 −37 Original line number Diff line number Diff line Loading @@ -44,7 +44,6 @@ import android.net.Uri; import android.os.Handler; import android.os.SystemClock; import android.provider.Settings; import android.util.Slog; import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; Loading Loading @@ -93,10 +92,11 @@ public class AutoclickController extends BaseEventStreamTransformation { private final AccessibilityTraceManager mTrace; private final Context mContext; private final int mUserId; // The position that scroll actual happens. @VisibleForTesting float mLastCursorX; float mScrollCursorX; @VisibleForTesting float mLastCursorY; float mScrollCursorY; // Lazily created on the first mouse motion event. @VisibleForTesting ClickScheduler mClickScheduler; Loading Loading @@ -351,11 +351,11 @@ public class AutoclickController extends BaseEventStreamTransformation { pointerProps[0].id = 0; pointerProps[0].toolType = MotionEvent.TOOL_TYPE_MOUSE; // Create pointer coordinates at the last cursor position. // Create pointer coordinates at the scroll cursor position. PointerCoords[] pointerCoords = new PointerCoords[1]; pointerCoords[0] = new PointerCoords(); pointerCoords[0].x = mLastCursorX; pointerCoords[0].y = mLastCursorY; pointerCoords[0].x = mScrollCursorX; pointerCoords[0].y = mScrollCursorY; // Set scroll values based on direction. switch (direction) { Loading Loading @@ -928,37 +928,8 @@ public class AutoclickController extends BaseEventStreamTransformation { return; } if (mAutoclickScrollPanel != null && mAutoclickScrollPanel.isVisible()) { // 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); // Remove previous scroll panel if exists. if (mAutoclickScrollPanel.isVisible()) { mAutoclickScrollPanel.hide(); } // Show scroll panel at the cursor position. mAutoclickScrollPanel.show(mLastCursorX, mLastCursorY); } else { // Fallback: Show scroll panel at its default position (center of screen). Slog.w(LOG_TAG, "Showing scroll panel at default position - no cursor position " + "available"); mAutoclickScrollPanel.show(); } } // Handle scroll-specific click behavior. if (handleScrollClick()) { return; } Loading Loading @@ -1015,6 +986,53 @@ public class AutoclickController extends BaseEventStreamTransformation { sendMotionEvent(actionButton, now); } /** * Handles scroll-specific click behavior when autoclick is triggered. * * @return true if scroll handling was performed (no further click processing needed), * false if regular click processing should continue. */ private boolean handleScrollClick() { // Only handle scroll type clicks. if (mActiveClickType != AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL || mAutoclickScrollPanel == null) { return false; } // Trigger left click instead of scroll when hovering over type panel. if (mAutoclickTypePanel.isHovered()) { return false; } boolean isPanelVisible = mAutoclickScrollPanel.isVisible(); boolean isPanelHovered = mAutoclickScrollPanel.isHovered(); // Handle exit button hover case. if (isPanelVisible && mHoveredDirection == AutoclickScrollPanel.DIRECTION_EXIT) { exitScrollMode(); return true; } // Update cursor position when not hovering over panels. if (!isPanelHovered) { final int pointerIndex = mLastMotionEvent.getActionIndex(); mScrollCursorX = mLastMotionEvent.getX(pointerIndex); mScrollCursorY = mLastMotionEvent.getY(pointerIndex); } // Show or reposition panel. if (isPanelVisible && !isPanelHovered) { // Reposition panel when cursor is outside the panel. mAutoclickScrollPanel.hide(); mAutoclickScrollPanel.show(mScrollCursorX, mScrollCursorY); } else if (!isPanelVisible) { // First time showing the panel. mAutoclickScrollPanel.show(mScrollCursorX, mScrollCursorY); } return true; } private void sendMotionEvent(int actionButton, long eventTime) { MotionEvent downEvent = MotionEvent.obtain( Loading services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java +8 −3 Original line number Diff line number Diff line Loading @@ -62,7 +62,7 @@ public class AutoclickScrollPanel { public @interface ScrollDirection {} private final Context mContext; private final View mContentView; private final AutoclickLinearLayout mContentView; private final WindowManager mWindowManager; private final WindowManager.LayoutParams mParams; private ScrollPanelControllerInterface mScrollPanelController; Loading Loading @@ -98,7 +98,7 @@ public class AutoclickScrollPanel { mContext = context; mWindowManager = windowManager; mScrollPanelController = controller; mContentView = LayoutInflater.from(context).inflate( mContentView = (AutoclickLinearLayout) LayoutInflater.from(context).inflate( R.layout.accessibility_autoclick_scroll_panel, null); mParams = getDefaultLayoutParams(); Loading Loading @@ -266,7 +266,12 @@ public class AutoclickScrollPanel { } @VisibleForTesting public View getContentViewForTesting() { public boolean isHovered() { return mContentView.isHovered(); } @VisibleForTesting public AutoclickLinearLayout getContentViewForTesting() { return mContentView; } Loading services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java +90 −4 Original line number Diff line number Diff line Loading @@ -820,8 +820,8 @@ public class AutoclickControllerTest { // Set cursor position. float expectedX = 75f; float expectedY = 125f; mController.mLastCursorX = expectedX; mController.mLastCursorY = expectedY; mController.mScrollCursorX = expectedX; mController.mScrollCursorY = expectedY; // Trigger scroll action in up direction. mController.mScrollPanelController.onHoverButtonChange( Loading @@ -845,8 +845,8 @@ public class AutoclickControllerTest { // Set cursor position. final float expectedX = 100f; final float expectedY = 200f; mController.mLastCursorX = expectedX; mController.mLastCursorY = expectedY; mController.mScrollCursorX = expectedX; mController.mScrollCursorY = expectedY; // Test UP direction. mController.mScrollPanelController.onHoverButtonChange( Loading Loading @@ -903,6 +903,92 @@ public class AutoclickControllerTest { assertThat(scrollCaptor.scrollEvent.getY()).isEqualTo(expectedY); } @Test @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) public void scrollCursor_maintainsScrollPositionWhenPanelHovered() { ScrollEventCaptor scrollCaptor = new ScrollEventCaptor(); mController.setNext(scrollCaptor); // Initialize controller. injectFakeMouseActionHoverMoveEvent(); mController.mClickScheduler.updateDelay(0); // Set click type to scroll. mController.clickPanelController.handleAutoclickTypeChange( AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL); // Set cursor position. float initialX = 100f; float initialY = 200f; mController.mScrollCursorX = initialX; mController.mScrollCursorY = initialY; // Create mock panel that is hovered. AutoclickScrollPanel mockScrollPanel = mock(AutoclickScrollPanel.class); when(mockScrollPanel.isHovered()).thenReturn(true); when(mockScrollPanel.isVisible()).thenReturn(true); mController.mAutoclickScrollPanel = mockScrollPanel; // Move cursor to panel position. float newX = 300f; float newY = 400f; injectFakeMouseMoveEvent(newX, newY, MotionEvent.ACTION_HOVER_MOVE); mController.mClickScheduler.updateDelay(0); mTestableLooper.processAllMessages(); // Trigger scroll action in up direction. mController.mScrollPanelController.onHoverButtonChange( AutoclickScrollPanel.DIRECTION_UP, true); // Verify scroll event still happens at the original position instead of new location. assertThat(scrollCaptor.scrollEvent).isNotNull(); assertThat(scrollCaptor.scrollEvent.getX()).isEqualTo(initialX); assertThat(scrollCaptor.scrollEvent.getY()).isEqualTo(initialY); } @Test @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) public void scrollCursor_updateScrollPositionWhenPanelNotHovered() { ScrollEventCaptor scrollCaptor = new ScrollEventCaptor(); mController.setNext(scrollCaptor); // Initialize controller. injectFakeMouseActionHoverMoveEvent(); mController.mClickScheduler.updateDelay(0); // Set click type to scroll. mController.clickPanelController.handleAutoclickTypeChange( AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL); // Set cursor position. float initialX = 100f; float initialY = 200f; mController.mScrollCursorX = initialX; mController.mScrollCursorY = initialY; // Create mock panel that is not hovered. AutoclickScrollPanel mockScrollPanel = mock(AutoclickScrollPanel.class); when(mockScrollPanel.isHovered()).thenReturn(false); when(mockScrollPanel.isVisible()).thenReturn(true); mController.mAutoclickScrollPanel = mockScrollPanel; // Move cursor to new position. float newX = 300f; float newY = 400f; injectFakeMouseMoveEvent(newX, newY, MotionEvent.ACTION_HOVER_MOVE); mController.mClickScheduler.updateDelay(0); mTestableLooper.processAllMessages(); // Trigger scroll action in up direction. mController.mScrollPanelController.onHoverButtonChange( AutoclickScrollPanel.DIRECTION_UP, true); // Verify scroll event happens at the new position. assertThat(scrollCaptor.scrollEvent).isNotNull(); assertThat(scrollCaptor.scrollEvent.getX()).isEqualTo(newX); assertThat(scrollCaptor.scrollEvent.getY()).isEqualTo(newY); } @Test @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) public void sendClick_clickType_doubleclick_triggerClickTwice() { Loading Loading
core/res/res/layout/accessibility_autoclick_scroll_panel.xml +2 −2 Original line number Diff line number Diff line Loading @@ -14,7 +14,7 @@ limitations under the License. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <com.android.server.accessibility.autoclick.AutoclickLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/accessibility_autoclick_scroll_panel" android:layout_width="wrap_content" android:layout_height="wrap_content" Loading Loading @@ -89,4 +89,4 @@ android:src="@drawable/accessibility_autoclick_scroll_down" /> </LinearLayout> </LinearLayout> </com.android.server.accessibility.autoclick.AutoclickLinearLayout>
services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java +55 −37 Original line number Diff line number Diff line Loading @@ -44,7 +44,6 @@ import android.net.Uri; import android.os.Handler; import android.os.SystemClock; import android.provider.Settings; import android.util.Slog; import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; Loading Loading @@ -93,10 +92,11 @@ public class AutoclickController extends BaseEventStreamTransformation { private final AccessibilityTraceManager mTrace; private final Context mContext; private final int mUserId; // The position that scroll actual happens. @VisibleForTesting float mLastCursorX; float mScrollCursorX; @VisibleForTesting float mLastCursorY; float mScrollCursorY; // Lazily created on the first mouse motion event. @VisibleForTesting ClickScheduler mClickScheduler; Loading Loading @@ -351,11 +351,11 @@ public class AutoclickController extends BaseEventStreamTransformation { pointerProps[0].id = 0; pointerProps[0].toolType = MotionEvent.TOOL_TYPE_MOUSE; // Create pointer coordinates at the last cursor position. // Create pointer coordinates at the scroll cursor position. PointerCoords[] pointerCoords = new PointerCoords[1]; pointerCoords[0] = new PointerCoords(); pointerCoords[0].x = mLastCursorX; pointerCoords[0].y = mLastCursorY; pointerCoords[0].x = mScrollCursorX; pointerCoords[0].y = mScrollCursorY; // Set scroll values based on direction. switch (direction) { Loading Loading @@ -928,37 +928,8 @@ public class AutoclickController extends BaseEventStreamTransformation { return; } if (mAutoclickScrollPanel != null && mAutoclickScrollPanel.isVisible()) { // 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); // Remove previous scroll panel if exists. if (mAutoclickScrollPanel.isVisible()) { mAutoclickScrollPanel.hide(); } // Show scroll panel at the cursor position. mAutoclickScrollPanel.show(mLastCursorX, mLastCursorY); } else { // Fallback: Show scroll panel at its default position (center of screen). Slog.w(LOG_TAG, "Showing scroll panel at default position - no cursor position " + "available"); mAutoclickScrollPanel.show(); } } // Handle scroll-specific click behavior. if (handleScrollClick()) { return; } Loading Loading @@ -1015,6 +986,53 @@ public class AutoclickController extends BaseEventStreamTransformation { sendMotionEvent(actionButton, now); } /** * Handles scroll-specific click behavior when autoclick is triggered. * * @return true if scroll handling was performed (no further click processing needed), * false if regular click processing should continue. */ private boolean handleScrollClick() { // Only handle scroll type clicks. if (mActiveClickType != AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL || mAutoclickScrollPanel == null) { return false; } // Trigger left click instead of scroll when hovering over type panel. if (mAutoclickTypePanel.isHovered()) { return false; } boolean isPanelVisible = mAutoclickScrollPanel.isVisible(); boolean isPanelHovered = mAutoclickScrollPanel.isHovered(); // Handle exit button hover case. if (isPanelVisible && mHoveredDirection == AutoclickScrollPanel.DIRECTION_EXIT) { exitScrollMode(); return true; } // Update cursor position when not hovering over panels. if (!isPanelHovered) { final int pointerIndex = mLastMotionEvent.getActionIndex(); mScrollCursorX = mLastMotionEvent.getX(pointerIndex); mScrollCursorY = mLastMotionEvent.getY(pointerIndex); } // Show or reposition panel. if (isPanelVisible && !isPanelHovered) { // Reposition panel when cursor is outside the panel. mAutoclickScrollPanel.hide(); mAutoclickScrollPanel.show(mScrollCursorX, mScrollCursorY); } else if (!isPanelVisible) { // First time showing the panel. mAutoclickScrollPanel.show(mScrollCursorX, mScrollCursorY); } return true; } private void sendMotionEvent(int actionButton, long eventTime) { MotionEvent downEvent = MotionEvent.obtain( Loading
services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java +8 −3 Original line number Diff line number Diff line Loading @@ -62,7 +62,7 @@ public class AutoclickScrollPanel { public @interface ScrollDirection {} private final Context mContext; private final View mContentView; private final AutoclickLinearLayout mContentView; private final WindowManager mWindowManager; private final WindowManager.LayoutParams mParams; private ScrollPanelControllerInterface mScrollPanelController; Loading Loading @@ -98,7 +98,7 @@ public class AutoclickScrollPanel { mContext = context; mWindowManager = windowManager; mScrollPanelController = controller; mContentView = LayoutInflater.from(context).inflate( mContentView = (AutoclickLinearLayout) LayoutInflater.from(context).inflate( R.layout.accessibility_autoclick_scroll_panel, null); mParams = getDefaultLayoutParams(); Loading Loading @@ -266,7 +266,12 @@ public class AutoclickScrollPanel { } @VisibleForTesting public View getContentViewForTesting() { public boolean isHovered() { return mContentView.isHovered(); } @VisibleForTesting public AutoclickLinearLayout getContentViewForTesting() { return mContentView; } Loading
services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java +90 −4 Original line number Diff line number Diff line Loading @@ -820,8 +820,8 @@ public class AutoclickControllerTest { // Set cursor position. float expectedX = 75f; float expectedY = 125f; mController.mLastCursorX = expectedX; mController.mLastCursorY = expectedY; mController.mScrollCursorX = expectedX; mController.mScrollCursorY = expectedY; // Trigger scroll action in up direction. mController.mScrollPanelController.onHoverButtonChange( Loading @@ -845,8 +845,8 @@ public class AutoclickControllerTest { // Set cursor position. final float expectedX = 100f; final float expectedY = 200f; mController.mLastCursorX = expectedX; mController.mLastCursorY = expectedY; mController.mScrollCursorX = expectedX; mController.mScrollCursorY = expectedY; // Test UP direction. mController.mScrollPanelController.onHoverButtonChange( Loading Loading @@ -903,6 +903,92 @@ public class AutoclickControllerTest { assertThat(scrollCaptor.scrollEvent.getY()).isEqualTo(expectedY); } @Test @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) public void scrollCursor_maintainsScrollPositionWhenPanelHovered() { ScrollEventCaptor scrollCaptor = new ScrollEventCaptor(); mController.setNext(scrollCaptor); // Initialize controller. injectFakeMouseActionHoverMoveEvent(); mController.mClickScheduler.updateDelay(0); // Set click type to scroll. mController.clickPanelController.handleAutoclickTypeChange( AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL); // Set cursor position. float initialX = 100f; float initialY = 200f; mController.mScrollCursorX = initialX; mController.mScrollCursorY = initialY; // Create mock panel that is hovered. AutoclickScrollPanel mockScrollPanel = mock(AutoclickScrollPanel.class); when(mockScrollPanel.isHovered()).thenReturn(true); when(mockScrollPanel.isVisible()).thenReturn(true); mController.mAutoclickScrollPanel = mockScrollPanel; // Move cursor to panel position. float newX = 300f; float newY = 400f; injectFakeMouseMoveEvent(newX, newY, MotionEvent.ACTION_HOVER_MOVE); mController.mClickScheduler.updateDelay(0); mTestableLooper.processAllMessages(); // Trigger scroll action in up direction. mController.mScrollPanelController.onHoverButtonChange( AutoclickScrollPanel.DIRECTION_UP, true); // Verify scroll event still happens at the original position instead of new location. assertThat(scrollCaptor.scrollEvent).isNotNull(); assertThat(scrollCaptor.scrollEvent.getX()).isEqualTo(initialX); assertThat(scrollCaptor.scrollEvent.getY()).isEqualTo(initialY); } @Test @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) public void scrollCursor_updateScrollPositionWhenPanelNotHovered() { ScrollEventCaptor scrollCaptor = new ScrollEventCaptor(); mController.setNext(scrollCaptor); // Initialize controller. injectFakeMouseActionHoverMoveEvent(); mController.mClickScheduler.updateDelay(0); // Set click type to scroll. mController.clickPanelController.handleAutoclickTypeChange( AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL); // Set cursor position. float initialX = 100f; float initialY = 200f; mController.mScrollCursorX = initialX; mController.mScrollCursorY = initialY; // Create mock panel that is not hovered. AutoclickScrollPanel mockScrollPanel = mock(AutoclickScrollPanel.class); when(mockScrollPanel.isHovered()).thenReturn(false); when(mockScrollPanel.isVisible()).thenReturn(true); mController.mAutoclickScrollPanel = mockScrollPanel; // Move cursor to new position. float newX = 300f; float newY = 400f; injectFakeMouseMoveEvent(newX, newY, MotionEvent.ACTION_HOVER_MOVE); mController.mClickScheduler.updateDelay(0); mTestableLooper.processAllMessages(); // Trigger scroll action in up direction. mController.mScrollPanelController.onHoverButtonChange( AutoclickScrollPanel.DIRECTION_UP, true); // Verify scroll event happens at the new position. assertThat(scrollCaptor.scrollEvent).isNotNull(); assertThat(scrollCaptor.scrollEvent.getX()).isEqualTo(newX); assertThat(scrollCaptor.scrollEvent.getY()).isEqualTo(newY); } @Test @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) public void sendClick_clickType_doubleclick_triggerClickTwice() { Loading