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

Commit ffa80d0a authored by Harry Cutts's avatar Harry Cutts Committed by Android (Google) Code Review
Browse files

Merge changes I9b9ae897,Iad98088b,Id6c7536a into main

* changes:
  uinput: improve Event.Builder API
  uinput: use enum valueOf method
  uinput: move Event.Reader to its own file
parents 5cfe5d02 822e9c80
Loading
Loading
Loading
Loading
+62 −405
Original line number Diff line number Diff line
@@ -16,19 +16,10 @@

package com.android.commands.uinput;

import android.util.JsonReader;
import android.util.JsonToken;
import android.util.Log;
import android.util.SparseArray;

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;

@@ -40,46 +31,44 @@ public class Event {
    private static final String TAG = "UinputEvent";

    enum Command {
        REGISTER("register"),
        DELAY("delay"),
        INJECT("inject"),
        SYNC("sync");

        final String mCommandName;

        Command(String command) {
            mCommandName = command;
        }
    }

    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;
        REGISTER,
        DELAY,
        INJECT,
        SYNC,
    }

    // Constants representing evdev event types, from include/uapi/linux/input-event-codes.h in the
    // kernel.
    public static final int EV_KEY = 0x01;
    public static final int EV_REL = 0x02;
    public static final int EV_ABS = 0x03;
    public static final int EV_MSC = 0x04;
    public static final int EV_SW = 0x05;
    public static final int EV_LED = 0x11;
    public static final int EV_SND = 0x12;
    public static final int EV_FF = 0x15;

    public enum UinputControlCode {
        UI_SET_EVBIT(100),
        UI_SET_KEYBIT(101),
        UI_SET_RELBIT(102),
        UI_SET_ABSBIT(103),
        UI_SET_MSCBIT(104),
        UI_SET_LEDBIT(105),
        UI_SET_SNDBIT(106),
        UI_SET_FFBIT(107),
        UI_SET_SWBIT(109),
        UI_SET_PROPBIT(110);

        private final int mValue;

        UinputControlCode(int value) {
            this.mValue = value;
        }

        public int getValue() {
            return mValue;
        }
    }

    // These constants come from "include/uapi/linux/input.h" in the kernel
