summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmds/uinput/README.md60
-rw-r--r--cmds/uinput/jni/Android.bp1
-rw-r--r--cmds/uinput/jni/com_android_commands_uinput_Device.cpp48
-rw-r--r--cmds/uinput/src/com/android/commands/uinput/Device.java31
-rw-r--r--cmds/uinput/src/com/android/commands/uinput/Event.java163
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();