uinput: move Event.Reader to its own file

We'll shortly be adding a parser for a different file format (evemu),
and having the different parsers in separate files will make the code
nicer to navigate.

In addition to moving it to a separate file, this CL renames Reader to
JsonStyleParser to avoid confusion with Java's Reader classes, and
replaces some switch statements with the new-style switch syntax to
improve readability.

Bug: 302297266
Test: atest CtsInputHostTestCases
Change-Id: Id6c7536a2caea3b61fac615dce071129bf4dc1e4
diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java
index 9d8f1f4..02269f8 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Event.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Event.java
@@ -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;
 
@@ -52,16 +43,18 @@
         }
     }
 
-    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;
+    // 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;
 
-    private enum UinputControlCode {
+    public enum UinputControlCode {
         UI_SET_EVBIT("UI_SET_EVBIT", 100),
         UI_SET_KEYBIT("UI_SET_KEYBIT", 101),
         UI_SET_RELBIT("UI_SET_RELBIT", 102),
@@ -73,13 +66,21 @@
         UI_SET_SWBIT("UI_SET_SWBIT", 109),
         UI_SET_PROPBIT("UI_SET_PROPBIT", 110);
 
-        final String mName;
-        final int mValue;
+        private final String mName;
+        private final int mValue;
 
         UinputControlCode(String name, int value) {
             this.mName = name;
             this.mValue = value;
         }
+
+        public String getName() {
+            return mName;
+        }
+
+        public int getValue() {
+            return mValue;
+        }
     }
 
     // These constants come from "include/uapi/linux/input.h" in the kernel
@@ -180,7 +181,7 @@
             + "}";
     }
 
-    private static class Builder {
+    public static class Builder {
         private Event mEvent;
 
         Builder() {
@@ -191,7 +192,7 @@
             mEvent.mId = id;
         }
 
-        private void setCommand(String command) {
+        public void setCommand(String command) {
             Objects.requireNonNull(command, "Command must not be null");
             for (Command cmd : Command.values()) {
                 if (cmd.mCommandName.equals(command)) {
@@ -278,343 +279,4 @@
             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));
-        }
-    }
 }
diff --git a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
new file mode 100644
index 0000000..d8e0105
--- /dev/null
+++ b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.IntStream;
+
+import src.com.android.commands.uinput.InputAbsInfo;
+
+/**
+ * A class that parses the JSON-like event format described in the README to build {@link Event}s.
+ */
+public class JsonStyleParser {
+    private static final String TAG = "UinputJsonStyleParser";
+
+    private JsonReader mReader;
+
+    public JsonStyleParser(InputStreamReader in) {
+        mReader = new JsonReader(in);
+        mReader.setLenient(true);
+    }
+
+    /**
+     * Gets the next event entry from the JSON file.
+     */
+    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());
+                        case "command" -> eb.setCommand(mReader.nextString());
+                        case "name" -> eb.setName(mReader.nextString());
+                        case "vid" -> eb.setVid(readInt());
+                        case "pid" -> eb.setPid(readInt());
+                        case "bus" -> eb.setBus(readBus());
+                        case "events" -> {
+                            int[] injections = readInjectedEvents().stream()
+                                    .mapToInt(Integer::intValue).toArray();
+                            eb.setInjections(injections);
+                        }
+                        case "configuration" -> eb.setConfiguration(readConfiguration());
+                        case "ff_effects_max" -> eb.setFfEffectsMax(readInt());
+                        case "abs_info" -> eb.setAbsInfo(readAbsInfoArray());
+                        case "duration" -> eb.setDuration(readInt());
+                        case "port" -> eb.setInputport(mReader.nextString());
+                        case "syncToken" -> eb.setSyncToken(mReader.nextString());
+                        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 Event.Bus readBus() throws IOException {
+        String val = mReader.nextString();
+        return Event.Bus.valueOf(val.toUpperCase());
+    }
+
+    private SparseArray<int[]> readConfiguration()
+            throws IllegalStateException, IOException {
+        SparseArray<int[]> configuration = new SparseArray<>();
+        try {
+            mReader.beginArray();
+            while (mReader.hasNext()) {
+                Event.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.getValue());
+                    configuration.put(controlCode.getValue(), 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 Event.UinputControlCode readUinputControlCode() throws IOException {
+        var code = readValueAsInt((controlTypeStr) -> {
+            for (Event.UinputControlCode controlCode : Event.UinputControlCode.values()) {
+                if (controlCode.getName().equals(controlTypeStr)) {
+                    return controlCode.getValue();
+                }
+            }
+            return -1;
+        });
+        for (Event.UinputControlCode controlCode : Event.UinputControlCode.values()) {
+            if (controlCode.getValue() == code) {
+                return controlCode;
+            }
+        }
+        return null;
+    }
+
+    private List<Integer> readDataForControlCode(
+            Event.UinputControlCode controlCode) throws IOException {
+        return switch (controlCode) {
+            case UI_SET_EVBIT -> readArrayAsInts(this::readEvdevEventType);
+            case UI_SET_KEYBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_KEY));
+            case UI_SET_RELBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_REL));
+            case UI_SET_ABSBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_ABS));
+            case UI_SET_MSCBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_MSC));
+            case UI_SET_LEDBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_LED));
+            case UI_SET_SNDBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_SND));
+            case UI_SET_FFBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_FF));
+            case UI_SET_SWBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.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();
+                    case "minimum" -> absInfo.minimum = readInt();
+                    case "maximum" -> absInfo.maximum = readInt();
+                    case "fuzz" -> absInfo.fuzz = readInt();
+                    case "flat" -> absInfo.flat = readInt();
+                    case "resolution" -> absInfo.resolution = readInt();
+                    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(Event.EV_ABS);
+                        case "info" -> absInfo = readAbsInfo();
+                        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));
+        }
+    }
+}
diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
index 47b7a354..16342ef 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
@@ -35,7 +35,7 @@
 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 @@
     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 @@
     private void run() {
         try {
             Event e = null;
-            while ((e = mReader.getNextEvent()) != null) {
+            while ((e = mParser.getNextEvent()) != null) {
                 process(e);
             }
         } catch (IOException ex) {