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

Commit 081955aa authored by Harry Cutts's avatar Harry Cutts Committed by Android (Google) Code Review
Browse files

Merge "uinput: add updateTimeBase command" into main

parents e70357a6 526ae41a
Loading
Loading
Loading
Loading
+57 −2
Original line number Diff line number Diff line
@@ -154,8 +154,24 @@ will be unregistered. There is no explicit command for unregistering a device.

#### `delay`

Add a delay between the processing of commands. The delay will be timed from when the last delay
ended, rather than from the current time, to allow for more precise timings to be produced.
Add a delay between the processing of commands.

The delay will be timed relative to the time base, a reference time which is set when the device is
registered or by the `updateTimeBase` command. Take the following set of example commands:

1. `register` device
2. `delay` 500ms
3. `inject` some events
4. `delay` 10ms
5. `inject` more events

If the `register` command is executed at time _X_, the injection at step 3 will be scheduled for
time _X_+500ms. Since scheduling isn't precise, they might actually be injected a few milliseconds
later, but regardless of that the injection at step 5 will always be scheduled for _X_+510ms. This
prevents scheduling delays from building up over time and slowing down the playback of recordings.
However, it does mean that when you expect to wait for an indeterminate period of time, you should
send `updateTimeBase` afterwards to prevent following events being scheduled in the past — see that
command's section for an example.

| Field         | Type          | Description                |
|:-------------:|:-------------:|:-------------------------- |
@@ -173,6 +189,45 @@ Example:
}
```

#### `updateTimeBase`

Update the time base from which the following events are scheduled to the current time. When
controlling `uinput` over standard input, you should send this command if you want following events
to be scheduled relative to now, rather than the last injection. See the following example set of
commands and the times they will be scheduled to run at:

1. `register` (say this occurs at time _X_)
2. `delay` 500ms
3. `inject`: scheduled for _X_+500ms
4. `delay` 10ms
5. `inject`: scheduled for _X_+510ms
6. (wait a few seconds)
7. `updateTimeBase` (say this occurs at time _Y_)
8. `delay` 10ms
9. `inject`: scheduled for _Y_+10ms

Without the `updateTimeBase` command, the final injection would be scheduled for _X_+520ms, which
would be in the past.

This is useful if you are issuing commands in multiple stages with long or unknown delays in between
them. For example, say you have a test that does the following:

1. `register` a device
2. `inject` a few events that should launch an app
3. Wait for the app to launch (an indeterminate amount of time, possibly seconds)
4. 1000 `inject` commands separated by `delay` commands of 10ms

Without `updateTimeBase`, the `inject` commands of step 4 will be scheduled to start immediately
after the events from step 2. That time is probably in the past, so many of the 1000 injections will
be sent immediately. This will likely fill the kernel's event buffers, causing events to be dropped.
Sending `updateTimeBase` before the `inject` commands in step 4 will schedule them relative to the
current time, meaning that they will be all injected with the intended 10ms delays between them.

| Field         | Type          | Description                     |
|:-------------:|:-------------:|:------------------------------- |
| `id`          | integer       | Device ID                       |
| `command`     | string        | Must be set to "updateTimeBase" |

#### `inject`

Send an array of uinput event packets to the uinput device
+8 −1
Original line number Diff line number Diff line
@@ -102,7 +102,7 @@ public class Device {
        }

        mHandler.obtainMessage(MSG_OPEN_UINPUT_DEVICE, args).sendToTarget();
        mTimeToSendNanos = SystemClock.uptimeNanos();
        updateTimeBase();
    }

    private long getTimeToSendMillis() {
@@ -134,6 +134,13 @@ public class Device {
        mHandler.sendMessageAtTime(msg, getTimeToSendMillis());
    }

    /**
     * Set the reference time from which future injections are scheduled to the current time.
     */
    public void updateTimeBase() {
        mTimeToSendNanos = SystemClock.uptimeNanos();
    }

    /**
     * Delay subsequent device activity by the specified amount of time.
     *
+1 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ public class Event {
        DELAY,
        INJECT,
        SYNC,
        UPDATE_TIME_BASE,
    }

    // Constants representing evdev event types, from include/uapi/linux/input-event-codes.h in the
+14 −2
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.IntStream;
@@ -57,8 +58,7 @@ public class JsonStyleParser implements EventParser {
                    String name = mReader.nextName();
                    switch (name) {
                        case "id" -> eb.setId(readInt());
                        case "command" -> eb.setCommand(
                                Event.Command.valueOf(mReader.nextString().toUpperCase()));
                        case "command" -> eb.setCommand(readCommand());
                        case "name" -> eb.setName(mReader.nextString());
                        case "vid" -> eb.setVendorId(readInt());
                        case "pid" -> eb.setProductId(readInt());
@@ -91,6 +91,18 @@ public class JsonStyleParser implements EventParser {
        return e;
    }

    private Event.Command readCommand() throws IOException {
        String commandStr = mReader.nextString();
        return switch (commandStr.toLowerCase(Locale.ROOT)) {
            case "register" -> Event.Command.REGISTER;
            case "delay" -> Event.Command.DELAY;
            case "inject" -> Event.Command.INJECT;
            case "sync" -> Event.Command.SYNC;
            case "updatetimebase" -> Event.Command.UPDATE_TIME_BASE;
            default -> throw new IllegalStateException("Invalid command \"" + commandStr + "\"");
        };
    }

    private ArrayList<Integer> readInjectedEvents() throws IOException {
        ArrayList<Integer> data = new ArrayList<>();
        try {
+1 −0
Original line number Diff line number Diff line
@@ -137,6 +137,7 @@ public class Uinput {
            case INJECT -> d.injectEvent(e.getInjections(), e.getTimestampOffsetMicros());
            case DELAY -> d.addDelayNanos(e.getDurationNanos());
            case SYNC -> d.syncEvent(e.getSyncToken());
            case UPDATE_TIME_BASE -> d.updateTimeBase();
        }
    }