From fb89d5688958209f13135b947fbfda392a790f0e Mon Sep 17 00:00:00 2001 From: arthurhung Date: Mon, 25 Jan 2021 20:17:21 +0800 Subject: [PATCH] Add keycombination and double tap key commands This will provide a convenient way to test combination key and single key gesture without connecting a physical keyboard. New commands: 'keycombination' : press multiple keys at same time. 'keyevent --doubletap' : double click with the same key. Also fix the command 'keyevent --longpress' to let it waiting until long press timeout instead passing the FLAG_LONG_PRESS flag, it's due to some behaviors may check the event time. Test: adb shell input keycombination keycode1 keycode2 Test: adb shell input keyevent --doubletap keycode Test: adb shell input keyevent --longpress keycode Bug: 162194917 Bug: 162194389 Change-Id: Ibe4d941ebe53cc5738db3ba9b59dffeeb31a7308 --- .../server/input/InputShellCommand.java | 88 +++++++++++++++++-- 1 file changed, 83 insertions(+), 5 deletions(-) diff --git a/services/core/java/com/android/server/input/InputShellCommand.java b/services/core/java/com/android/server/input/InputShellCommand.java index d08980cfbf24d..720be826fd387 100644 --- a/services/core/java/com/android/server/input/InputShellCommand.java +++ b/services/core/java/com/android/server/input/InputShellCommand.java @@ -29,6 +29,7 @@ import android.view.MotionEvent; import android.view.ViewConfiguration; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -199,6 +200,8 @@ public class InputShellCommand extends ShellCommand { runRoll(inputSource, displayId); } else if ("motionevent".equals(arg)) { runMotionEvent(inputSource, displayId); + } else if ("keycombination".equals(arg)) { + runKeyCombination(inputSource, displayId); } else { handleDefaultCommands(arg); } @@ -224,7 +227,7 @@ public class InputShellCommand extends ShellCommand { out.println(); out.println("The commands and default sources are:"); out.println(" text (Default: touchscreen)"); - out.println(" keyevent [--longpress] ..." + out.println(" keyevent [--longpress|--doubletap] ..." + " (Default: keyboard)"); out.println(" tap (Default: touchscreen)"); out.println(" swipe [duration(ms)]" @@ -234,6 +237,8 @@ public class InputShellCommand extends ShellCommand { out.println(" press (Default: trackball)"); out.println(" roll (Default: trackball)"); out.println(" motionevent (Default: touchscreen)"); + out.println(" keycombination ..." + + " (Default: keyboard)"); } } @@ -282,6 +287,14 @@ public class InputShellCommand extends ShellCommand { final boolean longpress = "--longpress".equals(arg); if (longpress) { arg = getNextArgRequired(); + } else { + final boolean doubleTap = "--doubletap".equals(arg); + if (doubleTap) { + arg = getNextArgRequired(); + final int keycode = KeyEvent.keyCodeFromString(arg); + sendKeyDoubleTap(inputSource, keycode, displayId); + return; + } } do { @@ -292,22 +305,32 @@ public class InputShellCommand extends ShellCommand { private void sendKeyEvent(int inputSource, int keyCode, boolean longpress, int displayId) { final long now = SystemClock.uptimeMillis(); - int repeatCount = 0; - KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, repeatCount, + KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0 /* repeatCount */, 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/, inputSource); event.setDisplayId(displayId); injectKeyEvent(event); if (longpress) { - repeatCount++; - injectKeyEvent(KeyEvent.changeTimeRepeat(event, now, repeatCount, + // Some long press behavior would check the event time, we set a new event time here. + final long nextEventTime = now + ViewConfiguration.getGlobalActionKeyTimeout(); + injectKeyEvent(KeyEvent.changeTimeRepeat(event, nextEventTime, 1 /* repeatCount */, KeyEvent.FLAG_LONG_PRESS)); } injectKeyEvent(KeyEvent.changeAction(event, KeyEvent.ACTION_UP)); } + private void sendKeyDoubleTap(int inputSource, int keyCode, int displayId) { + sendKeyEvent(inputSource, keyCode, false, displayId); + try { + Thread.sleep(ViewConfiguration.getDoubleTapMinTime()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + sendKeyEvent(inputSource, keyCode, false, displayId); + } + private void runTap(int inputSource, int displayId) { inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN); sendTap(inputSource, Float.parseFloat(getNextArgRequired()), @@ -440,4 +463,59 @@ public class InputShellCommand extends ShellCommand { final long now = SystemClock.uptimeMillis(); injectMotionEvent(inputSource, action, now, now, x, y, pressure, displayId); } + + private void runKeyCombination(int inputSource, int displayId) { + String arg = getNextArgRequired(); + ArrayList keyCodes = new ArrayList<>(); + + while (arg != null) { + final int keyCode = KeyEvent.keyCodeFromString(arg); + if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { + throw new IllegalArgumentException("Unknown keycode: " + arg); + } + keyCodes.add(keyCode); + arg = getNextArg(); + } + + // At least 2 keys. + if (keyCodes.size() < 2) { + throw new IllegalArgumentException("keycombination requires at least 2 keycodes"); + } + + sendKeyCombination(inputSource, keyCodes, displayId); + } + + private void injectKeyEventAsync(KeyEvent event) { + InputManager.getInstance().injectInputEvent(event, + InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + } + + private void sendKeyCombination(int inputSource, ArrayList keyCodes, int displayId) { + final long now = SystemClock.uptimeMillis(); + final int count = keyCodes.size(); + final KeyEvent[] events = new KeyEvent[count]; + for (int i = 0; i < count; i++) { + final KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCodes.get(i), 0, + 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/, + inputSource); + event.setDisplayId(displayId); + events[i] = event; + } + + for (KeyEvent event: events) { + // Use async inject so interceptKeyBeforeQueueing or interceptKeyBeforeDispatching could + // handle keys. + injectKeyEventAsync(event); + } + + try { + Thread.sleep(ViewConfiguration.getTapTimeout()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + for (KeyEvent event: events) { + injectKeyEventAsync(KeyEvent.changeAction(event, KeyEvent.ACTION_UP)); + } + } }