Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 89b39b25 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes Ie522ff44,I928da2ad into main

* changes:
  Uinput: Add sync command to get a response when the command is processed
  Uinput: Use enums for commands
parents b7346ca2 6115d411
Loading
Loading
Loading
Loading
+35 −1
Original line number Diff line number Diff line
@@ -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

@@ -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.
+37 −7
Original line number Diff line number Diff line
@@ -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;
@@ -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.
     *
@@ -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");
            }
@@ -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 {
@@ -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);
@@ -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() {
@@ -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) {
+54 −18
Original line number Diff line number Diff line
@@ -39,9 +39,19 @@ 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";
    enum Command {
        REGISTER("register"),
        DELAY("delay"),
        INJECT("inject"),
        SYNC("sync");

        final String mCommandName;

        Command(String command) {
            mCommandName = command;
        }
    }

    private static final int EV_KEY = 0x01;
    private static final int EV_REL = 0x02;
    private static final int EV_ABS = 0x03;
@@ -87,7 +97,7 @@ public class Event {
    }

    private int mId;
    private String mCommand;
    private Command mCommand;
    private String mName;
    private int mVid;
    private int mPid;
@@ -98,12 +108,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;
    }

@@ -147,6 +158,10 @@ public class Event {
        return mInputport;
    }

    public String getSyncToken() {
        return mSyncToken;
    }

    /**
     * Convert an event to String.
     */
@@ -177,7 +192,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) {
@@ -220,27 +242,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;
        }
@@ -307,6 +340,9 @@ public class Event {
                            case "port":
                                eb.setInputport(mReader.nextString());
                                break;
                            case "syncToken":
                                eb.setSyncToken(mReader.nextString());
                                break;
                            default:
                                mReader.skipValue();
                        }
+16 −16
Original line number Diff line number Diff line
@@ -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!");
        }