diff options
| -rw-r--r-- | cmds/uinput/README.md | 60 | ||||
| -rw-r--r-- | cmds/uinput/jni/Android.bp | 1 | ||||
| -rw-r--r-- | cmds/uinput/jni/com_android_commands_uinput_Device.cpp | 48 | ||||
| -rw-r--r-- | cmds/uinput/src/com/android/commands/uinput/Device.java | 31 | ||||
| -rw-r--r-- | cmds/uinput/src/com/android/commands/uinput/Event.java | 163 |
5 files changed, 237 insertions, 66 deletions
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md index 1ce8f9f33a9c..bdec8b97bae6 100644 --- a/cmds/uinput/README.md +++ b/cmds/uinput/README.md @@ -59,22 +59,25 @@ multiple devices are registered. and `"bluetooth"`. Device configuration is used to configure the uinput device. The `type` field provides a `UI_SET_*` -control code, and data is a vector of control values to be sent to the uinput device, which depends -on the control code. +control code as an integer value or a string label (e.g. `"UI_SET_EVBIT"`), and data is a vector of +control values to be sent to the uinput device, which depends on the control code. -| Field | Type | Description | -|:-------------:|:-------------:|:-------------------------- | -| `type` | integer | `UI_SET_` control type | -| `data` | integer array | control values | +| Field | Type | Description | +|:-------------:|:---------------------:|:-----------------------| +| `type` | integer\|string | `UI_SET_` control type | +| `data` | integer\|string array | control values | + +Due to the sequential nature in which this is parsed, the `type` field must be specified before +the `data` field in this JSON Object. `ff_effects_max` must be provided if `UI_SET_FFBIT` is used in `configuration`. `abs_info` fields are provided to set the device axes information. It is an array of below objects: -| Field | Type | Description | -|:-------------:|:-------------:|:-------------------------- | -| `code` | integer | Axis code | -| `info` | object | Axis information object | +| Field | Type | Description | +|:-------------:|:---------------:|:------------------------| +| `code` | integer\|string | Axis code or label | +| `info` | object | Axis information object | The axis information object is defined as below, with the fields having the same meaning as those Linux's [`struct input_absinfo`][struct input_absinfo]: @@ -99,16 +102,17 @@ Example: "pid": 0x2c42, "bus": "usb", "configuration":[ - {"type":100, "data":[1, 21]}, // UI_SET_EVBIT : EV_KEY and EV_FF - {"type":101, "data":[11, 2, 3, 4]}, // UI_SET_KEYBIT : KEY_0 KEY_1 KEY_2 KEY_3 - {"type":107, "data":[80]} // UI_SET_FFBIT : FF_RUMBLE + {"type":"UI_SET_EVBIT", "data":["EV_KEY", "EV_FF"]}, + {"type":"UI_SET_KEYBIT", "data":["KEY_0", "KEY_1", "KEY_2", "KEY_3"]}, + {"type":"UI_SET_ABSBIT", "data":["ABS_Y", "ABS_WHEEL"]}, + {"type":"UI_SET_FFBIT", "data":["FF_RUMBLE"]} ], "ff_effects_max" : 1, "abs_info": [ - {"code":1, "info": {"value":20, "minimum":-255, + {"code":"ABS_Y", "info": {"value":20, "minimum":-255, "maximum":255, "fuzz":0, "flat":0, "resolution":1} }, - {"code":8, "info": {"value":-50, "minimum":-255, + {"code":"ABS_WHEEL", "info": {"value":-50, "minimum":-255, "maximum":255, "fuzz":0, "flat":0, "resolution":1} } ] @@ -157,11 +161,11 @@ Example: Send an array of uinput event packets to the uinput device -| Field | Type | Description | -|:-------------:|:-------------:|:-------------------------- | -| `id` | integer | Device ID | -| `command` | string | Must be set to "inject" | -| `events` | integer array | events to inject | +| Field | Type | Description | +|:-------------:|:---------------------:|:-------------------------- | +| `id` | integer | Device ID | +| `command` | string | Must be set to "inject" | +| `events` | integer\|string array | events to inject | The `events` parameter is an array of integers in sets of three: a type, an axis code, and an axis value, like you'd find in Linux's `struct input_event`. For example, sending presses of the 0 and 1 @@ -171,14 +175,14 @@ keys would look like this: { "id": 1, "command": "inject", - "events": [0x01, 0xb, 0x1, // EV_KEY, KEY_0, DOWN - 0x00, 0x00, 0x00, // EV_SYN, SYN_REPORT, 0 - 0x01, 0x0b, 0x00, // EV_KEY, KEY_0, UP - 0x00, 0x00, 0x00, // EV_SYN, SYN_REPORT, 0 - 0x01, 0x2, 0x1, // EV_KEY, KEY_1, DOWN - 0x00, 0x00, 0x01, // EV_SYN, SYN_REPORT, 0 - 0x01, 0x02, 0x00, // EV_KEY, KEY_1, UP - 0x00, 0x00, 0x01 // EV_SYN, SYN_REPORT, 0 + "events": ["EV_KEY", "KEY_0", 1, + "EV_SYN", "SYN_REPORT", 0, + "EV_KEY", "KEY_0", 0, + "EV_SYN", "SYN_REPORT", 0, + "EV_KEY", "KEY_1", 1, + "EV_SYN", "SYN_REPORT", 0, + "EV_KEY", "KEY_1", 0, + "EV_SYN", "SYN_REPORT", 0 ] } ``` diff --git a/cmds/uinput/jni/Android.bp b/cmds/uinput/jni/Android.bp index c56adc35b580..558bcc54c7ef 100644 --- a/cmds/uinput/jni/Android.bp +++ b/cmds/uinput/jni/Android.bp @@ -21,6 +21,7 @@ cc_library_shared { "libbase", "libbinder", "liblog", + "libinput", "libnativehelper", ], diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp index 3f4163dc54ec..7659054119c8 100644 --- a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp +++ b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp @@ -16,12 +16,24 @@ #define LOG_TAG "UinputCommandDevice" -#include <linux/uinput.h> +#include "com_android_commands_uinput_Device.h" +#include <android-base/stringprintf.h> +#include <android/looper.h> +#include <android_os_Parcel.h> #include <fcntl.h> +#include <input/InputEventLabels.h> #include <inttypes.h> +#include <jni.h> +#include <linux/uinput.h> +#include <log/log.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedLocalRef.h> +#include <nativehelper/ScopedPrimitiveArray.h> +#include <nativehelper/ScopedUtfChars.h> #include <time.h> #include <unistd.h> + #include <algorithm> #include <array> #include <cstdio> @@ -30,19 +42,6 @@ #include <memory> #include <vector> -#include <android/looper.h> -#include <android_os_Parcel.h> -#include <jni.h> -#include <log/log.h> -#include <nativehelper/JNIHelp.h> -#include <nativehelper/ScopedLocalRef.h> -#include <nativehelper/ScopedPrimitiveArray.h> -#include <nativehelper/ScopedUtfChars.h> - -#include <android-base/stringprintf.h> - -#include "com_android_commands_uinput_Device.h" - namespace android { namespace uinput { @@ -307,6 +306,21 @@ static void setAbsInfo(JNIEnv* env, jclass /* clazz */, jint handle, jint axisCo ::ioctl(static_cast<int>(handle), UI_ABS_SETUP, &absSetup); } +static jint getEvdevEventTypeByLabel(JNIEnv* env, jclass /* clazz */, jstring rawLabel) { + ScopedUtfChars label(env, rawLabel); + return InputEventLookup::getLinuxEvdevEventTypeByLabel(label.c_str()).value_or(-1); +} + +static jint getEvdevEventCodeByLabel(JNIEnv* env, jclass /* clazz */, jint type, jstring rawLabel) { + ScopedUtfChars label(env, rawLabel); + return InputEventLookup::getLinuxEvdevEventCodeByLabel(type, label.c_str()).value_or(-1); +} + +static jint getEvdevInputPropByLabel(JNIEnv* env, jclass /* clazz */, jstring rawLabel) { + ScopedUtfChars label(env, rawLabel); + return InputEventLookup::getLinuxEvdevInputPropByLabel(label.c_str()).value_or(-1); +} + static JNINativeMethod sMethods[] = { {"nativeOpenUinputDevice", "(Ljava/lang/String;IIIIILjava/lang/String;" @@ -316,6 +330,12 @@ static JNINativeMethod sMethods[] = { {"nativeConfigure", "(II[I)V", reinterpret_cast<void*>(configure)}, {"nativeSetAbsInfo", "(IILandroid/os/Parcel;)V", reinterpret_cast<void*>(setAbsInfo)}, {"nativeCloseUinputDevice", "(J)V", reinterpret_cast<void*>(closeUinputDevice)}, + {"nativeGetEvdevEventTypeByLabel", "(Ljava/lang/String;)I", + reinterpret_cast<void*>(getEvdevEventTypeByLabel)}, + {"nativeGetEvdevEventCodeByLabel", "(ILjava/lang/String;)I", + reinterpret_cast<void*>(getEvdevEventCodeByLabel)}, + {"nativeGetEvdevInputPropByLabel", "(Ljava/lang/String;)I", + reinterpret_cast<void*>(getEvdevInputPropByLabel)}, }; int register_com_android_commands_uinput_Device(JNIEnv* env) { diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java index 732b33d60c15..6458eef99008 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Device.java +++ b/cmds/uinput/src/com/android/commands/uinput/Device.java @@ -66,6 +66,9 @@ public class Device { 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); + private static native int nativeGetEvdevEventCodeByLabel(int type, String label); + private static native int nativeGetEvdevInputPropByLabel(String label); public Device(int id, String name, int vid, int pid, int bus, SparseArray<int[]> configuration, int ffEffectsMax, @@ -234,4 +237,32 @@ public class Device { msg.sendToTarget(); } } + + static int getEvdevEventTypeByLabel(String label) { + final var type = nativeGetEvdevEventTypeByLabel(label); + if (type < 0) { + throw new IllegalArgumentException( + "Failed to get evdev event type from label: " + label); + } + return type; + } + + static int getEvdevEventCodeByLabel(int type, String label) { + final var code = nativeGetEvdevEventCodeByLabel(type, label); + if (code < 0) { + throw new IllegalArgumentException( + "Failed to get evdev event code for type " + type + " from label: " + label); + } + return code; + + } + + static int getEvdevInputPropByLabel(String label) { + final var prop = nativeGetEvdevInputPropByLabel(label); + if (prop < 0) { + throw new IllegalArgumentException( + "Failed to get evdev input prop from label: " + label); + } + return prop; + } } diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java index 4b090f5a713c..cddb407475a2 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Event.java +++ b/cmds/uinput/src/com/android/commands/uinput/Event.java @@ -25,6 +25,9 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; import java.util.stream.IntStream; import src.com.android.commands.uinput.InputAbsInfo; @@ -39,7 +42,35 @@ public class Event { public static final String COMMAND_REGISTER = "register"; public static final String COMMAND_DELAY = "delay"; public static final String COMMAND_INJECT = "inject"; - private static final int ABS_CNT = 64; + private static final int EV_KEY = 0x01; + private static final int EV_REL = 0x02; + private static final int EV_ABS = 0x03; + private static final int EV_MSC = 0x04; + private static final int EV_SW = 0x05; + private static final int EV_LED = 0x11; + private static final int EV_SND = 0x12; + private static final int EV_FF = 0x15; + + private enum UinputControlCode { + UI_SET_EVBIT("UI_SET_EVBIT", 100), + UI_SET_KEYBIT("UI_SET_KEYBIT", 101), + UI_SET_RELBIT("UI_SET_RELBIT", 102), + UI_SET_ABSBIT("UI_SET_ABSBIT", 103), + UI_SET_MSCBIT("UI_SET_MSCBIT", 104), + UI_SET_LEDBIT("UI_SET_LEDBIT", 105), + UI_SET_SNDBIT("UI_SET_SNDBIT", 106), + UI_SET_FFBIT("UI_SET_FFBIT", 107), + UI_SET_SWBIT("UI_SET_SWBIT", 109), + UI_SET_PROPBIT("UI_SET_PROPBIT", 110); + + final String mName; + final int mValue; + + UinputControlCode(String name, int value) { + this.mName = name; + this.mValue = value; + } + } // These constants come from "include/uapi/linux/input.h" in the kernel enum Bus { @@ -257,8 +288,8 @@ public class Event { eb.setBus(readBus()); break; case "events": - int[] injections = readIntList().stream() - .mapToInt(Integer::intValue).toArray(); + int[] injections = readInjectedEvents().stream() + .mapToInt(Integer::intValue).toArray(); eb.setInjections(injections); break; case "configuration": @@ -293,12 +324,17 @@ public class Event { return e; } - private ArrayList<Integer> readIntList() throws IOException { - ArrayList<Integer> data = new ArrayList<Integer>(); + private ArrayList<Integer> readInjectedEvents() throws IOException { + ArrayList<Integer> data = new ArrayList<>(); try { mReader.beginArray(); while (mReader.hasNext()) { - data.add(Integer.decode(mReader.nextString())); + // Read events in groups of three, because we expect an event type, event code, + // and event value. + final int type = readEvdevEventType(); + data.add(type); + data.add(readEvdevEventCode(type)); + data.add(readInt()); } mReader.endArray(); } catch (IllegalStateException | NumberFormatException e) { @@ -309,22 +345,32 @@ public class Event { return data; } - private byte[] readData() throws IOException { - ArrayList<Integer> data = readIntList(); - byte[] rawData = new byte[data.size()]; - for (int i = 0; i < data.size(); i++) { - int d = data.get(i); - if ((d & 0xFF) != d) { - throw new IllegalStateException("Invalid data, all values must be byte-sized"); + private int readValueAsInt(Function<String, Integer> stringToInt) throws IOException { + switch (mReader.peek()) { + case NUMBER: { + return mReader.nextInt(); + } + case STRING: { + final var str = mReader.nextString(); + try { + // Attempt to first parse the value as an int. + return Integer.decode(str); + } catch (NumberFormatException e) { + // Then fall back to the supplied function. + return stringToInt.apply(str); + } + } + default: { + throw new IllegalStateException( + "Encountered malformed data. Expected int or string."); } - rawData[i] = (byte) d; } - return rawData; } private int readInt() throws IOException { - String val = mReader.nextString(); - return Integer.decode(val); + return readValueAsInt((str) -> { + throw new IllegalStateException("Encountered malformed data. Expected int."); + }); } private Bus readBus() throws IOException { @@ -338,17 +384,20 @@ public class Event { try { mReader.beginArray(); while (mReader.hasNext()) { - int type = 0; + UinputControlCode controlCode = null; IntStream data = null; mReader.beginObject(); while (mReader.hasNext()) { String name = mReader.nextName(); switch (name) { case "type": - type = readInt(); + controlCode = readUinputControlCode(); break; case "data": - data = readIntList().stream().mapToInt(Integer::intValue); + Objects.requireNonNull(controlCode, + "Configuration 'type' must be specified before 'data'."); + data = readDataForControlCode(controlCode) + .stream().mapToInt(Integer::intValue); break; default: consumeRemainingElements(); @@ -358,9 +407,9 @@ public class Event { } } mReader.endObject(); - if (data != null) { - final int[] existing = configuration.get(type); - configuration.put(type, existing == null ? data.toArray() + if (controlCode != null && data != null) { + final int[] existing = configuration.get(controlCode.mValue); + configuration.put(controlCode.mValue, existing == null ? data.toArray() : IntStream.concat(IntStream.of(existing), data).toArray()); } } @@ -373,6 +422,60 @@ public class Event { return configuration; } + private UinputControlCode readUinputControlCode() throws IOException { + var code = readValueAsInt((controlTypeStr) -> { + for (UinputControlCode controlCode : UinputControlCode.values()) { + if (controlCode.mName.equals(controlTypeStr)) { + return controlCode.mValue; + } + } + return -1; + }); + for (UinputControlCode controlCode : UinputControlCode.values()) { + if (controlCode.mValue == code) { + return controlCode; + } + } + return null; + } + + private List<Integer> readDataForControlCode( + UinputControlCode controlCode) throws IOException { + return switch (controlCode) { + case UI_SET_EVBIT -> readArrayAsInts(this::readEvdevEventType); + case UI_SET_KEYBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_KEY)); + case UI_SET_RELBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_REL)); + case UI_SET_ABSBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_ABS)); + case UI_SET_MSCBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_MSC)); + case UI_SET_LEDBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_LED)); + case UI_SET_SNDBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_SND)); + case UI_SET_FFBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_FF)); + case UI_SET_SWBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_SW)); + case UI_SET_PROPBIT -> readArrayAsInts(this::readEvdevInputProp); + }; + } + + interface IntValueReader { + int readNextValue() throws IOException; + } + + private ArrayList<Integer> readArrayAsInts( + IntValueReader nextValueReader) throws IOException { + ArrayList<Integer> data = new ArrayList<>(); + try { + mReader.beginArray(); + while (mReader.hasNext()) { + data.add(nextValueReader.readNextValue()); + } + mReader.endArray(); + } catch (IllegalStateException | NumberFormatException e) { + consumeRemainingElements(); + mReader.endArray(); + throw new IllegalStateException("Encountered malformed data.", e); + } + return data; + } + private InputAbsInfo readAbsInfo() throws IllegalStateException, IOException { InputAbsInfo absInfo = new InputAbsInfo(); try { @@ -426,7 +529,7 @@ public class Event { String name = mReader.nextName(); switch (name) { case "code": - type = readInt(); + type = readEvdevEventCode(EV_ABS); break; case "info": absInfo = readAbsInfo(); @@ -452,6 +555,18 @@ public class Event { return infoArray; } + private int readEvdevEventType() throws IOException { + return readValueAsInt(Device::getEvdevEventTypeByLabel); + } + + private int readEvdevEventCode(int type) throws IOException { + return readValueAsInt((str) -> Device.getEvdevEventCodeByLabel(type, str)); + } + + private int readEvdevInputProp() throws IOException { + return readValueAsInt(Device::getEvdevInputPropByLabel); + } + private void consumeRemainingElements() throws IOException { while (mReader.hasNext()) { mReader.skipValue(); |