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:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user