Merge changes from topic "stylus-replay-from-file" into udc-qpr-dev

* changes:
  Uinput: Add sync command to get a response when the command is processed
  Uinput: Use enums for commands
This commit is contained in:
Uwais Ashraf
2023-08-16 16:34:03 +00:00
committed by Android (Google) Code Review
4 changed files with 140 additions and 41 deletions

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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;
@@ -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);
@@ -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;
@@ -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;
}
@@ -116,6 +128,10 @@ public class Event {
return mInputport;
}
public String getSyncToken() {
return mSyncToken;
}
/**
* Convert an event to String.
*/
@@ -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) {
@@ -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)) {
if (mEvent.mConfiguration == null) {
throw new IllegalStateException(
"Device registration is missing configuration");
switch (mEvent.mCommand) {
case REGISTER -> {
if (mEvent.mConfiguration == null) {
throw new IllegalStateException(
"Device registration is missing configuration");
}
}
} else if (COMMAND_DELAY.equals(mEvent.mCommand)) {
if (mEvent.mDuration <= 0) {
throw new IllegalStateException("Delay has missing or invalid duration");
case DELAY -> {
if (mEvent.mDuration <= 0) {
throw new IllegalStateException("Delay has missing or invalid duration");
}
}
} else if (COMMAND_INJECT.equals(mEvent.mCommand)) {
if (mEvent.mInjections == null) {
throw new IllegalStateException("Inject command is missing injection data");
case INJECT -> {
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");
}
}
} else {
throw new IllegalStateException("Unknown command " + mEvent.mCommand);
}
return mEvent;
}
@@ -276,6 +310,9 @@ public class Event {
case "port":
eb.setInputport(mReader.nextString());
break;
case "syncToken":
eb.setSyncToken(mReader.nextString());
break;
default:
mReader.skipValue();
}

View File

@@ -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
@@ -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!");
}