From 4bbabfb4ef5f1105215dc2bb8a282ffce221bbcc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 25 May 2020 02:32:37 +0200 Subject: [PATCH 1/3] Move injection methods to Device Only the main injection method was exposed on Device, the convenience methods were implemented in Controller. For consistency, move them all to the Device class. --- .../com/genymobile/scrcpy/Controller.java | 30 ++++--------------- .../java/com/genymobile/scrcpy/Device.java | 21 ++++++++++++- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 4442188ccd..ab7b6c4090 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -1,10 +1,7 @@ package com.genymobile.scrcpy; -import com.genymobile.scrcpy.wrappers.InputManager; - import android.os.SystemClock; import android.view.InputDevice; -import android.view.InputEvent; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; @@ -50,7 +47,7 @@ private void initPointers() { public void control() throws IOException { // on start, power on the device if (!device.isScreenOn()) { - injectKeycode(KeyEvent.KEYCODE_POWER); + device.injectKeycode(KeyEvent.KEYCODE_POWER); // dirty hack // After POWER is injected, the device is powered on asynchronously. @@ -133,7 +130,7 @@ private void handleEvent() throws IOException { } private boolean injectKeycode(int action, int keycode, int metaState) { - return injectKeyEvent(action, keycode, 0, metaState); + return device.injectKeyEvent(action, keycode, 0, metaState); } private boolean injectChar(char c) { @@ -144,7 +141,7 @@ private boolean injectChar(char c) { return false; } for (KeyEvent event : events) { - if (!injectEvent(event)) { + if (!device.injectEvent(event)) { return false; } } @@ -200,7 +197,7 @@ private boolean injectTouch(int action, long pointerId, Position position, float MotionEvent event = MotionEvent .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); - return injectEvent(event); + return device.injectEvent(event); } private boolean injectScroll(Position position, int hScroll, int vScroll) { @@ -223,26 +220,11 @@ private boolean injectScroll(Position position, int hScroll, int vScroll) { MotionEvent event = MotionEvent .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); - return injectEvent(event); - } - - private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) { - long now = SystemClock.uptimeMillis(); - KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, - InputDevice.SOURCE_KEYBOARD); - return injectEvent(event); - } - - private boolean injectKeycode(int keyCode) { - return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0); - } - - private boolean injectEvent(InputEvent event) { - return device.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + return device.injectEvent(event); } private boolean pressBackOrTurnScreenOn() { int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER; - return injectKeycode(keycode); + return device.injectKeycode(keycode); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index ec28bd7cb4..349486c356 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -10,8 +10,12 @@ import android.graphics.Rect; import android.os.Build; import android.os.IBinder; +import android.os.SystemClock; import android.view.IRotationWatcher; +import android.view.InputDevice; import android.view.InputEvent; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; import java.util.concurrent.atomic.AtomicBoolean; @@ -147,7 +151,7 @@ public boolean supportsInputEvents() { return supportsInputEvents; } - public boolean injectInputEvent(InputEvent inputEvent, int mode) { + public boolean injectEvent(InputEvent inputEvent, int mode) { if (!supportsInputEvents()) { throw new AssertionError("Could not inject input event if !supportsInputEvents()"); } @@ -159,6 +163,21 @@ public boolean injectInputEvent(InputEvent inputEvent, int mode) { return serviceManager.getInputManager().injectInputEvent(inputEvent, mode); } + public boolean injectEvent(InputEvent event) { + return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + } + + public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) { + long now = SystemClock.uptimeMillis(); + KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, + InputDevice.SOURCE_KEYBOARD); + return injectEvent(event); + } + + public boolean injectKeycode(int keyCode) { + return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0); + } + public boolean isScreenOn() { return serviceManager.getPowerManager().isScreenOn(); } From cc4e1e20de199fe7f73ef8e69f08aa4fd30568d4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 25 May 2020 03:13:18 +0200 Subject: [PATCH 2/3] Add more convenience methods for injection Expose methods to inject key events and key codes with an additional parameter to specify the "mode" (async, wait for finish, wait for result). --- .../main/java/com/genymobile/scrcpy/Device.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 349486c356..3a98ebd26d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -167,15 +167,23 @@ public boolean injectEvent(InputEvent event) { return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } - public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) { + public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int mode) { long now = SystemClock.uptimeMillis(); KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD); - return injectEvent(event); + return injectEvent(event, mode); + } + + public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) { + return injectKeyEvent(action, keyCode, repeat, metaState, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + } + + public boolean injectKeycode(int keyCode, int mode) { + return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, mode); } public boolean injectKeycode(int keyCode) { - return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0); + return injectKeycode(keyCode, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } public boolean isScreenOn() { From d613b10efcdf0d1cf76e30871e136ba0ff444e6e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 25 May 2020 02:29:23 +0200 Subject: [PATCH 3/3] Add a new method for text injection On Android >= 7, inject text using the clipboard. This is faster and allows to inject UTF-8. --- .../com/genymobile/scrcpy/Controller.java | 4 +++ .../java/com/genymobile/scrcpy/Device.java | 27 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index ab7b6c4090..8ea42c0794 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -149,6 +149,10 @@ private boolean injectChar(char c) { } private int injectText(String text) { + if (device.injectTextPaste(text)) { + // The best method (fastest and UTF-8) worked! + return text.length(); + } int successCount = 0; for (char c : text.toCharArray()) { if (!injectChar(c)) { diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 3a98ebd26d..a5f7bfe7b3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -186,6 +186,27 @@ public boolean injectKeycode(int keyCode) { return injectKeycode(keyCode, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } + public boolean injectTextPaste(String text) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + return false; + } + + // On Android >= 7, we can inject UTF-8 text as follow: + // - set the clipboard + // - inject the PASTE key event + // - restore the clipboard + + String clipboardBackup = getClipboardText(); + isSettingClipboard.set(true); + + rawSetClipboardText(text); + boolean ok = injectKeycode(KeyEvent.KEYCODE_PASTE, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT); + rawSetClipboardText(clipboardBackup); + + isSettingClipboard.set(false); + return ok; + } + public boolean isScreenOn() { return serviceManager.getPowerManager().isScreenOn(); } @@ -214,9 +235,13 @@ public String getClipboardText() { return s.toString(); } + private boolean rawSetClipboardText(String text) { + return serviceManager.getClipboardManager().setText(text); + } + public boolean setClipboardText(String text) { isSettingClipboard.set(true); - boolean ok = serviceManager.getClipboardManager().setText(text); + boolean ok = rawSetClipboardText(text); isSettingClipboard.set(false); return ok; }