Loading cmds/uinput/README.md +32 −28 Original line number Diff line number Diff line Loading @@ -59,21 +59,24 @@ 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 | |:-------------:|:---------------------:|:-----------------------| | `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 | |:-------------:|:---------------:|:------------------------| | `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 Loading @@ -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} } ] Loading Loading @@ -158,10 +162,10 @@ 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 | | `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 Loading @@ -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 ] } ``` Loading cmds/uinput/jni/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ cc_library_shared { "libbase", "libbinder", "liblog", "libinput", "libnativehelper", ], Loading cmds/uinput/jni/com_android_commands_uinput_Device.cpp +34 −14 Original line number Diff line number Diff line Loading @@ -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> Loading @@ -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 { Loading Loading @@ -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;" Loading @@ -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) { Loading cmds/uinput/src/com/android/commands/uinput/Device.java +31 −0 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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; } } cmds/uinput/src/com/android/commands/uinput/Event.java +139 −24 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 { Loading Loading @@ -257,7 +288,7 @@ public class Event { eb.setBus(readBus()); break; case "events": int[] injections = readIntList().stream() int[] injections = readInjectedEvents().stream() .mapToInt(Integer::intValue).toArray(); eb.setInjections(injections); break; Loading Loading @@ -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) { Loading @@ -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 { Loading @@ -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(); Loading @@ -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()); } } Loading @@ -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 { Loading Loading @@ -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(); Loading @@ -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(); Loading Loading
cmds/uinput/README.md +32 −28 Original line number Diff line number Diff line Loading @@ -59,21 +59,24 @@ 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 | |:-------------:|:---------------------:|:-----------------------| | `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 | |:-------------:|:---------------:|:------------------------| | `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 Loading @@ -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} } ] Loading Loading @@ -158,10 +162,10 @@ 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 | | `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 Loading @@ -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 ] } ``` Loading
cmds/uinput/jni/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ cc_library_shared { "libbase", "libbinder", "liblog", "libinput", "libnativehelper", ], Loading
cmds/uinput/jni/com_android_commands_uinput_Device.cpp +34 −14 Original line number Diff line number Diff line Loading @@ -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> Loading @@ -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 { Loading Loading @@ -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;" Loading @@ -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) { Loading
cmds/uinput/src/com/android/commands/uinput/Device.java +31 −0 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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; } }
cmds/uinput/src/com/android/commands/uinput/Event.java +139 −24 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 { Loading Loading @@ -257,7 +288,7 @@ public class Event { eb.setBus(readBus()); break; case "events": int[] injections = readIntList().stream() int[] injections = readInjectedEvents().stream() .mapToInt(Integer::intValue).toArray(); eb.setInjections(injections); break; Loading Loading @@ -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) { Loading @@ -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 { Loading @@ -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(); Loading @@ -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()); } } Loading @@ -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 { Loading Loading @@ -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(); Loading @@ -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(); Loading