Loading services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java +140 −0 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.Configuration; import android.database.ContentObserver; import android.hardware.input.InputManager; import android.net.Uri; import android.os.Handler; import android.os.SystemClock; Loading Loading @@ -248,6 +249,77 @@ public class AutoclickController extends BaseEventStreamTransformation implement } }; @VisibleForTesting InputManagerWrapper mInputManagerWrapper; private final InputManager.InputDeviceListener mInputDeviceListener = new InputManager.InputDeviceListener() { // True when the pointing device is connected, including mouse, touchpad, etc. private boolean mIsPointingDeviceConnected = false; // True when the autoclick type panel is temporarily hidden due to the pointing // device being disconnected. private boolean mTemporaryHideAutoclickTypePanel = false; @Override public void onInputDeviceAdded(int deviceId) { onInputDeviceChanged(deviceId); } @Override public void onInputDeviceRemoved(int deviceId) { onInputDeviceChanged(deviceId); } @Override public void onInputDeviceChanged(int deviceId) { boolean wasConnected = mIsPointingDeviceConnected; mIsPointingDeviceConnected = false; for (final int id : mInputManagerWrapper.getInputDeviceIds()) { final InputDeviceWrapper device = mInputManagerWrapper.getInputDevice(id); if (device == null || !device.isEnabled() || device.isVirtual()) { continue; } if (device.supportsSource(InputDevice.SOURCE_MOUSE) || device.supportsSource(InputDevice.SOURCE_TOUCHPAD) || device.supportsSource(InputDevice.SOURCE_STYLUS) || device.supportsSource(InputDevice.SOURCE_BLUETOOTH_STYLUS)) { mIsPointingDeviceConnected = true; break; } } // If the device state did not change, do nothing. if (wasConnected == mIsPointingDeviceConnected) { return; } // Pointing device state changes from connected to disconnected. if (!mIsPointingDeviceConnected) { if (mAutoclickTypePanel != null) { mTemporaryHideAutoclickTypePanel = true; mAutoclickTypePanel.hide(); if (mAutoclickScrollPanel != null) { mAutoclickScrollPanel.hide(); } } // Pointing device state changes from disconnected to connected and the panel // was temporarily hidden due to the pointing device being disconnected. } else if (mTemporaryHideAutoclickTypePanel && mIsPointingDeviceConnected) { if (mAutoclickTypePanel != null) { mTemporaryHideAutoclickTypePanel = false; mAutoclickTypePanel.show(); // No need to explicitly show the scroll panel here since we don't know // the cursor position when the pointing device is connected. If the // user disconnects the pointing device in scroll mode, another auto // click will trigger the scroll panel to be shown. } } } }; public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) { mTrace = trace; mContext = context; Loading Loading @@ -340,6 +412,12 @@ public class AutoclickController extends BaseEventStreamTransformation implement mAutoclickTypePanel.show(); mContext.registerComponentCallbacks(this); mWindowManager.addView(mAutoclickIndicatorView, mAutoclickIndicatorView.getLayoutParams()); if (mInputManagerWrapper == null) { mInputManagerWrapper = new InputManagerWrapper(mContext.getSystemService(InputManager.class)); } mInputManagerWrapper.registerInputDeviceListener(mInputDeviceListener, handler); } @Override Loading Loading @@ -375,6 +453,10 @@ public class AutoclickController extends BaseEventStreamTransformation implement @Override public void onDestroy() { mContext.unregisterComponentCallbacks(this); if (mInputManagerWrapper != null) { mInputManagerWrapper.unregisterInputDeviceListener(mInputDeviceListener); } if (mAutoclickSettingsObserver != null) { mAutoclickSettingsObserver.stop(); mAutoclickSettingsObserver = null; Loading Loading @@ -593,6 +675,64 @@ public class AutoclickController extends BaseEventStreamTransformation implement } /** A wrapper for the final InputManager class, to allow mocking in tests. */ @VisibleForTesting public static class InputManagerWrapper { private final InputManager mInputManager; InputManagerWrapper(InputManager inputManager) { mInputManager = inputManager; } public void registerInputDeviceListener( InputManager.InputDeviceListener listener, Handler handler) { if (mInputManager == null) return; mInputManager.registerInputDeviceListener(listener, handler); } public void unregisterInputDeviceListener(InputManager.InputDeviceListener listener) { if (mInputManager == null) return; mInputManager.unregisterInputDeviceListener(listener); } public int[] getInputDeviceIds() { if (mInputManager == null) return new int[0]; return mInputManager.getInputDeviceIds(); } public InputDeviceWrapper getInputDevice(int id) { if (mInputManager == null) return null; InputDevice device = mInputManager.getInputDevice(id); return device == null ? null : new InputDeviceWrapper(device); } } /** A wrapper for the final InputDevice class, to allow mocking in tests. */ @VisibleForTesting public static class InputDeviceWrapper { private final InputDevice mInputDevice; InputDeviceWrapper(InputDevice inputDevice) { mInputDevice = inputDevice; } public boolean isEnabled() { return mInputDevice.isEnabled(); } public boolean isVirtual() { return mInputDevice.isVirtual(); } public boolean supportsSource(int source) { return mInputDevice.supportsSource(source); } public int getSources() { return mInputDevice.getSources(); } } /** * Observes and updates various autoclick setting values. */ Loading services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java +23 −0 Original line number Diff line number Diff line Loading @@ -153,6 +153,8 @@ public class AutoclickTypePanel { // Whether autoclick is paused. private boolean mPaused = false; private boolean mIsPanelShown = false; private int mStatusBarHeight = 0; // The current corner position of the panel, default to bottom right. Loading Loading @@ -390,19 +392,32 @@ public class AutoclickTypePanel { } public void show() { if (mIsPanelShown) { return; } // Restores the panel position from saved settings. If no valid position is saved, // defaults to bottom-right corner. restorePanelPosition(); mWindowManager.addView(mContentView, mParams); mIsPanelShown = true; // Update icon after view is laid out on screen to ensure accurate position detection // (getLocationOnScreen only works properly after layout is complete). mContentView.post(() -> { updatePositionButtonIcon(getVisualCorner()); }); // Make sure the selected button is highlighted if not already. This is to handle the // case that the panel is shown when a pointing device is reconnected. toggleSelectedButtonStyle(mSelectedButton, /* isSelected= */ true); } public void hide() { if (!mIsPanelShown) { return; } // Sets the button background to unselected styling, this is necessary to make sure the // button background styling is correct when the panel shows up next time. toggleSelectedButtonStyle(mSelectedButton, /* isSelected= */ false); Loading @@ -411,6 +426,7 @@ public class AutoclickTypePanel { savePanelPosition(); mWindowManager.removeView(mContentView); mIsPanelShown = false; } /** Loading Loading @@ -513,6 +529,13 @@ public class AutoclickTypePanel { updatePositionButtonIcon(mCurrentCorner); } /** Resets the panel position to bottom-right corner. */ @VisibleForTesting void resetPanelPositionForTesting() { setPanelPositionForCorner(mParams, CORNER_BOTTOM_RIGHT); mCurrentCorner = CORNER_BOTTOM_RIGHT; } /** * Sets the panel's gravity and initial x/y offsets based on the specified corner. * @param params The WindowManager.LayoutParams to modify. Loading services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java +118 −1 Original line number Diff line number Diff line Loading @@ -25,8 +25,8 @@ import static com.android.server.testutils.MockitoUtilsKt.eq; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; Loading @@ -35,6 +35,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Configuration; import android.hardware.input.InputManager; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; Loading @@ -59,6 +60,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; Loading Loading @@ -86,6 +88,7 @@ public class AutoclickControllerTest { private AccessibilityTraceManager mMockTrace; @Mock private WindowManager mMockWindowManager; @Mock private AutoclickController.InputManagerWrapper mMockInputManagerWrapper; private AutoclickController mController; private MotionEventCaptor mMotionEventCaptor; Loading Loading @@ -1543,6 +1546,120 @@ public class AutoclickControllerTest { // Verify updateConfiguration was called. verify(spyIndicatorView).onConfigurationChanged(newConfig); } @Test @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) public void onInputDeviceChanged_disconnectAndReconnect_hidesAndShowsTypePanel() { // Setup: one mouse connected initially. mController.mInputManagerWrapper = mMockInputManagerWrapper; when(mMockInputManagerWrapper.getInputDeviceIds()).thenReturn(new int[] {1}); AutoclickController.InputDeviceWrapper mockMouse = mock(AutoclickController.InputDeviceWrapper.class); when(mockMouse.supportsSource(InputDevice.SOURCE_MOUSE)).thenReturn(true); when(mockMouse.isEnabled()).thenReturn(true); when(mockMouse.isVirtual()).thenReturn(false); when(mMockInputManagerWrapper.getInputDevice(1)).thenReturn(mockMouse); // Initialize controller and panels. injectFakeMouseActionHoverMoveEvent(); // Capture the listener. ArgumentCaptor<InputManager.InputDeviceListener> listenerCaptor = ArgumentCaptor.forClass(InputManager.InputDeviceListener.class); verify(mMockInputManagerWrapper) .registerInputDeviceListener(listenerCaptor.capture(), any()); InputManager.InputDeviceListener listener = listenerCaptor.getValue(); // Manually trigger once to establish initial connected state. listener.onInputDeviceChanged(1); mTestableLooper.processAllMessages(); // Mock panels to verify interactions. AutoclickTypePanel mockTypePanel = mock(AutoclickTypePanel.class); AutoclickScrollPanel mockScrollPanel = mock(AutoclickScrollPanel.class); mController.mAutoclickTypePanel = mockTypePanel; mController.mAutoclickScrollPanel = mockScrollPanel; // Action: disconnect mouse. when(mMockInputManagerWrapper.getInputDeviceIds()).thenReturn(new int[0]); listener.onInputDeviceChanged(1); mTestableLooper.processAllMessages(); // Verify panels are hidden. verify(mockTypePanel).hide(); verify(mockScrollPanel).hide(); // Action: reconnect mouse. when(mMockInputManagerWrapper.getInputDeviceIds()).thenReturn(new int[] {1}); listener.onInputDeviceChanged(1); mTestableLooper.processAllMessages(); // Verify type panel is shown, but scroll panel is not. verify(mockTypePanel).show(); verify(mockScrollPanel, Mockito.never()).show(anyFloat(), anyFloat()); } @Test @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) public void onInputDeviceChanged_noConnectionChange_panelsStateUnchanged() { // Setup: one mouse connected initially. mController.mInputManagerWrapper = mMockInputManagerWrapper; when(mMockInputManagerWrapper.getInputDeviceIds()).thenReturn(new int[] {1}); AutoclickController.InputDeviceWrapper mockMouse = mock(AutoclickController.InputDeviceWrapper.class); when(mockMouse.supportsSource(InputDevice.SOURCE_MOUSE)).thenReturn(true); when(mockMouse.isEnabled()).thenReturn(true); when(mockMouse.isVirtual()).thenReturn(false); when(mMockInputManagerWrapper.getInputDevice(1)).thenReturn(mockMouse); // Initialize controller and panels. injectFakeMouseActionHoverMoveEvent(); // Capture the listener. ArgumentCaptor<InputManager.InputDeviceListener> listenerCaptor = ArgumentCaptor.forClass(InputManager.InputDeviceListener.class); verify(mMockInputManagerWrapper) .registerInputDeviceListener(listenerCaptor.capture(), any()); InputManager.InputDeviceListener listener = listenerCaptor.getValue(); // Manually trigger once to establish initial connected state. listener.onInputDeviceChanged(1); mTestableLooper.processAllMessages(); // Mock panels to verify interactions. AutoclickTypePanel mockTypePanel = mock(AutoclickTypePanel.class); AutoclickScrollPanel mockScrollPanel = mock(AutoclickScrollPanel.class); mController.mAutoclickTypePanel = mockTypePanel; mController.mAutoclickScrollPanel = mockScrollPanel; // Action: trigger change, but connection state is the same (connected). listener.onInputDeviceChanged(1); mTestableLooper.processAllMessages(); // Verify panels state is unchanged. verify(mockTypePanel, Mockito.never()).hide(); verify(mockScrollPanel, Mockito.never()).hide(); verify(mockTypePanel, Mockito.never()).show(); // Action: disconnect mouse. when(mMockInputManagerWrapper.getInputDeviceIds()).thenReturn(new int[0]); listener.onInputDeviceChanged(1); mTestableLooper.processAllMessages(); // Verify hide was called once. verify(mockTypePanel, times(1)).hide(); verify(mockScrollPanel, times(1)).hide(); // Action: trigger change, but connection state is the same (disconnected). listener.onInputDeviceChanged(1); mTestableLooper.processAllMessages(); // Verify panels state is unchanged (hide not called again). verify(mockTypePanel, times(1)).hide(); verify(mockScrollPanel, times(1)).hide(); verify(mockTypePanel, Mockito.never()).show(); } /** * ========================================================================= * Helper Functions Loading services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java +4 −0 Original line number Diff line number Diff line Loading @@ -120,6 +120,10 @@ public class AutoclickTypePanelTest { mPauseButton = contentView.findViewById(R.id.accessibility_autoclick_pause_layout); mPositionButton = contentView.findViewById(R.id.accessibility_autoclick_position_layout); mLongPressButton = contentView.findViewById(R.id.accessibility_autoclick_long_press_layout); // Set panel to default bottom-right corner. mAutoclickTypePanel.show(); mAutoclickTypePanel.resetPanelPositionForTesting(); } @Test Loading Loading
services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java +140 −0 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.Configuration; import android.database.ContentObserver; import android.hardware.input.InputManager; import android.net.Uri; import android.os.Handler; import android.os.SystemClock; Loading Loading @@ -248,6 +249,77 @@ public class AutoclickController extends BaseEventStreamTransformation implement } }; @VisibleForTesting InputManagerWrapper mInputManagerWrapper; private final InputManager.InputDeviceListener mInputDeviceListener = new InputManager.InputDeviceListener() { // True when the pointing device is connected, including mouse, touchpad, etc. private boolean mIsPointingDeviceConnected = false; // True when the autoclick type panel is temporarily hidden due to the pointing // device being disconnected. private boolean mTemporaryHideAutoclickTypePanel = false; @Override public void onInputDeviceAdded(int deviceId) { onInputDeviceChanged(deviceId); } @Override public void onInputDeviceRemoved(int deviceId) { onInputDeviceChanged(deviceId); } @Override public void onInputDeviceChanged(int deviceId) { boolean wasConnected = mIsPointingDeviceConnected; mIsPointingDeviceConnected = false; for (final int id : mInputManagerWrapper.getInputDeviceIds()) { final InputDeviceWrapper device = mInputManagerWrapper.getInputDevice(id); if (device == null || !device.isEnabled() || device.isVirtual()) { continue; } if (device.supportsSource(InputDevice.SOURCE_MOUSE) || device.supportsSource(InputDevice.SOURCE_TOUCHPAD) || device.supportsSource(InputDevice.SOURCE_STYLUS) || device.supportsSource(InputDevice.SOURCE_BLUETOOTH_STYLUS)) { mIsPointingDeviceConnected = true; break; } } // If the device state did not change, do nothing. if (wasConnected == mIsPointingDeviceConnected) { return; } // Pointing device state changes from connected to disconnected. if (!mIsPointingDeviceConnected) { if (mAutoclickTypePanel != null) { mTemporaryHideAutoclickTypePanel = true; mAutoclickTypePanel.hide(); if (mAutoclickScrollPanel != null) { mAutoclickScrollPanel.hide(); } } // Pointing device state changes from disconnected to connected and the panel // was temporarily hidden due to the pointing device being disconnected. } else if (mTemporaryHideAutoclickTypePanel && mIsPointingDeviceConnected) { if (mAutoclickTypePanel != null) { mTemporaryHideAutoclickTypePanel = false; mAutoclickTypePanel.show(); // No need to explicitly show the scroll panel here since we don't know // the cursor position when the pointing device is connected. If the // user disconnects the pointing device in scroll mode, another auto // click will trigger the scroll panel to be shown. } } } }; public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) { mTrace = trace; mContext = context; Loading Loading @@ -340,6 +412,12 @@ public class AutoclickController extends BaseEventStreamTransformation implement mAutoclickTypePanel.show(); mContext.registerComponentCallbacks(this); mWindowManager.addView(mAutoclickIndicatorView, mAutoclickIndicatorView.getLayoutParams()); if (mInputManagerWrapper == null) { mInputManagerWrapper = new InputManagerWrapper(mContext.getSystemService(InputManager.class)); } mInputManagerWrapper.registerInputDeviceListener(mInputDeviceListener, handler); } @Override Loading Loading @@ -375,6 +453,10 @@ public class AutoclickController extends BaseEventStreamTransformation implement @Override public void onDestroy() { mContext.unregisterComponentCallbacks(this); if (mInputManagerWrapper != null) { mInputManagerWrapper.unregisterInputDeviceListener(mInputDeviceListener); } if (mAutoclickSettingsObserver != null) { mAutoclickSettingsObserver.stop(); mAutoclickSettingsObserver = null; Loading Loading @@ -593,6 +675,64 @@ public class AutoclickController extends BaseEventStreamTransformation implement } /** A wrapper for the final InputManager class, to allow mocking in tests. */ @VisibleForTesting public static class InputManagerWrapper { private final InputManager mInputManager; InputManagerWrapper(InputManager inputManager) { mInputManager = inputManager; } public void registerInputDeviceListener( InputManager.InputDeviceListener listener, Handler handler) { if (mInputManager == null) return; mInputManager.registerInputDeviceListener(listener, handler); } public void unregisterInputDeviceListener(InputManager.InputDeviceListener listener) { if (mInputManager == null) return; mInputManager.unregisterInputDeviceListener(listener); } public int[] getInputDeviceIds() { if (mInputManager == null) return new int[0]; return mInputManager.getInputDeviceIds(); } public InputDeviceWrapper getInputDevice(int id) { if (mInputManager == null) return null; InputDevice device = mInputManager.getInputDevice(id); return device == null ? null : new InputDeviceWrapper(device); } } /** A wrapper for the final InputDevice class, to allow mocking in tests. */ @VisibleForTesting public static class InputDeviceWrapper { private final InputDevice mInputDevice; InputDeviceWrapper(InputDevice inputDevice) { mInputDevice = inputDevice; } public boolean isEnabled() { return mInputDevice.isEnabled(); } public boolean isVirtual() { return mInputDevice.isVirtual(); } public boolean supportsSource(int source) { return mInputDevice.supportsSource(source); } public int getSources() { return mInputDevice.getSources(); } } /** * Observes and updates various autoclick setting values. */ Loading
services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java +23 −0 Original line number Diff line number Diff line Loading @@ -153,6 +153,8 @@ public class AutoclickTypePanel { // Whether autoclick is paused. private boolean mPaused = false; private boolean mIsPanelShown = false; private int mStatusBarHeight = 0; // The current corner position of the panel, default to bottom right. Loading Loading @@ -390,19 +392,32 @@ public class AutoclickTypePanel { } public void show() { if (mIsPanelShown) { return; } // Restores the panel position from saved settings. If no valid position is saved, // defaults to bottom-right corner. restorePanelPosition(); mWindowManager.addView(mContentView, mParams); mIsPanelShown = true; // Update icon after view is laid out on screen to ensure accurate position detection // (getLocationOnScreen only works properly after layout is complete). mContentView.post(() -> { updatePositionButtonIcon(getVisualCorner()); }); // Make sure the selected button is highlighted if not already. This is to handle the // case that the panel is shown when a pointing device is reconnected. toggleSelectedButtonStyle(mSelectedButton, /* isSelected= */ true); } public void hide() { if (!mIsPanelShown) { return; } // Sets the button background to unselected styling, this is necessary to make sure the // button background styling is correct when the panel shows up next time. toggleSelectedButtonStyle(mSelectedButton, /* isSelected= */ false); Loading @@ -411,6 +426,7 @@ public class AutoclickTypePanel { savePanelPosition(); mWindowManager.removeView(mContentView); mIsPanelShown = false; } /** Loading Loading @@ -513,6 +529,13 @@ public class AutoclickTypePanel { updatePositionButtonIcon(mCurrentCorner); } /** Resets the panel position to bottom-right corner. */ @VisibleForTesting void resetPanelPositionForTesting() { setPanelPositionForCorner(mParams, CORNER_BOTTOM_RIGHT); mCurrentCorner = CORNER_BOTTOM_RIGHT; } /** * Sets the panel's gravity and initial x/y offsets based on the specified corner. * @param params The WindowManager.LayoutParams to modify. Loading
services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java +118 −1 Original line number Diff line number Diff line Loading @@ -25,8 +25,8 @@ import static com.android.server.testutils.MockitoUtilsKt.eq; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; Loading @@ -35,6 +35,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Configuration; import android.hardware.input.InputManager; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; Loading @@ -59,6 +60,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; Loading Loading @@ -86,6 +88,7 @@ public class AutoclickControllerTest { private AccessibilityTraceManager mMockTrace; @Mock private WindowManager mMockWindowManager; @Mock private AutoclickController.InputManagerWrapper mMockInputManagerWrapper; private AutoclickController mController; private MotionEventCaptor mMotionEventCaptor; Loading Loading @@ -1543,6 +1546,120 @@ public class AutoclickControllerTest { // Verify updateConfiguration was called. verify(spyIndicatorView).onConfigurationChanged(newConfig); } @Test @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) public void onInputDeviceChanged_disconnectAndReconnect_hidesAndShowsTypePanel() { // Setup: one mouse connected initially. mController.mInputManagerWrapper = mMockInputManagerWrapper; when(mMockInputManagerWrapper.getInputDeviceIds()).thenReturn(new int[] {1}); AutoclickController.InputDeviceWrapper mockMouse = mock(AutoclickController.InputDeviceWrapper.class); when(mockMouse.supportsSource(InputDevice.SOURCE_MOUSE)).thenReturn(true); when(mockMouse.isEnabled()).thenReturn(true); when(mockMouse.isVirtual()).thenReturn(false); when(mMockInputManagerWrapper.getInputDevice(1)).thenReturn(mockMouse); // Initialize controller and panels. injectFakeMouseActionHoverMoveEvent(); // Capture the listener. ArgumentCaptor<InputManager.InputDeviceListener> listenerCaptor = ArgumentCaptor.forClass(InputManager.InputDeviceListener.class); verify(mMockInputManagerWrapper) .registerInputDeviceListener(listenerCaptor.capture(), any()); InputManager.InputDeviceListener listener = listenerCaptor.getValue(); // Manually trigger once to establish initial connected state. listener.onInputDeviceChanged(1); mTestableLooper.processAllMessages(); // Mock panels to verify interactions. AutoclickTypePanel mockTypePanel = mock(AutoclickTypePanel.class); AutoclickScrollPanel mockScrollPanel = mock(AutoclickScrollPanel.class); mController.mAutoclickTypePanel = mockTypePanel; mController.mAutoclickScrollPanel = mockScrollPanel; // Action: disconnect mouse. when(mMockInputManagerWrapper.getInputDeviceIds()).thenReturn(new int[0]); listener.onInputDeviceChanged(1); mTestableLooper.processAllMessages(); // Verify panels are hidden. verify(mockTypePanel).hide(); verify(mockScrollPanel).hide(); // Action: reconnect mouse. when(mMockInputManagerWrapper.getInputDeviceIds()).thenReturn(new int[] {1}); listener.onInputDeviceChanged(1); mTestableLooper.processAllMessages(); // Verify type panel is shown, but scroll panel is not. verify(mockTypePanel).show(); verify(mockScrollPanel, Mockito.never()).show(anyFloat(), anyFloat()); } @Test @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) public void onInputDeviceChanged_noConnectionChange_panelsStateUnchanged() { // Setup: one mouse connected initially. mController.mInputManagerWrapper = mMockInputManagerWrapper; when(mMockInputManagerWrapper.getInputDeviceIds()).thenReturn(new int[] {1}); AutoclickController.InputDeviceWrapper mockMouse = mock(AutoclickController.InputDeviceWrapper.class); when(mockMouse.supportsSource(InputDevice.SOURCE_MOUSE)).thenReturn(true); when(mockMouse.isEnabled()).thenReturn(true); when(mockMouse.isVirtual()).thenReturn(false); when(mMockInputManagerWrapper.getInputDevice(1)).thenReturn(mockMouse); // Initialize controller and panels. injectFakeMouseActionHoverMoveEvent(); // Capture the listener. ArgumentCaptor<InputManager.InputDeviceListener> listenerCaptor = ArgumentCaptor.forClass(InputManager.InputDeviceListener.class); verify(mMockInputManagerWrapper) .registerInputDeviceListener(listenerCaptor.capture(), any()); InputManager.InputDeviceListener listener = listenerCaptor.getValue(); // Manually trigger once to establish initial connected state. listener.onInputDeviceChanged(1); mTestableLooper.processAllMessages(); // Mock panels to verify interactions. AutoclickTypePanel mockTypePanel = mock(AutoclickTypePanel.class); AutoclickScrollPanel mockScrollPanel = mock(AutoclickScrollPanel.class); mController.mAutoclickTypePanel = mockTypePanel; mController.mAutoclickScrollPanel = mockScrollPanel; // Action: trigger change, but connection state is the same (connected). listener.onInputDeviceChanged(1); mTestableLooper.processAllMessages(); // Verify panels state is unchanged. verify(mockTypePanel, Mockito.never()).hide(); verify(mockScrollPanel, Mockito.never()).hide(); verify(mockTypePanel, Mockito.never()).show(); // Action: disconnect mouse. when(mMockInputManagerWrapper.getInputDeviceIds()).thenReturn(new int[0]); listener.onInputDeviceChanged(1); mTestableLooper.processAllMessages(); // Verify hide was called once. verify(mockTypePanel, times(1)).hide(); verify(mockScrollPanel, times(1)).hide(); // Action: trigger change, but connection state is the same (disconnected). listener.onInputDeviceChanged(1); mTestableLooper.processAllMessages(); // Verify panels state is unchanged (hide not called again). verify(mockTypePanel, times(1)).hide(); verify(mockScrollPanel, times(1)).hide(); verify(mockTypePanel, Mockito.never()).show(); } /** * ========================================================================= * Helper Functions Loading
services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java +4 −0 Original line number Diff line number Diff line Loading @@ -120,6 +120,10 @@ public class AutoclickTypePanelTest { mPauseButton = contentView.findViewById(R.id.accessibility_autoclick_pause_layout); mPositionButton = contentView.findViewById(R.id.accessibility_autoclick_position_layout); mLongPressButton = contentView.findViewById(R.id.accessibility_autoclick_long_press_layout); // Set panel to default bottom-right corner. mAutoclickTypePanel.show(); mAutoclickTypePanel.resetPanelPositionForTesting(); } @Test Loading