@@ -104,9 +93,9 @@ public class Event {
    private Bus mBus;
    private int[] mInjections;
    private SparseArray<int[]> mConfiguration;
    private int mDuration;
    private int mDurationMillis;
    private int mFfEffectsMax = 0;
    private String mInputport;
    private String mInputPort;
    private SparseArray<InputAbsInfo> mAbsInfo;
    private String mSyncToken;

@@ -138,12 +127,20 @@ public class Event {
        return mInjections;
    }

    /**
     * Returns a {@link SparseArray} describing the event codes that should be registered for the
     * device. The keys are uinput ioctl codes (such as those returned from {@link
     * UinputControlCode#getValue()}, while the values are arrays of event codes to be enabled with
     * those ioctls. For example, key 101 (corresponding to {@link UinputControlCode#UI_SET_KEYBIT})
     * could have values 0x110 ({@code BTN_LEFT}, 0x111 ({@code BTN_RIGHT}), and 0x112
     * ({@code BTN_MIDDLE}).
     */
    public SparseArray<int[]> getConfiguration() {
        return mConfiguration;
    }

    public int getDuration() {
        return mDuration;
    public int getDurationMillis() {
        return mDurationMillis;
    }

    public int getFfEffectsMax() {
@@ -155,7 +152,7 @@ public class Event {
    }

    public String getPort() {
        return mInputport;
        return mInputPort;
    }

    public String getSyncToken() {
@@ -174,13 +171,13 @@ public class Event {
            + ", bus=" + mBus
            + ", events=" + Arrays.toString(mInjections)
            + ", configuration=" + mConfiguration
            + ", duration=" + mDuration
            + ", duration=" + mDurationMillis + "ms"
            + ", ff_effects_max=" + mFfEffectsMax
            + ", port=" + mInputport
            + ", port=" + mInputPort
            + "}";
    }

    private static class Builder {
    public static class Builder {
        private Event mEvent;

        Builder() {
@@ -191,15 +188,8 @@ public class Event {
            mEvent.mId = id;
        }

        private void setCommand(String command) {
            Objects.requireNonNull(command, "Command must not be null");
            for (Command cmd : Command.values()) {
                if (cmd.mCommandName.equals(command)) {
                    mEvent.mCommand = cmd;
                    return;
                }
            }
            throw new IllegalStateException("Unrecognized command: " + command);
        public void setCommand(String command) {
            mEvent.mCommand = Command.valueOf(command.toUpperCase());
        }

        public void setName(String name) {
@@ -210,6 +200,12 @@ public class Event {
            mEvent.mInjections = events;
        }

        /**
         * Sets the event codes that should be registered with a {@code register} command.
         *
         * @param configuration An array of ioctls and event codes, as described at
         *                      {@link Event#getConfiguration()}.
         */
        public void setConfiguration(SparseArray<int[]> configuration) {
            mEvent.mConfiguration = configuration;
        }
@@ -226,8 +222,8 @@ public class Event {
            mEvent.mBus = bus;
        }

        public void setDuration(int duration) {
            mEvent.mDuration = duration;
        public void setDurationMillis(int durationMillis) {
            mEvent.mDurationMillis = durationMillis;
        }

        public void setFfEffectsMax(int ffEffectsMax) {
@@ -238,8 +234,8 @@ public class Event {
            mEvent.mAbsInfo = absInfo;
        }

        public void setInputport(String port) {
            mEvent.mInputport = port;
        public void setInputPort(String port) {
            mEvent.mInputPort = port;
        }

        public void setSyncToken(String syncToken) {
@@ -260,7 +256,7 @@ public class Event {
                    }
                }
                case DELAY -> {
                    if (mEvent.mDuration <= 0) {
                    if (mEvent.mDurationMillis <= 0) {
                        throw new IllegalStateException("Delay has missing or invalid duration");
                    }
                }
@@ -278,343 +274,4 @@ public class Event {
            return mEvent;
        }
    }

    /**
     *  A class that parses the JSON event format from an input stream to build device events.
     */
    public static class Reader {
        private JsonReader mReader;

        public Reader(InputStreamReader in) {
            mReader = new JsonReader(in);
            mReader.setLenient(true);
        }

        /**
         * Get next event entry from JSON file reader.
         */
        public Event getNextEvent() throws IOException {
            Event e = null;
            while (e == null && mReader.peek() != JsonToken.END_DOCUMENT) {
                Event.Builder eb = new Event.Builder();
                try {
                    mReader.beginObject();
                    while (mReader.hasNext()) {
                        String name = mReader.nextName();
                        switch (name) {
                            case "id":
                                eb.setId(readInt());
                                break;
                            case "command":
                                eb.setCommand(mReader.nextString());
                                break;
                            case "name":
                                eb.setName(mReader.nextString());
                                break;
                            case "vid":
                                eb.setVid(readInt());
                                break;
                            case "pid":
                                eb.setPid(readInt());
                                break;
                            case "bus":
                                eb.setBus(readBus());
                                break;
                            case "events":
                                int[] injections = readInjectedEvents().stream()
                                        .mapToInt(Integer::intValue).toArray();
                                eb.setInjections(injections);
                                break;
                            case "configuration":
                                eb.setConfiguration(readConfiguration());
                                break;
                            case "ff_effects_max":
                                eb.setFfEffectsMax(readInt());
                                break;
                            case "abs_info":
                                eb.setAbsInfo(readAbsInfoArray());
                                break;
                            case "duration":
                                eb.setDuration(readInt());
                                break;
                            case "port":
                                eb.setInputport(mReader.nextString());
                                break;
                            case "syncToken":
                                eb.setSyncToken(mReader.nextString());
                                break;
                            default:
                                mReader.skipValue();
                        }
                    }
                    mReader.endObject();
                } catch (IllegalStateException ex) {
                    error("Error reading in object, ignoring.", ex);
                    consumeRemainingElements();
                    mReader.endObject();
                    continue;
                }
                e = eb.build();
            }

            return e;
        }

        private ArrayList<Integer> readInjectedEvents() throws IOException {
            ArrayList<Integer> data = new ArrayList<>();
            try {
                mReader.beginArray();
                while (mReader.hasNext()) {
                    // 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) {
                consumeRemainingElements();
                mReader.endArray();
                throw new IllegalStateException("Encountered malformed data.", e);
            }
            return data;
        }

        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.");
                }
            }
        }

        private int readInt() throws IOException {
            return readValueAsInt((str) -> {
                throw new IllegalStateException("Encountered malformed data. Expected int.");
            });
        }

        private Bus readBus() throws IOException {
            String val = mReader.nextString();
            return Bus.valueOf(val.toUpperCase());
        }

        private SparseArray<int[]> readConfiguration()
                throws IllegalStateException, IOException {
            SparseArray<int[]> configuration = new SparseArray<>();
            try {
                mReader.beginArray();
                while (mReader.hasNext()) {
                    UinputControlCode controlCode = null;
                    IntStream data = null;
                    mReader.beginObject();
                    while (mReader.hasNext()) {
                        String name = mReader.nextName();
                        switch (name) {
                            case "type":
                                controlCode = readUinputControlCode();
                                break;
                            case "data":
                                Objects.requireNonNull(controlCode,
                                        "Configuration 'type' must be specified before 'data'.");
                                data = readDataForControlCode(controlCode)
                                        .stream().mapToInt(Integer::intValue);
                                break;
                            default:
                                consumeRemainingElements();
                                mReader.endObject();
                                throw new IllegalStateException(
                                        "Invalid key in device configuration: " + name);
                        }
                    }
                    mReader.endObject();
                    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());
                    }
                }
                mReader.endArray();
            } catch (IllegalStateException | NumberFormatException e) {
                consumeRemainingElements();
                mReader.endArray();
                throw new IllegalStateException("Encountered malformed data.", e);
            }
            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 {
                mReader.beginObject();
                while (mReader.hasNext()) {
                    String name = mReader.nextName();
                    switch (name) {
                        case "value":
                            absInfo.value = readInt();
                            break;
                        case "minimum":
                            absInfo.minimum = readInt();
                            break;
                        case "maximum":
                            absInfo.maximum = readInt();
                            break;
                        case "fuzz":
                            absInfo.fuzz = readInt();
                            break;
                        case "flat":
                            absInfo.flat = readInt();
                            break;
                        case "resolution":
                            absInfo.resolution = readInt();
                            break;
                        default:
                            consumeRemainingElements();
                            mReader.endObject();
                            throw new IllegalStateException("Invalid key in abs info: " + name);
                    }
                }
                mReader.endObject();
            } catch (IllegalStateException | NumberFormatException e) {
                consumeRemainingElements();
                mReader.endObject();
                throw new IllegalStateException("Encountered malformed data.", e);
            }
            return absInfo;
        }

        private SparseArray<InputAbsInfo> readAbsInfoArray()
                throws IllegalStateException, IOException {
            SparseArray<InputAbsInfo> infoArray = new SparseArray<>();
            try {
                mReader.beginArray();
                while (mReader.hasNext()) {
                    int type = 0;
                    InputAbsInfo absInfo = null;
                    mReader.beginObject();
                    while (mReader.hasNext()) {
                        String name = mReader.nextName();
                        switch (name) {
                            case "code":
                                type = readEvdevEventCode(EV_ABS);
                                break;
                            case "info":
                                absInfo = readAbsInfo();
                                break;
                            default:
                                consumeRemainingElements();
                                mReader.endObject();
                                throw new IllegalStateException("Invalid key in abs info array: "
                                        + name);
                        }
                    }
                    mReader.endObject();
                    if (absInfo != null) {
                        infoArray.put(type, absInfo);
                    }
                }
                mReader.endArray();
            } catch (IllegalStateException | NumberFormatException e) {
                consumeRemainingElements();
                mReader.endArray();
                throw new IllegalStateException("Encountered malformed data.", e);
            }
            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();
            }
        }
    }

    private static void error(String msg, Exception e) {
        System.out.println(msg);
        Log.e(TAG, msg);
        if (e != null) {
            Log.e(TAG, Log.getStackTraceString(e));
        }
    }
}
+333 −0

File added.

Preview size limit exceeded, changes collapsed.

+4 −4
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ import java.util.Objects;
public class Uinput {
    private static final String TAG = "UINPUT";

    private final Event.Reader mReader;
    private final JsonStyleParser mParser;
    private final SparseArray<Device> mDevices;

    private static void usage() {
@@ -74,7 +74,7 @@ public class Uinput {
    private Uinput(InputStream in) {
        mDevices = new SparseArray<Device>();
        try {
            mReader = new Event.Reader(new InputStreamReader(in, "UTF-8"));
            mParser = new JsonStyleParser(new InputStreamReader(in, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
@@ -83,7 +83,7 @@ public class Uinput {
    private void run() {
        try {
            Event e = null;
            while ((e = mReader.getNextEvent()) != null) {
            while ((e = mParser.getNextEvent()) != null) {
                process(e);
            }
        } catch (IOException ex) {
@@ -111,7 +111,7 @@ public class Uinput {
            case REGISTER ->
                    error("Device id=" + e.getId() + " is already registered. Ignoring event.");
            case INJECT -> d.injectEvent(e.getInjections());
            case DELAY -> d.addDelay(e.getDuration());
            case DELAY -> d.addDelay(e.getDurationMillis());
            case SYNC -> d.syncEvent(e.getSyncToken());
        }
    }