diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md index 47e1dad9ccd65..4d41dcd3712d4 100644 --- a/cmds/uinput/README.md +++ b/cmds/uinput/README.md @@ -153,6 +153,38 @@ Example: } ``` +4. `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 1. As soon as EOF is reached (either in interactive mode, or in file mode), the device that was created will be unregistered. There is no diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java index 732b33d60c15d..2f362d7fabab8 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Device.java +++ b/cmds/uinput/src/com/android/commands/uinput/Device.java @@ -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; @@ -118,6 +119,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. * @@ -171,6 +182,9 @@ public class Device { mCond.notify(); } break; + case MSG_SYNC_EVENT: + handleSyncEvent((String) msg.obj); + break; default: throw new IllegalArgumentException("Unknown device message"); } @@ -184,6 +198,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 { @@ -211,7 +237,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); @@ -219,12 +245,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() { @@ -234,4 +255,13 @@ public class Device { msg.sendToTarget(); } } + + private void writeOutputObject(JSONObject json) { + try { + mOutputStream.write(json.toString().getBytes()); + mOutputStream.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java index a59c7b9befd86..91b4a7418c424 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Event.java +++ b/cmds/uinput/src/com/android/commands/uinput/Event.java @@ -42,7 +42,8 @@ public class Event { enum Command { REGISTER("register"), DELAY("delay"), - INJECT("inject"); + INJECT("inject"), + SYNC("sync"); final String mCommandName; @@ -77,6 +78,7 @@ public class Event { private int mFfEffectsMax = 0; private String mInputport; private SparseArray mAbsInfo; + private String mSyncToken; public int getId() { return mId; @@ -126,6 +128,10 @@ public class Event { return mInputport; } + public String getSyncToken() { + return mSyncToken; + } + /** * Convert an event to String. */ @@ -206,6 +212,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"); @@ -225,10 +235,15 @@ public class Event { } } case INJECT -> { - if (mEvent.mInjections == null) { + if (mEvent.mInjections == null) { 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; } @@ -295,6 +310,9 @@ public class Event { case "port": eb.setInputport(mReader.nextString()); break; + case "syncToken": + eb.setSyncToken(mReader.nextString()); + break; default: mReader.skipValue(); } diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java index 16755c2ea9f73..47b7a354f3304 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java +++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java @@ -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()); } }