Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fc3d4eca authored by Prabir Pradhan's avatar Prabir Pradhan Committed by Android (Google) Code Review
Browse files

Merge "Uinput shell command: Support string literals for evdev event codes" into main

parents d0b05ade 32167af0
Loading
Loading
Loading
Loading
+32 −28
Original line number Diff line number Diff line
@@ -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
@@ -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}
        }
  ]
@@ -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
@@ -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
            ]
}
```
+1 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ cc_library_shared {
        "libbase",
        "libbinder",
        "liblog",
        "libinput",
        "libnativehelper",
    ],

+34 −14
Original line number Diff line number Diff line
@@ -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) {
+31 −0
Original line number Diff line number Diff line
@@ -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;
    }
}
+139 −24
Original line number Diff line number Diff line
@@ -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,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;
@@ -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();