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

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

Merge changes from topic "revert-26209003-revert-26154399-CXYXSETNUA-YFUIACDDDP" into main

* changes:
  Reland "uinput: delay from the end of the last delay"
  Reland "uinput: Specify timestamps when injecting events..."
  Reland "uinput: use nanoseconds for delay durations"
parents 61873aab dfc798ea
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -154,7 +154,8 @@ will be unregistered. There is no explicit command for unregistering a device.

#### `delay`

Add a delay to command processing
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.

| Field         | Type          | Description                |
|:-------------:|:-------------:|:-------------------------- |
+9 −9
Original line number Diff line number Diff line
@@ -166,14 +166,14 @@ UinputDevice::~UinputDevice() {
    ::ioctl(mFd, UI_DEV_DESTROY);
}

void UinputDevice::injectEvent(uint16_t type, uint16_t code, int32_t value) {
void UinputDevice::injectEvent(std::chrono::microseconds timestamp, uint16_t type, uint16_t code,
                               int32_t value) {
    struct input_event event = {};
    event.type = type;
    event.code = code;
    event.value = value;
    timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    TIMESPEC_TO_TIMEVAL(&event.time, &ts);
    event.time.tv_sec = timestamp.count() / 1'000'000;
    event.time.tv_usec = timestamp.count() % 1'000'000;

    if (::write(mFd, &event, sizeof(input_event)) < 0) {
        ALOGE("Could not write event %" PRIu16 " %" PRIu16 " with value %" PRId32 " : %s", type,
@@ -268,12 +268,12 @@ static void closeUinputDevice(JNIEnv* /* env */, jclass /* clazz */, jlong ptr)
    }
}

static void injectEvent(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint type, jint code,
                        jint value) {
static void injectEvent(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jlong timestampMicros,
                        jint type, jint code, jint value) {
    uinput::UinputDevice* d = reinterpret_cast<uinput::UinputDevice*>(ptr);
    if (d != nullptr) {
        d->injectEvent(static_cast<uint16_t>(type), static_cast<uint16_t>(code),
                       static_cast<int32_t>(value));
        d->injectEvent(std::chrono::microseconds(timestampMicros), static_cast<uint16_t>(type),
                       static_cast<uint16_t>(code), static_cast<int32_t>(value));
    } else {
        ALOGE("Could not inject event, Device* is null!");
    }
@@ -330,7 +330,7 @@ static JNINativeMethod sMethods[] = {
         "(Ljava/lang/String;IIIIIILjava/lang/String;"
         "Lcom/android/commands/uinput/Device$DeviceCallback;)J",
         reinterpret_cast<void*>(openUinputDevice)},
        {"nativeInjectEvent", "(JIII)V", reinterpret_cast<void*>(injectEvent)},
        {"nativeInjectEvent", "(JJIII)V", reinterpret_cast<void*>(injectEvent)},
        {"nativeConfigure", "(II[I)V", reinterpret_cast<void*>(configure)},
        {"nativeSetAbsInfo", "(IILandroid/os/Parcel;)V", reinterpret_cast<void*>(setAbsInfo)},
        {"nativeCloseUinputDevice", "(J)V", reinterpret_cast<void*>(closeUinputDevice)},
+7 −5
Original line number Diff line number Diff line
@@ -14,13 +14,14 @@
 * limitations under the License.
 */

#include <memory>
#include <vector>

#include <android-base/unique_fd.h>
#include <jni.h>
#include <linux/input.h>

#include <android-base/unique_fd.h>
#include <chrono>
#include <memory>
#include <vector>

#include "src/com/android/commands/uinput/InputAbsInfo.h"

namespace android {
@@ -53,7 +54,8 @@ public:

    virtual ~UinputDevice();

    void injectEvent(uint16_t type, uint16_t code, int32_t value);
    void injectEvent(std::chrono::microseconds timestamp, uint16_t type, uint16_t code,
                     int32_t value);
    int handleEvents(int events);

private:
+84 −22
Original line number Diff line number Diff line
@@ -55,7 +55,7 @@ public class Device {
    private final SparseArray<InputAbsInfo> mAbsInfo;
    private final OutputStream mOutputStream;
    private final Object mCond = new Object();
    private long mTimeToSend;
    private long mTimeToSendNanos;

    static {
        System.loadLibrary("uinputcommand_jni");
@@ -65,7 +65,8 @@ public class Device {
            int productId, int versionId, int bus, int ffEffectsMax, String port,
            DeviceCallback callback);
    private static native void nativeCloseUinputDevice(long ptr);
    private static native void nativeInjectEvent(long ptr, int type, int code, int value);
    private static native void nativeInjectEvent(long ptr, long timestampMicros, int type, int code,
                                                 int value);
    private static native void nativeConfigure(int handle, int code, int[] configs);
    private static native void nativeSetAbsInfo(int handle, int axisCode, Parcel axisParcel);
    private static native int nativeGetEvdevEventTypeByLabel(String label);
@@ -101,27 +102,54 @@ public class Device {
        }

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

    private long getTimeToSendMillis() {
        // Since we can only specify delays in milliseconds but evemu timestamps are in
        // microseconds, we have to round up the delays to avoid setting event timestamps
        // which are in the future (which the kernel would silently reject and replace with
        // the current time).
        //
        // This should be the same as (long) Math.ceil(mTimeToSendNanos / 1_000_000.0), except
        // without the precision loss that comes from converting from long to double and back.
        return mTimeToSendNanos / 1_000_000 + ((mTimeToSendNanos % 1_000_000 > 0) ? 1 : 0);
    }

    /**
     * Inject uinput events to device
     *
     * @param events  Array of raw uinput events.
     * @param offsetMicros The difference in microseconds between the timestamps of the previous
     *                     batch of events injected and this batch. If set to -1, the current
     *                     timestamp will be used.
     */
    public void injectEvent(int[] events) {
    public void injectEvent(int[] events, long offsetMicros) {
        // if two messages are sent at identical time, they will be processed in order received
        Message msg = mHandler.obtainMessage(MSG_INJECT_EVENT, events);
        mHandler.sendMessageAtTime(msg, mTimeToSend);
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = events;
        args.argl1 = offsetMicros;
        args.argl2 = SystemClock.uptimeNanos();
        Message msg = mHandler.obtainMessage(MSG_INJECT_EVENT, args);
        mHandler.sendMessageAtTime(msg, getTimeToSendMillis());
    }

    /**
     * Impose a delay to the device for execution.
     * Delay subsequent device activity by the specified amount of time.
     *
     * @param delay  Time to delay in unit of milliseconds.
     * <p>Note that although the delay is specified in nanoseconds, due to limitations of {@link
     * Handler}'s API, scheduling only occurs with millisecond precision. When scheduling an
     * injection or sync, the time at which it is scheduled will be rounded up to the nearest
     * millisecond. While this means that a particular injection cannot be scheduled precisely,
     * rounding errors will not accumulate over time. For example, if five injections are scheduled
     * with a delay of 1,200,000ns before each one, the total delay will be 6ms, as opposed to the
     * 10ms it would have been if each individual delay had been rounded up (as {@link EvemuParser}
     * would otherwise have to do to avoid sending timestamps that are in the future).
     *
     * @param delayNanos  Time to delay in unit of nanoseconds.
     */
    public void addDelay(int delay) {
        mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay;
    public void addDelayNanos(long delayNanos) {
        mTimeToSendNanos += delayNanos;
    }

    /**
@@ -131,7 +159,8 @@ public class Device {
     * @param syncToken  The token for this sync command.
     */
    public void syncEvent(String syncToken) {
        mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), mTimeToSend);
        mHandler.sendMessageAtTime(
                mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), getTimeToSendMillis());
    }

    /**
@@ -140,7 +169,8 @@ public class Device {
     */
    public void close() {
        Message msg = mHandler.obtainMessage(MSG_CLOSE_UINPUT_DEVICE);
        mHandler.sendMessageAtTime(msg, Math.max(SystemClock.uptimeMillis(), mTimeToSend) + 1);
        mHandler.sendMessageAtTime(
                msg, Math.max(SystemClock.uptimeMillis(), getTimeToSendMillis()) + 1);
        try {
            synchronized (mCond) {
                mCond.wait();
@@ -151,6 +181,7 @@ public class Device {

    private class DeviceHandler extends Handler {
        private long mPtr;
        private long mLastInjectTimestampMicros = -1;
        private int mBarrierToken;

        DeviceHandler(Looper looper) {
@@ -160,7 +191,7 @@ public class Device {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_OPEN_UINPUT_DEVICE:
                case MSG_OPEN_UINPUT_DEVICE: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    String name = (String) args.arg1;
                    mPtr = nativeOpenUinputDevice(name, args.argi1 /* id */,
@@ -177,15 +208,43 @@ public class Device {
                    }
                    args.recycle();
                    break;
                case MSG_INJECT_EVENT:
                    if (mPtr != 0) {
                        int[] events = (int[]) msg.obj;
                        for (int pos = 0; pos + 2 < events.length; pos += 3) {
                            nativeInjectEvent(mPtr, events[pos], events[pos + 1], events[pos + 2]);
                }
                case MSG_INJECT_EVENT: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    if (mPtr == 0) {
                        args.recycle();
                        break;
                    }
                    long offsetMicros = args.argl1;
                    if (mLastInjectTimestampMicros == -1 || offsetMicros == -1) {
                        // There's often a delay of a few milliseconds between the time specified to
                        // Handler.sendMessageAtTime and the handler actually being called, due to
                        // the way threads are scheduled. We don't take this into account when
                        // calling addDelayNanos between the first batch of event injections (when
                        // we set the "base timestamp" from which all others will be offset) and the
                        // second batch, meaning that the actual time between the handler calls for
                        // those batches may be less than the offset between their timestamps. When
                        // that happens, we would pass a timestamp for the second batch that's
                        // actually in the future. The kernel's uinput API rejects timestamps that
                        // are in the future and uses the current time instead, making the reported
                        // timestamps inconsistent with the recording we're replaying.
                        //
                        // To prevent this, we need to use the time at which we scheduled this first
                        // batch, rather than the actual current time.
                        mLastInjectTimestampMicros = args.argl2 / 1000;
                    } else {
                        mLastInjectTimestampMicros += offsetMicros;
                    }

                    int[] events = (int[]) args.arg1;
                    for (int pos = 0; pos + 2 < events.length; pos += 3) {
                        nativeInjectEvent(mPtr, mLastInjectTimestampMicros, events[pos],
                                events[pos + 1], events[pos + 2]);
                    }
                    args.recycle();
                    break;
                case MSG_CLOSE_UINPUT_DEVICE:
                }
                case MSG_CLOSE_UINPUT_DEVICE: {
                    if (mPtr != 0) {
                        nativeCloseUinputDevice(mPtr);
                        getLooper().quitSafely();
@@ -198,13 +257,16 @@ public class Device {
                        mCond.notify();
                    }
                    break;
                case MSG_SYNC_EVENT:
                }
                case MSG_SYNC_EVENT: {
                    handleSyncEvent((String) msg.obj);
                    break;
                default:
                }
                default: {
                    throw new IllegalArgumentException("Unknown device message");
                }
            }
        }

        public void pauseEvents() {
            mBarrierToken = getLooper().myQueue().postSyncBarrier();
+12 −16
Original line number Diff line number Diff line
@@ -44,7 +44,7 @@ public class EvemuParser implements EventParser {
     * recordings, this will always be the same.
     */
    private static final int DEVICE_ID = 1;
    private static final int REGISTRATION_DELAY_MILLIS = 500;
    private static final int REGISTRATION_DELAY_NANOS = 500_000_000;

    private static class CommentAwareReader {
        private final LineNumberReader mReader;
@@ -152,7 +152,7 @@ public class EvemuParser implements EventParser {
        final Event.Builder delayEb = new Event.Builder();
        delayEb.setId(DEVICE_ID);
        delayEb.setCommand(Event.Command.DELAY);
        delayEb.setDurationMillis(REGISTRATION_DELAY_MILLIS);
        delayEb.setDurationNanos(REGISTRATION_DELAY_NANOS);
        mQueuedEvents.add(delayEb.build());
    }

@@ -175,7 +175,6 @@ public class EvemuParser implements EventParser {
            throw new ParsingException(
                    "Invalid timestamp '" + parts[0] + "' (should contain a single '.')", mReader);
        }
        // TODO(b/310958309): use timeMicros to set the timestamp on the event being sent.
        final long timeMicros =
                parseLong(timeParts[0], 10) * 1_000_000 + parseInt(timeParts[1], 10);
        final Event.Builder eb = new Event.Builder();
@@ -192,23 +191,20 @@ public class EvemuParser implements EventParser {
            return eb.build();
        } else {
            final long delayMicros = timeMicros - mLastEventTimeMicros;
            // The shortest delay supported by Handler.sendMessageAtTime (used for timings by the
            // Device class) is 1ms, so ignore time differences smaller than that.
            if (delayMicros < 1000) {
                mLastEventTimeMicros = timeMicros;
            eb.setTimestampOffsetMicros(delayMicros);
            if (delayMicros == 0) {
                return eb.build();
            } else {
            }
            // Send a delay now, and queue the actual event for the next call.
            mQueuedEvents.add(eb.build());
            mLastEventTimeMicros = timeMicros;
            final Event.Builder delayEb = new Event.Builder();
            delayEb.setId(DEVICE_ID);
            delayEb.setCommand(Event.Command.DELAY);
                delayEb.setDurationMillis((int) (delayMicros / 1000));
            delayEb.setDurationNanos(delayMicros * 1000);
            return delayEb.build();
        }
    }
    }

    private Event parseRegistrationEvent() throws IOException {
        // The registration details at the start of a recording are specified by a set of lines
Loading