Loading cmds/uinput/README.md +32 −28 Original line number Original line Diff line number Diff line Loading @@ -59,21 +59,24 @@ multiple devices are registered. and `"bluetooth"`. and `"bluetooth"`. Device configuration is used to configure the uinput device. The `type` field provides a `UI_SET_*` 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 control code as an integer value or a string label (e.g. `"UI_SET_EVBIT"`), and data is a vector of on the control code. control values to be sent to the uinput device, which depends on the control code. | Field | Type | Description | | Field | Type | Description | |:-------------:|:-------------:|:-------------------------- | |:-------------:|:---------------------:|:-----------------------| | `type` | integer | `UI_SET_` control type | | `type` | integer\|string | `UI_SET_` control type | | `data` | integer array | control values | | `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`. `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: `abs_info` fields are provided to set the device axes information. It is an array of below objects: | Field | Type | Description | | Field | Type | Description | |:-------------:|:-------------:|:-------------------------- | |:-------------:|:---------------:|:------------------------| | `code` | integer | Axis code | | `code` | integer\|string | Axis code or label | | `info` | object | Axis information object | | `info` | object | Axis information object | The axis information object is defined as below, with the fields having the same meaning as those 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, "pid": 0x2c42, "bus": "usb", "bus": "usb", "configuration":[ "configuration":[ {"type":100, "data":[1, 21]}, // UI_SET_EVBIT : EV_KEY and EV_FF {"type":"UI_SET_EVBIT", "data":["EV_KEY", "EV_FF"]}, {"type":101, "data":[11, 2, 3, 4]}, // UI_SET_KEYBIT : KEY_0 KEY_1 KEY_2 KEY_3 {"type":"UI_SET_KEYBIT", "data":["KEY_0", "KEY_1", "KEY_2", "KEY_3"]}, {"type":107, "data":[80]} // UI_SET_FFBIT : FF_RUMBLE {"type":"UI_SET_ABSBIT", "data":["ABS_Y", "ABS_WHEEL"]}, {"type":"UI_SET_FFBIT", "data":["FF_RUMBLE"]} ], ], "ff_effects_max" : 1, "ff_effects_max" : 1, "abs_info": [ "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} "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} "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 Send an array of uinput event packets to the uinput device | Field | Type | Description | | Field | Type | Description | |:-------------:|:-------------:|:-------------------------- | |:-------------:|:---------------------:|:-------------------------- | | `id` | integer | Device ID | | `id` | integer | Device ID | | `command` | string | Must be set to "inject" | | `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 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 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, "id": 1, "command": "inject", "command": "inject", "events": [0x01, 0xb, 0x1, // EV_KEY, KEY_0, DOWN "events": ["EV_KEY", "KEY_0", 1, 0x00, 0x00, 0x00, // EV_SYN, SYN_REPORT, 0 "EV_SYN", "SYN_REPORT", 0, 0x01, 0x0b, 0x00, // EV_KEY, KEY_0, UP "EV_KEY", "KEY_0", 0, 0x00, 0x00, 0x00, // EV_SYN, SYN_REPORT, 0 "EV_SYN", "SYN_REPORT", 0, 0x01, 0x2, 0x1, // EV_KEY, KEY_1, DOWN "EV_KEY", "KEY_1", 1, 0x00, 0x00, 0x01, // EV_SYN, SYN_REPORT, 0 "EV_SYN", "SYN_REPORT", 0, 0x01, 0x02, 0x00, // EV_KEY, KEY_1, UP "EV_KEY", "KEY_1", 0, 0x00, 0x00, 0x01 // EV_SYN, SYN_REPORT, 0 "EV_SYN", "SYN_REPORT", 0 ] ] } } ``` ``` Loading cmds/uinput/jni/Android.bp +1 −0 Original line number Original line Diff line number Diff line Loading @@ -21,6 +21,7 @@ cc_library_shared { "libbase", "libbase", "libbinder", "libbinder", "liblog", "liblog", "libinput", "libnativehelper", "libnativehelper", ], ], Loading cmds/uinput/jni/com_android_commands_uinput_Device.cpp +34 −14 Original line number Original line Diff line number Diff line Loading @@ -16,12 +16,24 @@ #define LOG_TAG "UinputCommandDevice" #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 <fcntl.h> #include <input/InputEventLabels.h> #include <inttypes.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 <time.h> #include <unistd.h> #include <unistd.h> #include <algorithm> #include <algorithm> #include <array> #include <array> #include <cstdio> #include <cstdio> Loading @@ -30,19 +42,6 @@ #include <memory> #include <memory> #include <vector> #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 android { namespace uinput { 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); ::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[] = { static JNINativeMethod sMethods[] = { {"nativeOpenUinputDevice", {"nativeOpenUinputDevice", "(Ljava/lang/String;IIIIILjava/lang/String;" "(Ljava/lang/String;IIIIILjava/lang/String;" Loading @@ -316,6 +330,12 @@ static JNINativeMethod sMethods[] = { {"nativeConfigure", "(II[I)V", reinterpret_cast<void*>(configure)}, {"nativeConfigure", "(II[I)V", reinterpret_cast<void*>(configure)}, {"nativeSetAbsInfo", "(IILandroid/os/Parcel;)V", reinterpret_cast<void*>(setAbsInfo)}, {"nativeSetAbsInfo", "(IILandroid/os/Parcel;)V", reinterpret_cast<void*>(setAbsInfo)}, {"nativeCloseUinputDevice", "(J)V", reinterpret_cast<void*>(closeUinputDevice)}, {"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) { int register_com_android_commands_uinput_Device(JNIEnv* env) { Loading cmds/uinput/src/com/android/commands/uinput/Device.java +31 −0 Original line number Original line 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 nativeInjectEvent(long ptr, int type, int code, int value); private static native void nativeConfigure(int handle, int code, int[] configs); 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 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, public Device(int id, String name, int vid, int pid, int bus, SparseArray<int[]> configuration, int ffEffectsMax, SparseArray<int[]> configuration, int ffEffectsMax, Loading Loading @@ -234,4 +237,32 @@ public class Device { msg.sendToTarget(); 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 Original line Diff line number Diff line Loading @@ -25,6 +25,9 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.ArrayList; import java.util.Arrays; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.function.Function; import java.util.stream.IntStream; import java.util.stream.IntStream; import src.com.android.commands.uinput.InputAbsInfo; 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_REGISTER = "register"; public static final String COMMAND_DELAY = "delay"; public static final String COMMAND_DELAY = "delay"; public static final String COMMAND_INJECT = "inject"; 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 // These constants come from "include/uapi/linux/input.h" in the kernel enum Bus { enum Bus { Loading Loading @@ -257,7 +288,7 @@ public class Event { eb.setBus(readBus()); eb.setBus(readBus()); break; break; case "events": case "events": int[] injections = readIntList().stream() int[] injections = readInjectedEvents().stream() .mapToInt(Integer::intValue).toArray(); .mapToInt(Integer::intValue).toArray(); eb.setInjections(injections); eb.setInjections(injections); break; break; Loading Loading @@ -293,12 +324,17 @@ public class Event { return e; return e; } } private ArrayList<Integer> readIntList() throws IOException { private ArrayList<Integer> readInjectedEvents() throws IOException { ArrayList<Integer> data = new ArrayList<Integer>(); ArrayList<Integer> data = new ArrayList<>(); try { try { mReader.beginArray(); mReader.beginArray(); while (mReader.hasNext()) { 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(); mReader.endArray(); } catch (IllegalStateException | NumberFormatException e) { } catch (IllegalStateException | NumberFormatException e) { Loading @@ -309,22 +345,32 @@ public class Event { return data; return data; } } private byte[] readData() throws IOException { private int readValueAsInt(Function<String, Integer> stringToInt) throws IOException { ArrayList<Integer> data = readIntList(); switch (mReader.peek()) { byte[] rawData = new byte[data.size()]; case NUMBER: { for (int i = 0; i < data.size(); i++) { return mReader.nextInt(); int d = data.get(i); } if ((d & 0xFF) != d) { case STRING: { throw new IllegalStateException("Invalid data, all values must be byte-sized"); 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 { private int readInt() throws IOException { String val = mReader.nextString(); return readValueAsInt((str) -> { return Integer.decode(val); throw new IllegalStateException("Encountered malformed data. Expected int."); }); } } private Bus readBus() throws IOException { private Bus readBus() throws IOException { Loading @@ -338,17 +384,20 @@ public class Event { try { try { mReader.beginArray(); mReader.beginArray(); while (mReader.hasNext()) { while (mReader.hasNext()) { int type = 0; UinputControlCode controlCode = null; IntStream data = null; IntStream data = null; mReader.beginObject(); mReader.beginObject(); while (mReader.hasNext()) { while (mReader.hasNext()) { String name = mReader.nextName(); String name = mReader.nextName(); switch (name) { switch (name) { case "type": case "type": type = readInt(); controlCode = readUinputControlCode(); break; break; case "data": 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; break; default: default: consumeRemainingElements(); consumeRemainingElements(); Loading @@ -358,9 +407,9 @@ public class Event { } } } } mReader.endObject(); mReader.endObject(); if (data != null) { if (controlCode != null && data != null) { final int[] existing = configuration.get(type); final int[] existing = configuration.get(controlCode.mValue); configuration.put(type, existing == null ? data.toArray() configuration.put(controlCode.mValue, existing == null ? data.toArray() : IntStream.concat(IntStream.of(existing), data).toArray()); : IntStream.concat(IntStream.of(existing), data).toArray()); } } } } Loading @@ -373,6 +422,60 @@ public class Event { return configuration; 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 { private InputAbsInfo readAbsInfo() throws IllegalStateException, IOException { InputAbsInfo absInfo = new InputAbsInfo(); InputAbsInfo absInfo = new InputAbsInfo(); try { try { Loading Loading @@ -426,7 +529,7 @@ public class Event { String name = mReader.nextName(); String name = mReader.nextName(); switch (name) { switch (name) { case "code": case "code": type = readInt(); type = readEvdevEventCode(EV_ABS); break; break; case "info": case "info": absInfo = readAbsInfo(); absInfo = readAbsInfo(); Loading @@ -452,6 +555,18 @@ public class Event { return infoArray; 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 { private void consumeRemainingElements() throws IOException { while (mReader.hasNext()) { while (mReader.hasNext()) { mReader.skipValue(); mReader.skipValue(); Loading Loading
cmds/uinput/README.md +32 −28 Original line number Original line Diff line number Diff line Loading @@ -59,21 +59,24 @@ multiple devices are registered. and `"bluetooth"`. and `"bluetooth"`. Device configuration is used to configure the uinput device. The `type` field provides a `UI_SET_*` 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 control code as an integer value or a string label (e.g. `"UI_SET_EVBIT"`), and data is a vector of on the control code. control values to be sent to the uinput device, which depends on the control code. | Field | Type | Description | | Field | Type | Description | |:-------------:|:-------------:|:-------------------------- | |:-------------:|:---------------------:|:-----------------------| | `type` | integer | `UI_SET_` control type | | `type` | integer\|string | `UI_SET_` control type | | `data` | integer array | control values | | `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`. `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: `abs_info` fields are provided to set the device axes information. It is an array of below objects: | Field | Type | Description | | Field | Type | Description | |:-------------:|:-------------:|:-------------------------- | |:-------------:|:---------------:|:------------------------| | `code` | integer | Axis code | | `code` | integer\|string | Axis code or label | | `info` | object | Axis information object | | `info` | object | Axis information object | The axis information object is defined as below, with the fields having the same meaning as those 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, "pid": 0x2c42, "bus": "usb", "bus": "usb", "configuration":[ "configuration":[ {"type":100, "data":[1, 21]}, // UI_SET_EVBIT : EV_KEY and EV_FF {"type":"UI_SET_EVBIT", "data":["EV_KEY", "EV_FF"]}, {"type":101, "data":[11, 2, 3, 4]}, // UI_SET_KEYBIT : KEY_0 KEY_1 KEY_2 KEY_3 {"type":"UI_SET_KEYBIT", "data":["KEY_0", "KEY_1", "KEY_2", "KEY_3"]}, {"type":107, "data":[80]} // UI_SET_FFBIT : FF_RUMBLE {"type":"UI_SET_ABSBIT", "data":["ABS_Y", "ABS_WHEEL"]}, {"type":"UI_SET_FFBIT", "data":["FF_RUMBLE"]} ], ], "ff_effects_max" : 1, "ff_effects_max" : 1, "abs_info": [ "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} "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} "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 Send an array of uinput event packets to the uinput device | Field | Type | Description | | Field | Type | Description | |:-------------:|:-------------:|:-------------------------- | |:-------------:|:---------------------:|:-------------------------- | | `id` | integer | Device ID | | `id` | integer | Device ID | | `command` | string | Must be set to "inject" | | `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 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 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, "id": 1, "command": "inject", "command": "inject", "events": [0x01, 0xb, 0x1, // EV_KEY, KEY_0, DOWN "events": ["EV_KEY", "KEY_0", 1, 0x00, 0x00, 0x00, // EV_SYN, SYN_REPORT, 0 "EV_SYN", "SYN_REPORT", 0, 0x01, 0x0b, 0x00, // EV_KEY, KEY_0, UP "EV_KEY", "KEY_0", 0, 0x00, 0x00, 0x00, // EV_SYN, SYN_REPORT, 0 "EV_SYN", "SYN_REPORT", 0, 0x01, 0x2, 0x1, // EV_KEY, KEY_1, DOWN "EV_KEY", "KEY_1", 1, 0x00, 0x00, 0x01, // EV_SYN, SYN_REPORT, 0 "EV_SYN", "SYN_REPORT", 0, 0x01, 0x02, 0x00, // EV_KEY, KEY_1, UP "EV_KEY", "KEY_1", 0, 0x00, 0x00, 0x01 // EV_SYN, SYN_REPORT, 0 "EV_SYN", "SYN_REPORT", 0 ] ] } } ``` ``` Loading
cmds/uinput/jni/Android.bp +1 −0 Original line number Original line Diff line number Diff line Loading @@ -21,6 +21,7 @@ cc_library_shared { "libbase", "libbase", "libbinder", "libbinder", "liblog", "liblog", "libinput", "libnativehelper", "libnativehelper", ], ], Loading
cmds/uinput/jni/com_android_commands_uinput_Device.cpp +34 −14 Original line number Original line Diff line number Diff line Loading @@ -16,12 +16,24 @@ #define LOG_TAG "UinputCommandDevice" #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 <fcntl.h> #include <input/InputEventLabels.h> #include <inttypes.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 <time.h> #include <unistd.h> #include <unistd.h> #include <algorithm> #include <algorithm> #include <array> #include <array> #include <cstdio> #include <cstdio> Loading @@ -30,19 +42,6 @@ #include <memory> #include <memory> #include <vector> #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 android { namespace uinput { 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); ::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[] = { static JNINativeMethod sMethods[] = { {"nativeOpenUinputDevice", {"nativeOpenUinputDevice", "(Ljava/lang/String;IIIIILjava/lang/String;" "(Ljava/lang/String;IIIIILjava/lang/String;" Loading @@ -316,6 +330,12 @@ static JNINativeMethod sMethods[] = { {"nativeConfigure", "(II[I)V", reinterpret_cast<void*>(configure)}, {"nativeConfigure", "(II[I)V", reinterpret_cast<void*>(configure)}, {"nativeSetAbsInfo", "(IILandroid/os/Parcel;)V", reinterpret_cast<void*>(setAbsInfo)}, {"nativeSetAbsInfo", "(IILandroid/os/Parcel;)V", reinterpret_cast<void*>(setAbsInfo)}, {"nativeCloseUinputDevice", "(J)V", reinterpret_cast<void*>(closeUinputDevice)}, {"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) { int register_com_android_commands_uinput_Device(JNIEnv* env) { Loading
cmds/uinput/src/com/android/commands/uinput/Device.java +31 −0 Original line number Original line 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 nativeInjectEvent(long ptr, int type, int code, int value); private static native void nativeConfigure(int handle, int code, int[] configs); 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 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, public Device(int id, String name, int vid, int pid, int bus, SparseArray<int[]> configuration, int ffEffectsMax, SparseArray<int[]> configuration, int ffEffectsMax, Loading Loading @@ -234,4 +237,32 @@ public class Device { msg.sendToTarget(); 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 Original line Diff line number Diff line Loading @@ -25,6 +25,9 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.ArrayList; import java.util.Arrays; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.function.Function; import java.util.stream.IntStream; import java.util.stream.IntStream; import src.com.android.commands.uinput.InputAbsInfo; 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_REGISTER = "register"; public static final String COMMAND_DELAY = "delay"; public static final String COMMAND_DELAY = "delay"; public static final String COMMAND_INJECT = "inject"; 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 // These constants come from "include/uapi/linux/input.h" in the kernel enum Bus { enum Bus { Loading Loading @@ -257,7 +288,7 @@ public class Event { eb.setBus(readBus()); eb.setBus(readBus()); break; break; case "events": case "events": int[] injections = readIntList().stream() int[] injections = readInjectedEvents().stream() .mapToInt(Integer::intValue).toArray(); .mapToInt(Integer::intValue).toArray(); eb.setInjections(injections); eb.setInjections(injections); break; break; Loading Loading @@ -293,12 +324,17 @@ public class Event { return e; return e; } } private ArrayList<Integer> readIntList() throws IOException { private ArrayList<Integer> readInjectedEvents() throws IOException { ArrayList<Integer> data = new ArrayList<Integer>(); ArrayList<Integer> data = new ArrayList<>(); try { try { mReader.beginArray(); mReader.beginArray(); while (mReader.hasNext()) { 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(); mReader.endArray(); } catch (IllegalStateException | NumberFormatException e) { } catch (IllegalStateException | NumberFormatException e) { Loading @@ -309,22 +345,32 @@ public class Event { return data; return data; } } private byte[] readData() throws IOException { private int readValueAsInt(Function<String, Integer> stringToInt) throws IOException { ArrayList<Integer> data = readIntList(); switch (mReader.peek()) { byte[] rawData = new byte[data.size()]; case NUMBER: { for (int i = 0; i < data.size(); i++) { return mReader.nextInt(); int d = data.get(i); } if ((d & 0xFF) != d) { case STRING: { throw new IllegalStateException("Invalid data, all values must be byte-sized"); 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 { private int readInt() throws IOException { String val = mReader.nextString(); return readValueAsInt((str) -> { return Integer.decode(val); throw new IllegalStateException("Encountered malformed data. Expected int."); }); } } private Bus readBus() throws IOException { private Bus readBus() throws IOException { Loading @@ -338,17 +384,20 @@ public class Event { try { try { mReader.beginArray(); mReader.beginArray(); while (mReader.hasNext()) { while (mReader.hasNext()) { int type = 0; UinputControlCode controlCode = null; IntStream data = null; IntStream data = null; mReader.beginObject(); mReader.beginObject(); while (mReader.hasNext()) { while (mReader.hasNext()) { String name = mReader.nextName(); String name = mReader.nextName(); switch (name) { switch (name) { case "type": case "type": type = readInt(); controlCode = readUinputControlCode(); break; break; case "data": 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; break; default: default: consumeRemainingElements(); consumeRemainingElements(); Loading @@ -358,9 +407,9 @@ public class Event { } } } } mReader.endObject(); mReader.endObject(); if (data != null) { if (controlCode != null && data != null) { final int[] existing = configuration.get(type); final int[] existing = configuration.get(controlCode.mValue); configuration.put(type, existing == null ? data.toArray() configuration.put(controlCode.mValue, existing == null ? data.toArray() : IntStream.concat(IntStream.of(existing), data).toArray()); : IntStream.concat(IntStream.of(existing), data).toArray()); } } } } Loading @@ -373,6 +422,60 @@ public class Event { return configuration; 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 { private InputAbsInfo readAbsInfo() throws IllegalStateException, IOException { InputAbsInfo absInfo = new InputAbsInfo(); InputAbsInfo absInfo = new InputAbsInfo(); try { try { Loading Loading @@ -426,7 +529,7 @@ public class Event { String name = mReader.nextName(); String name = mReader.nextName(); switch (name) { switch (name) { case "code": case "code": type = readInt(); type = readEvdevEventCode(EV_ABS); break; break; case "info": case "info": absInfo = readAbsInfo(); absInfo = readAbsInfo(); Loading @@ -452,6 +555,18 @@ public class Event { return infoArray; 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 { private void consumeRemainingElements() throws IOException { while (mReader.hasNext()) { while (mReader.hasNext()) { mReader.skipValue(); mReader.skipValue(); Loading