Loading cmds/uinput/README.md +32 −0 Original line number Diff line number Diff line Loading @@ -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 Loading 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 @@ -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. * Loading Loading @@ -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"); } Loading @@ -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 { Loading Loading @@ -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); Loading @@ -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() { Loading @@ -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); } } } cmds/uinput/src/com/android/commands/uinput/Event.java +55 −18 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; import java.util.stream.IntStream; import src.com.android.commands.uinput.InputAbsInfo; Loading @@ -36,11 +37,21 @@ import src.com.android.commands.uinput.InputAbsInfo; public class Event { private static final String TAG = "UinputEvent"; public static final String COMMAND_REGISTER = "register"; public static final String COMMAND_DELAY = "delay"; public static final String COMMAND_INJECT = "inject"; private static final int ABS_CNT = 64; enum Command { REGISTER("register"), DELAY("delay"), INJECT("inject"), SYNC("sync"); final String mCommandName; Command(String command) { mCommandName = command; } } // These constants come from "include/uapi/linux/input.h" in the kernel enum Bus { USB(0x03), BLUETOOTH(0x05); Loading @@ -56,7 +67,7 @@ public class Event { } private int mId; private String mCommand; private Command mCommand; private String mName; private int mVid; private int mPid; Loading @@ -67,12 +78,13 @@ public class Event { private int mFfEffectsMax = 0; private String mInputport; private SparseArray<InputAbsInfo> mAbsInfo; private String mSyncToken; public int getId() { return mId; } public String getCommand() { public Command getCommand() { return mCommand; } Loading Loading @@ -116,6 +128,10 @@ public class Event { return mInputport; } public String getSyncToken() { return mSyncToken; } /** * Convert an event to String. */ Loading Loading @@ -146,7 +162,14 @@ public class Event { } private void setCommand(String command) { mEvent.mCommand = command; Objects.requireNonNull(command, "Command must not be null"); for (Command cmd : Command.values()) { if (cmd.mCommandName.equals(command)) { mEvent.mCommand = cmd; return; } } throw new IllegalStateException("Unrecognized command: " + command); } public void setName(String name) { Loading Loading @@ -189,27 +212,38 @@ 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"); } else if (mEvent.mCommand == null) { throw new IllegalStateException("Event does not contain a command"); } if (COMMAND_REGISTER.equals(mEvent.mCommand)) { switch (mEvent.mCommand) { case REGISTER -> { if (mEvent.mConfiguration == null) { throw new IllegalStateException( "Device registration is missing configuration"); } } else if (COMMAND_DELAY.equals(mEvent.mCommand)) { } case DELAY -> { if (mEvent.mDuration <= 0) { throw new IllegalStateException("Delay has missing or invalid duration"); } } else if (COMMAND_INJECT.equals(mEvent.mCommand)) { } case INJECT -> { if (mEvent.mInjections == null) { throw new IllegalStateException("Inject command is missing injection data"); } } else { throw new IllegalStateException("Unknown command " + mEvent.mCommand); } case SYNC -> { if (mEvent.mSyncToken == null) { throw new IllegalStateException("Sync command is missing sync token"); } } } return mEvent; } Loading Loading @@ -276,6 +310,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 +16 −16 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.Objects; /** * Uinput class encapsulates execution of "uinput" command. It parses the provided input stream Loading Loading @@ -96,28 +97,27 @@ public class Uinput { private void process(Event e) { final int index = mDevices.indexOfKey(e.getId()); if (index >= 0) { Device d = mDevices.valueAt(index); if (Event.COMMAND_DELAY.equals(e.getCommand())) { d.addDelay(e.getDuration()); } else if (Event.COMMAND_INJECT.equals(e.getCommand())) { d.injectEvent(e.getInjections()); } else { if (Event.COMMAND_REGISTER.equals(e.getCommand())) { error("Device id=" + e.getId() + " is already registered. Ignoring event."); } else { error("Unknown command \"" + e.getCommand() + "\". Ignoring event."); } if (index < 0) { if (e.getCommand() != Event.Command.REGISTER) { Log.e(TAG, "Unknown device id specified. Ignoring event."); return; } } else if (Event.COMMAND_REGISTER.equals(e.getCommand())) { registerDevice(e); } else { Log.e(TAG, "Unknown device id specified. Ignoring event."); return; } final Device d = mDevices.valueAt(index); switch (Objects.requireNonNull(e.getCommand())) { case REGISTER -> 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()); } } private void registerDevice(Event e) { if (!Event.COMMAND_REGISTER.equals(e.getCommand())) { if (!Event.Command.REGISTER.equals(e.getCommand())) { throw new IllegalStateException( "Tried to send command \"" + e.getCommand() + "\" to an unregistered device!"); } Loading Loading
cmds/uinput/README.md +32 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
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 @@ -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. * Loading Loading @@ -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"); } Loading @@ -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 { Loading Loading @@ -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); Loading @@ -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() { Loading @@ -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); } } }
cmds/uinput/src/com/android/commands/uinput/Event.java +55 −18 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; import java.util.stream.IntStream; import src.com.android.commands.uinput.InputAbsInfo; Loading @@ -36,11 +37,21 @@ import src.com.android.commands.uinput.InputAbsInfo; public class Event { private static final String TAG = "UinputEvent"; public static final String COMMAND_REGISTER = "register"; public static final String COMMAND_DELAY = "delay"; public static final String COMMAND_INJECT = "inject"; private static final int ABS_CNT = 64; enum Command { REGISTER("register"), DELAY("delay"), INJECT("inject"), SYNC("sync"); final String mCommandName; Command(String command) { mCommandName = command; } } // These constants come from "include/uapi/linux/input.h" in the kernel enum Bus { USB(0x03), BLUETOOTH(0x05); Loading @@ -56,7 +67,7 @@ public class Event { } private int mId; private String mCommand; private Command mCommand; private String mName; private int mVid; private int mPid; Loading @@ -67,12 +78,13 @@ public class Event { private int mFfEffectsMax = 0; private String mInputport; private SparseArray<InputAbsInfo> mAbsInfo; private String mSyncToken; public int getId() { return mId; } public String getCommand() { public Command getCommand() { return mCommand; } Loading Loading @@ -116,6 +128,10 @@ public class Event { return mInputport; } public String getSyncToken() { return mSyncToken; } /** * Convert an event to String. */ Loading Loading @@ -146,7 +162,14 @@ public class Event { } private void setCommand(String command) { mEvent.mCommand = command; Objects.requireNonNull(command, "Command must not be null"); for (Command cmd : Command.values()) { if (cmd.mCommandName.equals(command)) { mEvent.mCommand = cmd; return; } } throw new IllegalStateException("Unrecognized command: " + command); } public void setName(String name) { Loading Loading @@ -189,27 +212,38 @@ 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"); } else if (mEvent.mCommand == null) { throw new IllegalStateException("Event does not contain a command"); } if (COMMAND_REGISTER.equals(mEvent.mCommand)) { switch (mEvent.mCommand) { case REGISTER -> { if (mEvent.mConfiguration == null) { throw new IllegalStateException( "Device registration is missing configuration"); } } else if (COMMAND_DELAY.equals(mEvent.mCommand)) { } case DELAY -> { if (mEvent.mDuration <= 0) { throw new IllegalStateException("Delay has missing or invalid duration"); } } else if (COMMAND_INJECT.equals(mEvent.mCommand)) { } case INJECT -> { if (mEvent.mInjections == null) { throw new IllegalStateException("Inject command is missing injection data"); } } else { throw new IllegalStateException("Unknown command " + mEvent.mCommand); } case SYNC -> { if (mEvent.mSyncToken == null) { throw new IllegalStateException("Sync command is missing sync token"); } } } return mEvent; } Loading Loading @@ -276,6 +310,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 +16 −16 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.Objects; /** * Uinput class encapsulates execution of "uinput" command. It parses the provided input stream Loading Loading @@ -96,28 +97,27 @@ public class Uinput { private void process(Event e) { final int index = mDevices.indexOfKey(e.getId()); if (index >= 0) { Device d = mDevices.valueAt(index); if (Event.COMMAND_DELAY.equals(e.getCommand())) { d.addDelay(e.getDuration()); } else if (Event.COMMAND_INJECT.equals(e.getCommand())) { d.injectEvent(e.getInjections()); } else { if (Event.COMMAND_REGISTER.equals(e.getCommand())) { error("Device id=" + e.getId() + " is already registered. Ignoring event."); } else { error("Unknown command \"" + e.getCommand() + "\". Ignoring event."); } if (index < 0) { if (e.getCommand() != Event.Command.REGISTER) { Log.e(TAG, "Unknown device id specified. Ignoring event."); return; } } else if (Event.COMMAND_REGISTER.equals(e.getCommand())) { registerDevice(e); } else { Log.e(TAG, "Unknown device id specified. Ignoring event."); return; } final Device d = mDevices.valueAt(index); switch (Objects.requireNonNull(e.getCommand())) { case REGISTER -> 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()); } } private void registerDevice(Event e) { if (!Event.COMMAND_REGISTER.equals(e.getCommand())) { if (!Event.Command.REGISTER.equals(e.getCommand())) { throw new IllegalStateException( "Tried to send command \"" + e.getCommand() + "\" to an unregistered device!"); } Loading