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

Commit 526ae41a authored by Harry Cutts's avatar Harry Cutts
Browse files

uinput: add updateTimeBase command

The recent change to how event injections are scheduled causes problems
when a client waits a considerable time after a device registration
before sending a lot of events separated by delays. The long wait means
that many of the events get scheduled in the past, so they're all
injected at once, filling the kernel's buffers and causing events to be
dropped.

The updateTimeBase command allows the client to tell us that the long
delay is intentional, and that the following injections should be
scheduled relative to the current time, rather than the previous
injections or the registration.

[0]: change Ieaa4f2f06d5e0b7d13abc3afa474948996db7c0a

Bug: 330844071
Test: check for SYN_DROPPED events in the output of:
      $ adb shell getevent -lt
      while running the inking test:
      $ atest 'PlatformScenarioTests:android.platform.test.scenario.sysui.stylus.StylusInkingTest#writeText_appearsInShowcaseApp'
Change-Id: I31c596251e27149da16270a23f4f57e7bb8e3460
parent 08ece225
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();
        }
    }