diff options
9 files changed, 75 insertions, 158 deletions
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md index f1775864aca0..b6e4e0de8cf7 100644 --- a/cmds/uinput/README.md +++ b/cmds/uinput/README.md @@ -154,8 +154,7 @@ 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 to command processing | Field | Type | Description | |:-------------:|:-------------:|:-------------------------- | diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp index bd61000186e5..a78a46504684 100644 --- a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp +++ b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp @@ -166,14 +166,14 @@ UinputDevice::~UinputDevice() { ::ioctl(mFd, UI_DEV_DESTROY); } -void UinputDevice::injectEvent(std::chrono::microseconds timestamp, uint16_t type, uint16_t code, - int32_t value) { +void UinputDevice::injectEvent(uint16_t type, uint16_t code, int32_t value) { struct input_event event = {}; event.type = type; event.code = code; event.value = value; - event.time.tv_sec = timestamp.count() / 1'000'000; - event.time.tv_usec = timestamp.count() % 1'000'000; + timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + TIMESPEC_TO_TIMEVAL(&event.time, &ts); 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, jlong timestampMicros, - jint type, jint code, jint value) { +static void injectEvent(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint type, jint code, + jint value) { uinput::UinputDevice* d = reinterpret_cast<uinput::UinputDevice*>(ptr); if (d != nullptr) { - d->injectEvent(std::chrono::microseconds(timestampMicros), static_cast<uint16_t>(type), - static_cast<uint16_t>(code), static_cast<int32_t>(value)); + d->injectEvent(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", "(JJIII)V", reinterpret_cast<void*>(injectEvent)}, + {"nativeInjectEvent", "(JIII)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)}, diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.h b/cmds/uinput/jni/com_android_commands_uinput_Device.h index 72c8647ae743..9769a75bd9ef 100644 --- a/cmds/uinput/jni/com_android_commands_uinput_Device.h +++ b/cmds/uinput/jni/com_android_commands_uinput_Device.h @@ -14,14 +14,13 @@ * limitations under the License. */ -#include <android-base/unique_fd.h> -#include <jni.h> -#include <linux/input.h> - -#include <chrono> #include <memory> #include <vector> +#include <jni.h> +#include <linux/input.h> + +#include <android-base/unique_fd.h> #include "src/com/android/commands/uinput/InputAbsInfo.h" namespace android { @@ -54,8 +53,7 @@ public: virtual ~UinputDevice(); - void injectEvent(std::chrono::microseconds timestamp, uint16_t type, uint16_t code, - int32_t value); + void injectEvent(uint16_t type, uint16_t code, int32_t value); int handleEvents(int events); private: diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java index 76ab475adc0a..25d3a341bf6e 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Device.java +++ b/cmds/uinput/src/com/android/commands/uinput/Device.java @@ -55,7 +55,7 @@ public class Device { private final SparseArray<InputAbsInfo> mAbsInfo; private final OutputStream mOutputStream; private final Object mCond = new Object(); - private long mTimeToSendNanos; + private long mTimeToSend; static { System.loadLibrary("uinputcommand_jni"); @@ -65,8 +65,7 @@ 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, long timestampMicros, int type, int code, - int value); + private static native void nativeInjectEvent(long ptr, 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); @@ -102,54 +101,27 @@ public class Device { } mHandler.obtainMessage(MSG_OPEN_UINPUT_DEVICE, args).sendToTarget(); - 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); + mTimeToSend = SystemClock.uptimeMillis(); } /** * 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, long offsetMicros) { + public void injectEvent(int[] events) { // if two messages are sent at identical time, they will be processed in order received - SomeArgs args = SomeArgs.obtain(); - args.arg1 = events; - args.argl1 = offsetMicros; - args.argl2 = mTimeToSendNanos; - Message msg = mHandler.obtainMessage(MSG_INJECT_EVENT, args); - mHandler.sendMessageAtTime(msg, getTimeToSendMillis()); + Message msg = mHandler.obtainMessage(MSG_INJECT_EVENT, events); + mHandler.sendMessageAtTime(msg, mTimeToSend); } /** - * Delay subsequent device activity by the specified amount of time. + * Impose a delay to the device for execution. * - * <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. + * @param delay Time to delay in unit of milliseconds. */ - public void addDelayNanos(long delayNanos) { - mTimeToSendNanos += delayNanos; + public void addDelay(int delay) { + mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay; } /** @@ -159,8 +131,7 @@ public class Device { * @param syncToken The token for this sync command. */ public void syncEvent(String syncToken) { - mHandler.sendMessageAtTime( - mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), getTimeToSendMillis()); + mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), mTimeToSend); } /** @@ -169,8 +140,7 @@ public class Device { */ public void close() { Message msg = mHandler.obtainMessage(MSG_CLOSE_UINPUT_DEVICE); - mHandler.sendMessageAtTime( - msg, Math.max(SystemClock.uptimeMillis(), getTimeToSendMillis()) + 1); + mHandler.sendMessageAtTime(msg, Math.max(SystemClock.uptimeMillis(), mTimeToSend) + 1); try { synchronized (mCond) { mCond.wait(); @@ -181,7 +151,6 @@ public class Device { private class DeviceHandler extends Handler { private long mPtr; - private long mLastInjectTimestampMicros = -1; private int mBarrierToken; DeviceHandler(Looper looper) { @@ -191,7 +160,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 */, @@ -208,44 +177,15 @@ public class Device { } args.recycle(); break; - } - 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 we scheduled this first batch - // for (in microseconds, to avoid potential rounding up from - // getTimeToSendMillis), 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]); + 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]); + } } - args.recycle(); break; - } - case MSG_CLOSE_UINPUT_DEVICE: { + case MSG_CLOSE_UINPUT_DEVICE: if (mPtr != 0) { nativeCloseUinputDevice(mPtr); getLooper().quitSafely(); @@ -258,14 +198,11 @@ 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"); - } } } diff --git a/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java index da991624e685..7652f2403f6e 100644 --- a/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java +++ b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java @@ -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_NANOS = 500_000_000; + private static final int REGISTRATION_DELAY_MILLIS = 500; 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.setDurationNanos(REGISTRATION_DELAY_NANOS); + delayEb.setDurationMillis(REGISTRATION_DELAY_MILLIS); mQueuedEvents.add(delayEb.build()); } @@ -175,6 +175,7 @@ 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(); @@ -191,18 +192,21 @@ public class EvemuParser implements EventParser { return eb.build(); } else { final long delayMicros = timeMicros - mLastEventTimeMicros; - eb.setTimestampOffsetMicros(delayMicros); - if (delayMicros == 0) { + // 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; 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)); + return delayEb.build(); } - // 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.setDurationNanos(delayMicros * 1000); - return delayEb.build(); } } diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java index 9e7ee0937efe..0f16a27aac1d 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Event.java +++ b/cmds/uinput/src/com/android/commands/uinput/Event.java @@ -99,9 +99,8 @@ public class Event { private int mVersionId; private int mBusId; private int[] mInjections; - private long mTimestampOffsetMicros = -1; private SparseArray<int[]> mConfiguration; - private long mDurationNanos; + private int mDurationMillis; private int mFfEffectsMax = 0; private String mInputPort; private SparseArray<InputAbsInfo> mAbsInfo; @@ -140,28 +139,19 @@ public class Event { } /** - * Returns the number of microseconds that should be added to the previous {@code INJECT} - * event's timestamp to produce the timestamp for this {@code INJECT} event. A value of -1 - * indicates that the current timestamp should be used instead. - */ - public long getTimestampOffsetMicros() { - return mTimestampOffsetMicros; - } - - /** * Returns a {@link SparseArray} describing the event codes that should be registered for the * device. The keys are uinput ioctl codes (such as those returned from {@link * UinputControlCode#getValue()}, while the values are arrays of event codes to be enabled with * those ioctls. For example, key 101 (corresponding to {@link UinputControlCode#UI_SET_KEYBIT}) - * could have values 0x110 ({@code BTN_LEFT}), 0x111 ({@code BTN_RIGHT}), and 0x112 + * could have values 0x110 ({@code BTN_LEFT}, 0x111 ({@code BTN_RIGHT}), and 0x112 * ({@code BTN_MIDDLE}). */ public SparseArray<int[]> getConfiguration() { return mConfiguration; } - public long getDurationNanos() { - return mDurationNanos; + public int getDurationMillis() { + return mDurationMillis; } public int getFfEffectsMax() { @@ -192,7 +182,7 @@ public class Event { + ", busId=" + mBusId + ", events=" + Arrays.toString(mInjections) + ", configuration=" + mConfiguration - + ", duration=" + mDurationNanos + "ns" + + ", duration=" + mDurationMillis + "ms" + ", ff_effects_max=" + mFfEffectsMax + ", port=" + mInputPort + "}"; @@ -221,10 +211,6 @@ public class Event { mEvent.mInjections = events; } - public void setTimestampOffsetMicros(long offsetMicros) { - mEvent.mTimestampOffsetMicros = offsetMicros; - } - /** * Sets the event codes that should be registered with a {@code register} command. * @@ -251,8 +237,8 @@ public class Event { mEvent.mBusId = busId; } - public void setDurationNanos(long durationNanos) { - mEvent.mDurationNanos = durationNanos; + public void setDurationMillis(int durationMillis) { + mEvent.mDurationMillis = durationMillis; } public void setFfEffectsMax(int ffEffectsMax) { @@ -285,7 +271,7 @@ public class Event { } } case DELAY -> { - if (mEvent.mDurationNanos <= 0) { + if (mEvent.mDurationMillis <= 0) { throw new IllegalStateException("Delay has missing or invalid duration"); } } diff --git a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java index 6994f0cb0e4b..ed3ff33f7e52 100644 --- a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java +++ b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java @@ -71,8 +71,7 @@ public class JsonStyleParser implements EventParser { case "configuration" -> eb.setConfiguration(readConfiguration()); case "ff_effects_max" -> eb.setFfEffectsMax(readInt()); case "abs_info" -> eb.setAbsInfo(readAbsInfoArray()); - // Duration is specified in milliseconds in the JSON-style format. - case "duration" -> eb.setDurationNanos(readInt() * 1_000_000L); + case "duration" -> eb.setDurationMillis(readInt()); case "port" -> eb.setInputPort(mReader.nextString()); case "syncToken" -> eb.setSyncToken(mReader.nextString()); default -> mReader.skipValue(); diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java index 760e981c8465..04df27987d58 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java +++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java @@ -134,8 +134,8 @@ public class Uinput { switch (Objects.requireNonNull(e.getCommand())) { case REGISTER -> error("Device id=" + e.getId() + " is already registered. Ignoring event."); - case INJECT -> d.injectEvent(e.getInjections(), e.getTimestampOffsetMicros()); - case DELAY -> d.addDelayNanos(e.getDurationNanos()); + case INJECT -> d.injectEvent(e.getInjections()); + case DELAY -> d.addDelay(e.getDurationMillis()); case SYNC -> d.syncEvent(e.getSyncToken()); } } diff --git a/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java b/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java index 5239fbc7e0a8..a05cc676ddfa 100644 --- a/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java +++ b/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java @@ -183,22 +183,16 @@ public class EvemuParserTest { } private void assertInjectEvent(Event event, int eventType, int eventCode, int value) { - assertInjectEvent(event, eventType, eventCode, value, 0); - } - - private void assertInjectEvent(Event event, int eventType, int eventCode, int value, - long timestampOffsetMicros) { assertThat(event).isNotNull(); assertThat(event.getCommand()).isEqualTo(Event.Command.INJECT); assertThat(event.getInjections()).asList() .containsExactly(eventType, eventCode, value).inOrder(); - assertThat(event.getTimestampOffsetMicros()).isEqualTo(timestampOffsetMicros); } - private void assertDelayEvent(Event event, int durationNanos) { + private void assertDelayEvent(Event event, int durationMillis) { assertThat(event).isNotNull(); assertThat(event.getCommand()).isEqualTo(Event.Command.DELAY); - assertThat(event.getDurationNanos()).isEqualTo(durationNanos); + assertThat(event.getDurationMillis()).isEqualTo(durationMillis); } @Test @@ -213,7 +207,7 @@ public class EvemuParserTest { EvemuParser parser = new EvemuParser(reader); assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER); assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY); - assertInjectEvent(parser.getNextEvent(), 0x2, 0x0, 1, -1); + assertInjectEvent(parser.getNextEvent(), 0x2, 0x0, 1); assertInjectEvent(parser.getNextEvent(), 0x2, 0x1, -2); assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0); } @@ -234,17 +228,17 @@ public class EvemuParserTest { assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER); assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY); - assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1, -1); + assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1); assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0); - assertDelayEvent(parser.getNextEvent(), 10_000_000); + assertDelayEvent(parser.getNextEvent(), 10); - assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 0, 10_000); + assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 0); assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0); - assertDelayEvent(parser.getNextEvent(), 1_000_000_000); + assertDelayEvent(parser.getNextEvent(), 1000); - assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1, 1_000_000); + assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1); assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0); } @@ -483,7 +477,7 @@ public class EvemuParserTest { assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY); - assertInjectEvent(parser.getNextEvent(), 0x3, 0x39, 0, -1); + assertInjectEvent(parser.getNextEvent(), 0x3, 0x39, 0); assertInjectEvent(parser.getNextEvent(), 0x3, 0x35, 891); assertInjectEvent(parser.getNextEvent(), 0x3, 0x36, 333); assertInjectEvent(parser.getNextEvent(), 0x3, 0x3a, 56); @@ -496,8 +490,8 @@ public class EvemuParserTest { assertInjectEvent(parser.getNextEvent(), 0x3, 0x18, 56); assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0); - assertDelayEvent(parser.getNextEvent(), 6_080_000); + assertDelayEvent(parser.getNextEvent(), 6); - assertInjectEvent(parser.getNextEvent(), 0x3, 0x0035, 888, 6_080); + assertInjectEvent(parser.getNextEvent(), 0x3, 0x0035, 888); } } |