Loading cmds/uinput/README.md +35 −1 Original line number Diff line number Diff line Loading @@ -128,7 +128,9 @@ stack, and `uinput` does not wait for that process to finish. Any commands sent that time will be dropped. If you are controlling `uinput` by sending commands through standard input from an app, you need to wait for [`onInputDeviceAdded`][onInputDeviceAdded] to be called on an `InputDeviceListener` before issuing commands to the device. If you are passing a file to `uinput`, add a `delay` after the `register` command to let registration complete. `uinput`, add a `delay` after the `register` command to let registration complete. You can add a `sync` in certain positions, like at the end of the file to get a response when all commands have finished processing. [onInputDeviceAdded]: https://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener.html Loading Loading @@ -187,6 +189,38 @@ keys would look like this: } ``` ### `sync` A command used to get a response once the command is processed. When several `inject` and `delay` commands are used in a row, the `sync` command can be used to track the progress of the command queue. | Field | Type | Description | |:-----------:|:-------:|:---------------------------------------------| | `id` | integer | Device ID | | `command` | string | Must be set to "sync" | | `syncToken` | string | The token used to identify this sync command | Example: ```json5 { "id": 1, "command": "syncToken", "syncToken": "finished_injecting_events" } ``` This command will result in the following response when it is processed: ```json5 { "id": 1, "result": "sync", "syncToken": "finished_injecting_events" } ``` ## Notes The `getevent` utility can used to print out the key events for debugging purposes. cmds/uinput/src/com/android/commands/uinput/Device.java +37 −7 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ public class Device { private static final int MSG_OPEN_UINPUT_DEVICE = 1; private static final int MSG_CLOSE_UINPUT_DEVICE = 2; private static final int MSG_INJECT_EVENT = 3; private static final int MSG_SYNC_EVENT = 4; private final int mId; private final HandlerThread mThread; Loading Loading @@ -121,6 +122,16 @@ public class Device { mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay; } /** * Synchronize the uinput command queue by writing a sync response with the provided syncToken * to the output stream when this event is processed. * * @param syncToken The token for this sync command. */ public void syncEvent(String syncToken) { mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), mTimeToSend); } /** * Close an uinput device. * Loading Loading @@ -174,6 +185,9 @@ public class Device { mCond.notify(); } break; case MSG_SYNC_EVENT: handleSyncEvent((String) msg.obj); break; default: throw new IllegalArgumentException("Unknown device message"); } Loading @@ -187,6 +201,18 @@ public class Device { getLooper().myQueue().removeSyncBarrier(mBarrierToken); mBarrierToken = 0; } private void handleSyncEvent(String syncToken) { final JSONObject json = new JSONObject(); try { json.put("reason", "sync"); json.put("id", mId); json.put("syncToken", syncToken); } catch (JSONException e) { throw new RuntimeException("Could not create JSON object ", e); } writeOutputObject(json); } } private class DeviceCallback { Loading Loading @@ -214,7 +240,7 @@ public class Device { } public void onDeviceVibrating(int value) { JSONObject json = new JSONObject(); final JSONObject json = new JSONObject(); try { json.put("reason", "vibrating"); json.put("id", mId); Loading @@ -222,12 +248,7 @@ public class Device { } catch (JSONException e) { throw new RuntimeException("Could not create JSON object ", e); } try { mOutputStream.write(json.toString().getBytes()); mOutputStream.flush(); } catch (IOException e) { throw new RuntimeException(e); } writeOutputObject(json); } public void onDeviceError() { Loading @@ -238,6 +259,15 @@ public class Device { } } private void writeOutputObject(JSONObject json) { try { mOutputStream.write(json.toString().getBytes()); mOutputStream.flush(); } catch (IOException e) { throw new RuntimeException(e); } } static int getEvdevEventTypeByLabel(String label) { final var type = nativeGetEvdevEventTypeByLabel(label); if (type < 0) { Loading cmds/uinput/src/com/android/commands/uinput/Event.java +20 −2 Original line number Diff line number Diff line Loading @@ -42,7 +42,8 @@ public class Event { enum Command { REGISTER("register"), DELAY("delay"), INJECT("inject"); INJECT("inject"), SYNC("sync"); final String mCommandName; Loading Loading @@ -107,6 +108,7 @@ public class Event { private int mFfEffectsMax = 0; private String mInputport; private SparseArray<InputAbsInfo> mAbsInfo; private String mSyncToken; public int getId() { return mId; Loading Loading @@ -156,6 +158,10 @@ public class Event { return mInputport; } public String getSyncToken() { return mSyncToken; } /** * Convert an event to String. */ Loading Loading @@ -236,6 +242,10 @@ public class Event { mEvent.mInputport = port; } public void setSyncToken(String syncToken) { mEvent.mSyncToken = Objects.requireNonNull(syncToken, "Sync token must not be null"); } public Event build() { if (mEvent.mId == -1) { throw new IllegalStateException("No event id"); Loading @@ -259,6 +269,11 @@ public class Event { throw new IllegalStateException("Inject command is missing injection data"); } } case SYNC -> { if (mEvent.mSyncToken == null) { throw new IllegalStateException("Sync command is missing sync token"); } } } return mEvent; } Loading Loading @@ -325,6 +340,9 @@ public class Event { case "port": eb.setInputport(mReader.nextString()); break; case "syncToken": eb.setSyncToken(mReader.nextString()); break; default: mReader.skipValue(); } Loading cmds/uinput/src/com/android/commands/uinput/Uinput.java +1 −0 Original line number Diff line number Diff line Loading @@ -112,6 +112,7 @@ public class Uinput { error("Device id=" + e.getId() + " is already registered. Ignoring event."); case INJECT -> d.injectEvent(e.getInjections()); case DELAY -> d.addDelay(e.getDuration()); case SYNC -> d.syncEvent(e.getSyncToken()); } } Loading Loading
cmds/uinput/README.md +35 −1 Original line number Diff line number Diff line Loading @@ -128,7 +128,9 @@ stack, and `uinput` does not wait for that process to finish. Any commands sent that time will be dropped. If you are controlling `uinput` by sending commands through standard input from an app, you need to wait for [`onInputDeviceAdded`][onInputDeviceAdded] to be called on an `InputDeviceListener` before issuing commands to the device. If you are passing a file to `uinput`, add a `delay` after the `register` command to let registration complete. `uinput`, add a `delay` after the `register` command to let registration complete. You can add a `sync` in certain positions, like at the end of the file to get a response when all commands have finished processing. [onInputDeviceAdded]: https://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener.html Loading Loading @@ -187,6 +189,38 @@ keys would look like this: } ``` ### `sync` A command used to get a response once the command is processed. When several `inject` and `delay` commands are used in a row, the `sync` command can be used to track the progress of the command queue. | Field | Type | Description | |:-----------:|:-------:|:---------------------------------------------| | `id` | integer | Device ID | | `command` | string | Must be set to "sync" | | `syncToken` | string | The token used to identify this sync command | Example: ```json5 { "id": 1, "command": "syncToken", "syncToken": "finished_injecting_events" } ``` This command will result in the following response when it is processed: ```json5 { "id": 1, "result": "sync", "syncToken": "finished_injecting_events" } ``` ## Notes The `getevent` utility can used to print out the key events for debugging purposes.
cmds/uinput/src/com/android/commands/uinput/Device.java +37 −7 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ public class Device { private static final int MSG_OPEN_UINPUT_DEVICE = 1; private static final int MSG_CLOSE_UINPUT_DEVICE = 2; private static final int MSG_INJECT_EVENT = 3; private static final int MSG_SYNC_EVENT = 4; private final int mId; private final HandlerThread mThread; Loading Loading @@ -121,6 +122,16 @@ public class Device { mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay; } /** * Synchronize the uinput command queue by writing a sync response with the provided syncToken * to the output stream when this event is processed. * * @param syncToken The token for this sync command. */ public void syncEvent(String syncToken) { mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), mTimeToSend); } /** * Close an uinput device. * Loading Loading @@ -174,6 +185,9 @@ public class Device { mCond.notify(); } break; case MSG_SYNC_EVENT: handleSyncEvent((String) msg.obj); break; default: throw new IllegalArgumentException("Unknown device message"); } Loading @@ -187,6 +201,18 @@ public class Device { getLooper().myQueue().removeSyncBarrier(mBarrierToken); mBarrierToken = 0; } private void handleSyncEvent(String syncToken) { final JSONObject json = new JSONObject(); try { json.put("reason", "sync"); json.put("id", mId); json.put("syncToken", syncToken); } catch (JSONException e) { throw new RuntimeException("Could not create JSON object ", e); } writeOutputObject(json); } } private class DeviceCallback { Loading Loading @@ -214,7 +240,7 @@ public class Device { } public void onDeviceVibrating(int value) { JSONObject json = new JSONObject(); final JSONObject json = new JSONObject(); try { json.put("reason", "vibrating"); json.put("id", mId); Loading @@ -222,12 +248,7 @@ public class Device { } catch (JSONException e) { throw new RuntimeException("Could not create JSON object ", e); } try { mOutputStream.write(json.toString().getBytes()); mOutputStream.flush(); } catch (IOException e) { throw new RuntimeException(e); } writeOutputObject(json); } public void onDeviceError() { Loading @@ -238,6 +259,15 @@ public class Device { } } private void writeOutputObject(JSONObject json) { try { mOutputStream.write(json.toString().getBytes()); mOutputStream.flush(); } catch (IOException e) { throw new RuntimeException(e); } } static int getEvdevEventTypeByLabel(String label) { final var type = nativeGetEvdevEventTypeByLabel(label); if (type < 0) { Loading
cmds/uinput/src/com/android/commands/uinput/Event.java +20 −2 Original line number Diff line number Diff line Loading @@ -42,7 +42,8 @@ public class Event { enum Command { REGISTER("register"), DELAY("delay"), INJECT("inject"); INJECT("inject"), SYNC("sync"); final String mCommandName; Loading Loading @@ -107,6 +108,7 @@ public class Event { private int mFfEffectsMax = 0; private String mInputport; private SparseArray<InputAbsInfo> mAbsInfo; private String mSyncToken; public int getId() { return mId; Loading Loading @@ -156,6 +158,10 @@ public class Event { return mInputport; } public String getSyncToken() { return mSyncToken; } /** * Convert an event to String. */ Loading Loading @@ -236,6 +242,10 @@ public class Event { mEvent.mInputport = port; } public void setSyncToken(String syncToken) { mEvent.mSyncToken = Objects.requireNonNull(syncToken, "Sync token must not be null"); } public Event build() { if (mEvent.mId == -1) { throw new IllegalStateException("No event id"); Loading @@ -259,6 +269,11 @@ public class Event { throw new IllegalStateException("Inject command is missing injection data"); } } case SYNC -> { if (mEvent.mSyncToken == null) { throw new IllegalStateException("Sync command is missing sync token"); } } } return mEvent; } Loading Loading @@ -325,6 +340,9 @@ public class Event { case "port": eb.setInputport(mReader.nextString()); break; case "syncToken": eb.setSyncToken(mReader.nextString()); break; default: mReader.skipValue(); } Loading
cmds/uinput/src/com/android/commands/uinput/Uinput.java +1 −0 Original line number Diff line number Diff line Loading @@ -112,6 +112,7 @@ public class Uinput { error("Device id=" + e.getId() + " is already registered. Ignoring event."); case INJECT -> d.injectEvent(e.getInjections()); case DELAY -> d.addDelay(e.getDuration()); case SYNC -> d.syncEvent(e.getSyncToken()); } } Loading