From 917fff9d16bcf2eb95edab0683b94e8986333e52 Mon Sep 17 00:00:00 2001 From: nergi Date: Tue, 30 Apr 2024 18:46:48 +0900 Subject: Adds duration args to allow arbitrary duration of keyevent press This will be helpful to debug longer duration key press. This has also been one of the requested features asked in stackoverflows (some people tried to seed events directly to evdev as workaround). New commands: keyevent --duration This will be mutually exclusive with --longpress, meaning that only one of them can be passed. This 'duration_ms' will also be applied when combined with other args ('--doubletap', '--delay'). Bug: 335533324 Test: atest InputShellCommandTest Test: adb shell input keyevent --duration 1000 Change-Id: I3a86f5c4b89b435bc984b09ba6f504a59b5e0918 --- .../android/server/input/InputShellCommand.java | 48 ++++++++++++++++------ .../server/input/InputShellCommandTest.java | 8 ++++ 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/services/core/java/com/android/server/input/InputShellCommand.java b/services/core/java/com/android/server/input/InputShellCommand.java index 138186ba6191..4c5a3c27e156 100644 --- a/services/core/java/com/android/server/input/InputShellCommand.java +++ b/services/core/java/com/android/server/input/InputShellCommand.java @@ -333,8 +333,8 @@ public class InputShellCommand extends ShellCommand { out.println(); out.println("The commands and default sources are:"); out.println(" text (Default: keyboard)"); - out.println(" keyevent [--longpress|--doubletap|--async" - + "|--delay ]" + out.println(" keyevent [--longpress|--duration ]" + + " [--doubletap] [--async] [--delay ]" + " ..." + " (Default: keyboard)"); out.println(" tap (Default: touchscreen)"); @@ -402,6 +402,7 @@ public class InputShellCommand extends ShellCommand { boolean async = false; boolean doubleTap = false; long delayMs = 0; + long durationMs = 0; String arg = getNextArgRequired(); do { @@ -411,9 +412,21 @@ public class InputShellCommand extends ShellCommand { doubleTap = (doubleTap || arg.equals("--doubletap")); if (arg.equals("--delay")) { delayMs = Long.parseLong(getNextArgRequired()); + } else if (arg.equals("--duration")) { + durationMs = Long.parseLong(getNextArgRequired()); } } while ((arg = getNextArg()) != null); + if (durationMs > 0 && longPress) { + getErrPrintWriter().println( + "--duration and --longpress cannot be used at the same time."); + throw new IllegalArgumentException( + "keyevent args should only contain either durationMs or longPress"); + } + if (longPress) { + durationMs = ViewConfiguration.getLongPressTimeout(); + } + boolean firstInput = true; do { if (!firstInput && delayMs > 0) { @@ -422,16 +435,17 @@ public class InputShellCommand extends ShellCommand { firstInput = false; final int keyCode = KeyEvent.keyCodeFromString(arg); - sendKeyEvent(inputSource, keyCode, longPress, displayId, async); + sendKeyEvent(inputSource, keyCode, durationMs, displayId, async); if (doubleTap) { sleep(ViewConfiguration.getDoubleTapMinTime()); - sendKeyEvent(inputSource, keyCode, longPress, displayId, async); + sendKeyEvent(inputSource, keyCode, durationMs, displayId, async); } } while ((arg = getNextArg()) != null); } private void sendKeyEvent( - int inputSource, int keyCode, boolean longPress, int displayId, boolean async) { + int inputSource, int keyCode, long durationMs, int displayId, + boolean async) { final long now = SystemClock.uptimeMillis(); KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0 /* repeatCount */, @@ -440,13 +454,23 @@ public class InputShellCommand extends ShellCommand { event.setDisplayId(displayId); injectKeyEvent(event, async); - if (longPress) { - sleep(ViewConfiguration.getLongPressTimeout()); - // Some long press behavior would check the event time, we set a new event time here. - final long nextEventTime = now + ViewConfiguration.getLongPressTimeout(); - KeyEvent longPressEvent = KeyEvent.changeTimeRepeat( - event, nextEventTime, 1 /* repeatCount */, KeyEvent.FLAG_LONG_PRESS); - injectKeyEvent(longPressEvent, async); + long firstSleepDurationMs = Math.min(durationMs, ViewConfiguration.getLongPressTimeout()); + if (firstSleepDurationMs > 0) { + sleep(firstSleepDurationMs); + // Send FLAG_LONG_PRESS right after `longPressTimeout`, and resume sleep if needed. + if (durationMs >= ViewConfiguration.getLongPressTimeout()) { + // Some long press behavior would check the event time, we set a new event time + // here. + final long nextEventTime = now + ViewConfiguration.getLongPressTimeout(); + KeyEvent longPressEvent = KeyEvent.changeTimeRepeat(event, nextEventTime, + 1 /* repeatCount */, KeyEvent.FLAG_LONG_PRESS); + injectKeyEvent(longPressEvent, async); + + long secondSleepDurationMs = durationMs - firstSleepDurationMs; + if (secondSleepDurationMs > 0) { + sleep(secondSleepDurationMs); + } + } } injectKeyEvent(KeyEvent.changeAction(event, KeyEvent.ACTION_UP), async); } diff --git a/tests/Input/src/com/android/server/input/InputShellCommandTest.java b/tests/Input/src/com/android/server/input/InputShellCommandTest.java index f4845a518b20..11f46335f017 100644 --- a/tests/Input/src/com/android/server/input/InputShellCommandTest.java +++ b/tests/Input/src/com/android/server/input/InputShellCommandTest.java @@ -125,6 +125,14 @@ public class InputShellCommandTest { assertThat(mInputEventInjector.mInjectedEvents).isEmpty(); } + @Test + public void testInvalidKeyEventCommandArgsCombination() { + // --duration and --longpress must not be sent together + runCommand("keyevent --duration 1000 --longpress KEYCODE_A"); + + assertThat(mInputEventInjector.mInjectedEvents).isEmpty(); + } + private InputEvent getSingleInjectedInputEvent() { assertThat(mInputEventInjector.mInjectedEvents).hasSize(1); return mInputEventInjector.mInjectedEvents.get(0); -- cgit v1.2.3-59-g8ed1b