uinput: add unit tests for evemu parsing

Bug: 302297266
Test: atest UinputTests
Change-Id: I7b4c6fa87f6ba50147f3ddeb9b8e4ceaa238ffc1
diff --git a/cmds/uinput/Android.bp b/cmds/uinput/Android.bp
index 4b08d96..da497dc 100644
--- a/cmds/uinput/Android.bp
+++ b/cmds/uinput/Android.bp
@@ -22,7 +22,7 @@
     name: "uinput",
     wrapper: "uinput.sh",
     srcs: [
-        "**/*.java",
+        "src/**/*.java",
         ":uinputcommand_aidl",
     ],
     required: ["libuinputcommand_jni"],
diff --git a/cmds/uinput/TEST_MAPPING b/cmds/uinput/TEST_MAPPING
new file mode 100644
index 0000000..e7d619c
--- /dev/null
+++ b/cmds/uinput/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "imports": [
+    {
+      "path": "frameworks/native/services/inputflinger"
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "UinputTests"
+    }
+  ]
+}
diff --git a/cmds/uinput/tests/Android.bp b/cmds/uinput/tests/Android.bp
new file mode 100644
index 0000000..e728bd2
--- /dev/null
+++ b/cmds/uinput/tests/Android.bp
@@ -0,0 +1,20 @@
+package {
+    default_applicable_licenses: ["frameworks_base_cmds_uinput_license"],
+}
+
+android_test {
+    name: "UinputTests",
+    srcs: [
+        "src/**/*.java",
+    ],
+    static_libs: [
+        "androidx.test.runner",
+        "frameworks-base-testutils",
+        "platform-test-annotations",
+        "truth",
+        "uinput",
+    ],
+    test_suites: [
+        "device-tests",
+    ],
+}
diff --git a/cmds/uinput/tests/AndroidManifest.xml b/cmds/uinput/tests/AndroidManifest.xml
new file mode 100644
index 0000000..c364c1c
--- /dev/null
+++ b/cmds/uinput/tests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.commands.uinput.tests">
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="Uinput Tests"
+        android:targetPackage="com.android.commands.uinput.tests" />
+</manifest>
diff --git a/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java b/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java
new file mode 100644
index 0000000..6ee987f
--- /dev/null
+++ b/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java
@@ -0,0 +1,476 @@
+/*
+ * 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.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.platform.test.annotations.Postsubmit;
+import android.util.SparseArray;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.commands.uinput.EvemuParser;
+import com.android.commands.uinput.Event;
+import com.android.commands.uinput.Event.UinputControlCode;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import src.com.android.commands.uinput.InputAbsInfo;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Postsubmit
+public class EvemuParserTest {
+
+    private Event getRegistrationEvent(String fileContents) throws IOException {
+        StringReader reader = new StringReader(fileContents);
+        EvemuParser parser = new EvemuParser(reader);
+        Event event = parser.getNextEvent();
+        assertThat(event.getCommand()).isEqualTo(Event.Command.REGISTER);
+        return event;
+    }
+
+    @Test
+    public void testNameParsing() throws IOException {
+        Event event = getRegistrationEvent("""
+                N: ACME Pointing Widget #4
+                I: 0001 1234 5678 9abc
+                """);
+        assertThat(event.getName()).isEqualTo("ACME Pointing Widget #4");
+    }
+
+    @Test
+    public void testIdParsing() throws IOException {
+        Event event = getRegistrationEvent("""
+                N: ACME Pointing Widget #4
+                I: 0001 1234 5678 9abc
+                """);
+        assertThat(event.getBus()).isEqualTo(0x0001);
+        assertThat(event.getVendorId()).isEqualTo(0x1234);
+        assertThat(event.getProductId()).isEqualTo(0x5678);
+        // TODO(b/302297266): check version ID once it's supported
+    }
+
+    @Test
+    public void testPropertyBitmapParsing() throws IOException {
+        Event event = getRegistrationEvent("""
+                N: ACME Pointing Widget #4
+                I: 0001 1234 5678 9abc
+                P: 05 00 00 00 00 00 00 00
+                P: 01
+                """);
+        assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_PROPBIT.getValue()))
+                .asList().containsExactly(0, 2, 64);
+    }
+
+    @Test
+    public void testEventBitmapParsing() throws IOException {
+        Event event = getRegistrationEvent("""
+                N: ACME Pointing Widget #4
+                I: 0001 1234 5678 9abc
+                B: 00 0b 00 00 00 00 00 00 00  # SYN
+                B: 01 00 00 03 00 00 00 00 00  # KEY
+                B: 01 00 01 00 00 00 00 00 00
+                B: 02 03 00 00 00 00 00 00 00  # REL
+                B: 03 00 00                    # ABS
+                """);
+        assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_EVBIT.getValue()))
+                .asList().containsExactly(Event.EV_KEY, Event.EV_REL);
+        assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_KEYBIT.getValue()))
+                .asList().containsExactly(16, 17, 72);
+        assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_RELBIT.getValue()))
+                .asList().containsExactly(0, 1);
+        assertThat(event.getConfiguration().contains(UinputControlCode.UI_SET_ABSBIT.getValue()))
+                .isFalse();
+    }
+
+    @Test
+    public void testEventBitmapParsing_WithForceFeedback() throws IOException {
+        Event event = getRegistrationEvent("""
+                N: ACME Pointing Widget #4
+                I: 0001 1234 5678 9abc
+                B: 15 05  # FF
+                """);
+        assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_EVBIT.getValue()))
+                .asList().containsExactly(Event.EV_FF);
+        assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_FFBIT.getValue()))
+                .asList().containsExactly(0, 2);
+        assertThat(event.getFfEffectsMax()).isEqualTo(2);
+    }
+
+    private void assertAbsInfo(InputAbsInfo info, int minimum, int maximum, int fuzz, int flat,
+                               int resolution) {
+        assertThat(info).isNotNull();
+        assertWithMessage("Incorrect minimum").that(info.minimum).isEqualTo(minimum);
+        assertWithMessage("Incorrect maximum").that(info.maximum).isEqualTo(maximum);
+        assertWithMessage("Incorrect fuzz").that(info.fuzz).isEqualTo(fuzz);
+        assertWithMessage("Incorrect flat").that(info.flat).isEqualTo(flat);
+        assertWithMessage("Incorrect resolution").that(info.resolution).isEqualTo(resolution);
+    }
+
+    @Test
+    public void testAbsInfoParsing_WithResolution() throws IOException {
+        Event event = getRegistrationEvent("""
+                N: ACME Weird Gamepad
+                I: 0001 1234 5678 9abc
+                A: 03 -128 128 4 4 0    # ABS_MT_RX
+                A: 2f 0 9 0 0 0         # ABS_MT_SLOT
+                A: 34 -4096 4096 0 0 0  # ABS_MT_ORIENTATION
+                A: 35 0 1599 0 0 11     # ABS_MT_POSITION_X
+                """);
+        SparseArray<InputAbsInfo> absInfos = event.getAbsInfo();
+        assertThat(absInfos.size()).isEqualTo(4);
+        assertAbsInfo(absInfos.get(0x03), -128, 128, 4, 4, 0);
+        assertAbsInfo(absInfos.get(0x2f), 0, 9, 0, 0, 0);
+        assertAbsInfo(absInfos.get(0x34), -4096, 4096, 0, 0, 0);
+        assertAbsInfo(absInfos.get(0x35), 0, 1599, 0, 0, 11);
+    }
+
+    @Test
+    public void testAbsInfoParsing_WithoutResolution() throws IOException {
+        Event event = getRegistrationEvent("""
+                N: ACME Terrible Touchscreen
+                I: 0001 1234 5678 9abc
+                A: 2f 0 9 0 0         # ABS_MT_SLOT
+                A: 35 0 1599 0 0      # ABS_MT_POSITION_X
+                A: 36 0 2559 0 0      # ABS_MT_POSITION_X
+                """);
+        SparseArray<InputAbsInfo> absInfos = event.getAbsInfo();
+        assertThat(absInfos.size()).isEqualTo(3);
+        assertAbsInfo(absInfos.get(0x2f), 0, 9, 0, 0, 0);
+        assertAbsInfo(absInfos.get(0x35), 0, 1599, 0, 0, 0);
+        assertAbsInfo(absInfos.get(0x36), 0, 2559, 0, 0, 0);
+    }
+
+    @Test
+    public void testLedAndSwitchStatesIgnored() throws IOException {
+        // We don't support L: and S: lines yet, so all we need to check here is that they don't
+        // prevent the other events from being parsed.
+        StringReader reader = new StringReader("""
+                N: ACME Widget
+                I: 0001 1234 5678 9abc
+                L: 00 0
+                L: 09 1
+                S: 0a 1
+                E: 0.000001 0 0 0  # SYN_REPORT
+                """);
+        EvemuParser parser = new EvemuParser(reader);
+        assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER);
+        assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
+        assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.INJECT);
+    }
+
+    private void assertInjectEvent(Event event, int eventType, int eventCode, int value) {
+        assertThat(event).isNotNull();
+        assertThat(event.getCommand()).isEqualTo(Event.Command.INJECT);
+        assertThat(event.getInjections()).asList()
+                .containsExactly(eventType, eventCode, value).inOrder();
+    }
+
+    private void assertDelayEvent(Event event, int durationMillis) {
+        assertThat(event).isNotNull();
+        assertThat(event.getCommand()).isEqualTo(Event.Command.DELAY);
+        assertThat(event.getDurationMillis()).isEqualTo(durationMillis);
+    }
+
+    @Test
+    public void testEventParsing_OneFrame() throws IOException {
+        StringReader reader = new StringReader("""
+                N: ACME Widget
+                I: 0001 1234 5678 9abc
+                E: 0.000001 0002 0000 0001   # REL_X +1
+                E: 0.000001 0002 0001 -0002  # REL_Y -2
+                E: 0.000001 0000 0000 0000   # SYN_REPORT
+                """);
+        EvemuParser parser = new EvemuParser(reader);
+        assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER);
+        assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
+        assertInjectEvent(parser.getNextEvent(), 0x2, 0x0, 1);
+        assertInjectEvent(parser.getNextEvent(), 0x2, 0x1, -2);
+        assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
+    }
+
+    @Test
+    public void testEventParsing_MultipleFrames() throws IOException {
+        StringReader reader = new StringReader("""
+                N: ACME YesBird Typing Aid
+                I: 0001 1234 5678 9abc
+                E: 0.000001 0001 0015 0001   # KEY_Y press
+                E: 0.000001 0000 0000 0000   # SYN_REPORT
+                E: 0.010001 0001 0015 0000   # KEY_Y release
+                E: 0.010001 0000 0000 0000   # SYN_REPORT
+                E: 1.010001 0001 0015 0001   # KEY_Y press
+                E: 1.010001 0000 0000 0000   # SYN_REPORT
+                """);
+        EvemuParser parser = new EvemuParser(reader);
+        assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER);
+        assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
+
+        assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1);
+        assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
+
+        assertDelayEvent(parser.getNextEvent(), 10);
+
+        assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 0);
+        assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
+
+        assertDelayEvent(parser.getNextEvent(), 1000);
+
+        assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1);
+        assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
+    }
+
+    @Test
+    public void testFreeDesktopEvemuRecording() throws IOException {
+        // This is a real recording from FreeDesktop's evemu-record tool, as a basic compatibility
+        // check with the FreeDesktop tools.
+        // (CheckStyle objects to the long line here. It can be split up with escaped newlines once
+        // the fix for b/306423115 reaches Android.)
+        StringReader reader = new StringReader("""
+                # EVEMU 1.3
+                # Kernel: 6.5.6-1rodete4-amd64
+                # DMI: dmi:bvnLENOVO:bvrXXXXXXXX(X.XX):bdXX/XX/XXXX:brX.XX:efrX.XX:svnLENOVO:pnXXXXXXXXXX:pvrThinkPadX1Carbon:rvnLENOVO:rnXXXXXXXXX:rvrXXXXX:cvnLENOVO:ctXX:cvrNone:skuLENOVO_MT_20KG_BU_Think_FM_ThinkPadX1Carbon:
+                # Input device name: "Synaptics TM3289-021"
+                # Input device ID: bus 0x1d vendor 0x6cb product 0000 version 0000
+                # Size in mm: 96x52
+                # Supported events:
+                #   Event type 0 (EV_SYN)
+                #     Event code 0 (SYN_REPORT)
+                #     Event code 1 (SYN_CONFIG)
+                #     Event code 2 (SYN_MT_REPORT)
+                #     Event code 3 (SYN_DROPPED)
+                #     Event code 4 ((null))
+                #     Event code 5 ((null))
+                #     Event code 6 ((null))
+                #     Event code 7 ((null))
+                #     Event code 8 ((null))
+                #     Event code 9 ((null))
+                #     Event code 10 ((null))
+                #     Event code 11 ((null))
+                #     Event code 12 ((null))
+                #     Event code 13 ((null))
+                #     Event code 14 ((null))
+                #     Event code 15 (SYN_MAX)
+                #   Event type 1 (EV_KEY)
+                #     Event code 272 (BTN_LEFT)
+                #     Event code 325 (BTN_TOOL_FINGER)
+                #     Event code 328 (BTN_TOOL_QUINTTAP)
+                #     Event code 330 (BTN_TOUCH)
+                #     Event code 333 (BTN_TOOL_DOUBLETAP)
+                #     Event code 334 (BTN_TOOL_TRIPLETAP)
+                #     Event code 335 (BTN_TOOL_QUADTAP)
+                #   Event type 3 (EV_ABS)
+                #     Event code 0 (ABS_X)
+                #       Value        0
+                #       Min          0
+                #       Max       1936
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution  20
+                #     Event code 1 (ABS_Y)
+                #       Value        0
+                #       Min          0
+                #       Max       1057
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution  20
+                #     Event code 24 (ABS_PRESSURE)
+                #       Value        0
+                #       Min          0
+                #       Max        255
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution   0
+                #     Event code 47 (ABS_MT_SLOT)
+                #       Value        0
+                #       Min          0
+                #       Max          4
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution   0
+                #     Event code 48 (ABS_MT_TOUCH_MAJOR)
+                #       Value        0
+                #       Min          0
+                #       Max         15
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution   0
+                #     Event code 49 (ABS_MT_TOUCH_MINOR)
+                #       Value        0
+                #       Min          0
+                #       Max         15
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution   0
+                #     Event code 52 (ABS_MT_ORIENTATION)
+                #       Value        0
+                #       Min          0
+                #       Max          1
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution   0
+                #     Event code 53 (ABS_MT_POSITION_X)
+                #       Value        0
+                #       Min          0
+                #       Max       1936
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution  20
+                #     Event code 54 (ABS_MT_POSITION_Y)
+                #       Value        0
+                #       Min          0
+                #       Max       1057
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution  20
+                #     Event code 55 (ABS_MT_TOOL_TYPE)
+                #       Value        0
+                #       Min          0
+                #       Max         15
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution   0
+                #     Event code 57 (ABS_MT_TRACKING_ID)
+                #       Value        0
+                #       Min          0
+                #       Max      65535
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution   0
+                #     Event code 58 (ABS_MT_PRESSURE)
+                #       Value        0
+                #       Min          0
+                #       Max        255
+                #       Fuzz         0
+                #       Flat         0
+                #       Resolution   0
+                # Properties:
+                #   Property  type 0 (INPUT_PROP_POINTER)
+                #   Property  type 2 (INPUT_PROP_BUTTONPAD)
+                N: Synaptics TM3289-021
+                I: 001d 06cb 0000 0000
+                P: 05 00 00 00 00 00 00 00
+                B: 00 0b 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 01 00 00 00 00 00
+                B: 01 20 e5 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 01 00 00 00 00 00 00 00 00
+                B: 02 00 00 00 00 00 00 00 00
+                B: 03 03 00 00 01 00 80 f3 06
+                B: 04 00 00 00 00 00 00 00 00
+                B: 05 00 00 00 00 00 00 00 00
+                B: 11 00 00 00 00 00 00 00 00
+                B: 12 00 00 00 00 00 00 00 00
+                B: 14 00 00 00 00 00 00 00 00
+                B: 15 00 00 00 00 00 00 00 00
+                B: 15 00 00 00 00 00 00 00 00
+                A: 00 0 1936 0 0 20
+                A: 01 0 1057 0 0 20
+                A: 18 0 255 0 0 0
+                A: 2f 0 4 0 0 0
+                A: 30 0 15 0 0 0
+                A: 31 0 15 0 0 0
+                A: 34 0 1 0 0 0
+                A: 35 0 1936 0 0 20
+                A: 36 0 1057 0 0 20
+                A: 37 0 15 0 0 0
+                A: 39 0 65535 0 0 0
+                A: 3a 0 255 0 0 0
+                ################################
+                #      Waiting for events      #
+                ################################
+                E: 0.000001 0003 0039 0000\t# EV_ABS / ABS_MT_TRACKING_ID   0
+                E: 0.000001 0003 0035 0891\t# EV_ABS / ABS_MT_POSITION_X    891
+                E: 0.000001 0003 0036 0333\t# EV_ABS / ABS_MT_POSITION_Y    333
+                E: 0.000001 0003 003a 0056\t# EV_ABS / ABS_MT_PRESSURE      56
+                E: 0.000001 0003 0030 0001\t# EV_ABS / ABS_MT_TOUCH_MAJOR   1
+                E: 0.000001 0003 0031 0001\t# EV_ABS / ABS_MT_TOUCH_MINOR   1
+                E: 0.000001 0001 014a 0001\t# EV_KEY / BTN_TOUCH            1
+                E: 0.000001 0001 0145 0001\t# EV_KEY / BTN_TOOL_FINGER      1
+                E: 0.000001 0003 0000 0891\t# EV_ABS / ABS_X                891
+                E: 0.000001 0003 0001 0333\t# EV_ABS / ABS_Y                333
+                E: 0.000001 0003 0018 0056\t# EV_ABS / ABS_PRESSURE         56
+                E: 0.000001 0000 0000 0000\t# ------------ SYN_REPORT (0) ---------- +0ms
+                E: 0.006081 0003 0035 0888\t# EV_ABS / ABS_MT_POSITION_X    888
+                """);
+        EvemuParser parser = new EvemuParser(reader);
+        Event regEvent = parser.getNextEvent();
+        assertThat(regEvent.getName()).isEqualTo("Synaptics TM3289-021");
+
+        assertThat(regEvent.getBus()).isEqualTo(0x001d);
+        assertThat(regEvent.getVendorId()).isEqualTo(0x6cb);
+        assertThat(regEvent.getProductId()).isEqualTo(0x0000);
+        // TODO(b/302297266): check version ID once it's supported
+
+        assertThat(regEvent.getConfiguration().get(UinputControlCode.UI_SET_PROPBIT.getValue()))
+                .asList().containsExactly(0, 2);
+
+        assertThat(regEvent.getConfiguration().get(UinputControlCode.UI_SET_EVBIT.getValue()))
+                .asList().containsExactly(Event.EV_KEY, Event.EV_ABS);
+        assertThat(regEvent.getConfiguration().get(UinputControlCode.UI_SET_KEYBIT.getValue()))
+                .asList().containsExactly(272, 325, 328, 330, 333, 334, 335);
+        assertThat(regEvent.getConfiguration().get(UinputControlCode.UI_SET_ABSBIT.getValue()))
+                .asList().containsExactly(0, 1, 24, 47, 48, 49, 52, 53, 54, 55, 57, 58);
+
+        SparseArray<InputAbsInfo> absInfos = regEvent.getAbsInfo();
+        assertAbsInfo(absInfos.get(0), 0, 1936, 0, 0, 20);
+        assertAbsInfo(absInfos.get(1), 0, 1057, 0, 0, 20);
+        assertAbsInfo(absInfos.get(24), 0, 255, 0, 0, 0);
+        assertAbsInfo(absInfos.get(47), 0, 4, 0, 0, 0);
+        assertAbsInfo(absInfos.get(48), 0, 15, 0, 0, 0);
+        assertAbsInfo(absInfos.get(49), 0, 15, 0, 0, 0);
+        assertAbsInfo(absInfos.get(52), 0, 1, 0, 0, 0);
+        assertAbsInfo(absInfos.get(53), 0, 1936, 0, 0, 20);
+        assertAbsInfo(absInfos.get(54), 0, 1057, 0, 0, 20);
+        assertAbsInfo(absInfos.get(55), 0, 15, 0, 0, 0);
+        assertAbsInfo(absInfos.get(57), 0, 65535, 0, 0, 0);
+        assertAbsInfo(absInfos.get(58), 0, 255, 0, 0, 0);
+
+        assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
+
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x39, 0);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x35, 891);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x36, 333);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x3a, 56);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x30, 1);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x31, 1);
+        assertInjectEvent(parser.getNextEvent(), 0x1, 0x14a, 1);
+        assertInjectEvent(parser.getNextEvent(), 0x1, 0x145, 1);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x0, 891);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x1, 333);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x18, 56);
+        assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
+
+        assertDelayEvent(parser.getNextEvent(), 6);
+
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x0035, 888);
+    }
+}