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

Commit 7d0569c3 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Introduce UinputRecordingIntegrationTests" into main

parents 294922a2 00f0da0e
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -32,6 +32,14 @@
             android:process=":externalProcess">
        </activity>

        <activity android:name="com.android.test.input.CaptureEventActivity"
            android:label="Capture events"
            android:configChanges="touchscreen|uiMode|orientation|screenSize|screenLayout|keyboardHidden|uiMode|navigation|keyboard|density|fontScale|layoutDirection|locale|mcc|mnc|smallestScreenSize"
            android:enableOnBackInvokedCallback="false"
            android:turnScreenOn="true"
            android:exported="true">
        </activity>

    </application>
    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                     android:targetPackage="com.android.test.input"
+150 −0
Original line number Diff line number Diff line
# EVEMU 1.2
# One finger swipe gesture on the Google Pixel Tablet touchscreen
N: NVTCapacitiveTouchScreen
I: 001c 0603 7806 0100
P: 02 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 80 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 04 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 00 00 00 00 00 80 f3 46
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
A: 2f 0 9 0 0 0
A: 30 0 2559 0 0 11
A: 31 0 1599 0 0 11
A: 34 -4096 4096 0 0 0
A: 35 0 1599 0 0 11
A: 36 0 2559 0 0 11
A: 37 0 2 0 0 0
A: 39 0 65535 0 0 0
A: 3a 0 256 0 0 0
A: 3e 0 8 0 0 0
E: 0.000001 0001 014a 0001
E: 0.000001 0003 0039 0003
E: 0.000001 0003 0035 0810
E: 0.000001 0003 0036 1650
E: 0.000001 0003 0030 0082
E: 0.000001 0003 0031 0077
E: 0.000001 0003 003a 0215
E: 0.000001 0003 0034 3218
E: 0.000001 0000 0000 0000
E: 0.008818 0003 0035 0825
E: 0.008818 0003 0036 1645
E: 0.008818 0003 0034 3217
E: 0.008818 0000 0000 0000
E: 0.016306 0003 0035 0841
E: 0.016306 0003 0036 1639
E: 0.016306 0003 0034 3102
E: 0.016306 0000 0000 0000
E: 0.025653 0003 0035 0862
E: 0.025653 0003 0036 1630
E: 0.025653 0003 0034 3092
E: 0.025653 0000 0000 0000
E: 0.032936 0003 0035 0883
E: 0.032936 0003 0036 1619
E: 0.032936 0003 0034 3030
E: 0.032936 0000 0000 0000
E: 0.042072 0003 0035 0905
E: 0.042072 0003 0036 1604
E: 0.042072 0003 0034 2848
E: 0.042072 0000 0000 0000
E: 0.049569 0003 0035 0924
E: 0.049569 0003 0036 1591
E: 0.049569 0003 0034 2830
E: 0.049569 0000 0000 0000
E: 0.058706 0003 0035 0942
E: 0.058706 0003 0036 1573
E: 0.058706 0000 0000 0000
E: 0.066207 0003 0035 0954
E: 0.066207 0003 0036 1557
E: 0.066207 0003 0034 2790
E: 0.066207 0000 0000 0000
E: 0.075337 0003 0035 0966
E: 0.075337 0003 0036 1535
E: 0.075337 0000 0000 0000
E: 0.082841 0003 0035 0973
E: 0.082841 0003 0036 1511
E: 0.082841 0003 0034 2788
E: 0.082841 0000 0000 0000
E: 0.091972 0003 0035 0971
E: 0.091972 0003 0036 1480
E: 0.091972 0003 0034 2770
E: 0.091972 0000 0000 0000
E: 0.099474 0003 0035 0961
E: 0.099474 0003 0036 1445
E: 0.099474 0003 0034 2644
E: 0.099474 0000 0000 0000
E: 0.108631 0003 0035 0937
E: 0.108631 0003 0036 1400
E: 0.108631 0003 0030 0083
E: 0.108631 0003 0034 2461
E: 0.108631 0000 0000 0000
E: 0.116109 0003 0035 0909
E: 0.116109 0003 0036 1361
E: 0.116109 0003 0034 2278
E: 0.116109 0000 0000 0000
E: 0.125263 0003 0035 0865
E: 0.125263 0003 0036 1311
E: 0.125263 0003 0034 2096
E: 0.125263 0000 0000 0000
E: 0.132741 0003 0035 0820
E: 0.132741 0003 0036 1261
E: 0.132741 0003 0034 2083
E: 0.132741 0000 0000 0000
E: 0.141876 0003 0035 0755
E: 0.141876 0003 0036 1193
E: 0.141876 0003 003a 0216
E: 0.141876 0003 0034 2266
E: 0.141876 0000 0000 0000
E: 0.149376 0003 0035 0691
E: 0.149376 0003 0036 1124
E: 0.149376 0003 0034 2448
E: 0.149376 0000 0000 0000
E: 0.158510 0003 0035 0609
E: 0.158510 0003 0036 1033
E: 0.158510 0003 0034 2631
E: 0.158510 0000 0000 0000
E: 0.166011 0003 0035 0543
E: 0.166011 0003 0036 0957
E: 0.166011 0003 0034 2813
E: 0.166011 0000 0000 0000
E: 0.175182 0003 0035 0471
E: 0.175182 0003 0036 0864
E: 0.175182 0003 0031 0076
E: 0.175182 0003 0034 2996
E: 0.175182 0000 0000 0000
E: 0.182683 0003 0035 0417
E: 0.182683 0003 0036 0792
E: 0.182683 0003 003a 0214
E: 0.182683 0003 0034 3178
E: 0.182683 0000 0000 0000
E: 0.191777 0003 0035 0361
E: 0.191777 0003 0036 0719
E: 0.191777 0003 0031 0075
E: 0.191777 0003 003a 0213
E: 0.191777 0003 0034 2996
E: 0.191777 0000 0000 0000
E: 0.199431 0003 0035 0271
E: 0.199431 0003 0036 0603
E: 0.199431 0003 0030 0060
E: 0.199431 0003 0031 0029
E: 0.199431 0003 003a 0060
E: 0.199431 0003 0034 2813
E: 0.199431 0000 0000 0000
E: 0.207943 0003 003a 0000
E: 0.207943 0003 0039 -001
E: 0.207943 0001 014a 0000
E: 0.207943 0000 0000 0000
+34 −0
Original line number Diff line number Diff line
[
  {
    "name": "One finger swipe",
    "source": "TOUCHSCREEN",
    "events": [
      {"action":"DOWN","axes":{"AXIS_X":810,"AXIS_Y":1650,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.234087586402893},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":825,"AXIS_Y":1645,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.2337040901184082},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":841,"AXIS_Y":1639,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.1896021366119385},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":862,"AXIS_Y":1630,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.1857671737670898},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":883,"AXIS_Y":1619,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.1619905233383179},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":905,"AXIS_Y":1604,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0921943187713623},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":924,"AXIS_Y":1591,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0852913856506348},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":942,"AXIS_Y":1573,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0852913856506348},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":954,"AXIS_Y":1557,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0699516534805298},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":966,"AXIS_Y":1535,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0699516534805298},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":973,"AXIS_Y":1511,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.06918466091156},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":971,"AXIS_Y":1480,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0622817277908325},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":961,"AXIS_Y":1445,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0139613151550293},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":937,"AXIS_Y":1400,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.9437817335128784},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":909,"AXIS_Y":1361,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.8736020922660828},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":865,"AXIS_Y":1311,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.803805947303772},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":820,"AXIS_Y":1261,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.7988204956054688},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":755,"AXIS_Y":1193,"AXIS_PRESSURE":0.84375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.8690001368522644},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":691,"AXIS_Y":1124,"AXIS_PRESSURE":0.84375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.9387962818145752},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":609,"AXIS_Y":1033,"AXIS_PRESSURE":0.84375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.008975863456726},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":543,"AXIS_Y":957,"AXIS_PRESSURE":0.84375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0787720680236816},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":471,"AXIS_Y":864,"AXIS_PRESSURE":0.84375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":76,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":76,"AXIS_ORIENTATION":1.1489516496658325},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":417,"AXIS_Y":792,"AXIS_PRESSURE":0.8359375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":76,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":76,"AXIS_ORIENTATION":1.2187477350234985},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":361,"AXIS_Y":719,"AXIS_PRESSURE":0.83203125,"AXIS_SIZE":0.03087143413722515,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":75,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":75,"AXIS_ORIENTATION":1.1489516496658325},"buttonState":[]},
      {"action":"MOVE","axes":{"AXIS_X":271,"AXIS_Y":603,"AXIS_PRESSURE":0.234375,"AXIS_SIZE":0.017389604821801186,"AXIS_TOUCH_MAJOR":60,"AXIS_TOUCH_MINOR":29,"AXIS_TOOL_MAJOR":60,"AXIS_TOOL_MINOR":29,"AXIS_ORIENTATION":1.0787720680236816},"buttonState":[]},
      {"action":"UP","axes":{"AXIS_X":271,"AXIS_Y":603,"AXIS_PRESSURE":0.234375,"AXIS_SIZE":0.017389604821801186,"AXIS_TOUCH_MAJOR":60,"AXIS_TOUCH_MINOR":29,"AXIS_TOOL_MAJOR":60,"AXIS_TOOL_MINOR":29,"AXIS_ORIENTATION":1.0787720680236816},"buttonState":[]}
    ]
  }
]
+85 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 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.test.input

import android.app.Activity
import android.os.Bundle
import android.view.InputEvent
import android.view.KeyEvent
import android.view.MotionEvent
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit
import org.junit.Assert.assertNull

class CaptureEventActivity : Activity() {
    private val events = LinkedBlockingQueue<InputEvent>()
    var shouldHandleKeyEvents = true

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Set the fixed orientation if requested
        if (intent.hasExtra(EXTRA_FIXED_ORIENTATION)) {
            val orientation = intent.getIntExtra(EXTRA_FIXED_ORIENTATION, 0)
            setRequestedOrientation(orientation)
        }

        // Set the flag if requested
        if (intent.hasExtra(EXTRA_WINDOW_FLAGS)) {
            val flags = intent.getIntExtra(EXTRA_WINDOW_FLAGS, 0)
            window.addFlags(flags)
        }
    }

    override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
        events.add(MotionEvent.obtain(ev))
        return true
    }

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        events.add(MotionEvent.obtain(ev))
        return true
    }

    override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
        events.add(KeyEvent(event))
        return shouldHandleKeyEvents
    }

    override fun dispatchTrackballEvent(ev: MotionEvent?): Boolean {
        events.add(MotionEvent.obtain(ev))
        return true
    }

    fun getInputEvent(): InputEvent? {
        return events.poll(5, TimeUnit.SECONDS)
    }

    fun hasReceivedEvents(): Boolean {
        return !events.isEmpty()
    }

    fun assertNoEvents() {
        val event = events.poll(100, TimeUnit.MILLISECONDS)
        assertNull("Expected no events, but received $event", event)
    }

    companion object {
        const val EXTRA_FIXED_ORIENTATION = "fixed_orientation"
        const val EXTRA_WINDOW_FLAGS = "window_flags"
    }
}
+170 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 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.test.input

import android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY
import android.app.Instrumentation
import android.cts.input.EventVerifier
import android.graphics.PointF
import android.hardware.input.InputManager
import android.os.ParcelFileDescriptor
import android.util.Size
import android.view.MotionEvent
import androidx.test.platform.app.InstrumentationRegistry
import com.android.cts.input.BatchedEventSplitter
import com.android.cts.input.InputJsonParser
import com.android.cts.input.VirtualDisplayActivityScenario
import com.android.cts.input.inputeventmatchers.isResampled
import com.android.cts.input.inputeventmatchers.withButtonState
import com.android.cts.input.inputeventmatchers.withHistorySize
import com.android.cts.input.inputeventmatchers.withMotionAction
import com.android.cts.input.inputeventmatchers.withPressure
import com.android.cts.input.inputeventmatchers.withRawCoords
import com.android.cts.input.inputeventmatchers.withSource
import java.io.InputStream
import org.hamcrest.Matchers.allOf
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestName
import org.junit.runner.RunWith
import org.junit.runners.Parameterized

/**
 * Integration tests for the input pipeline that replays recording taken from physical input devices
 * at the evdev interface level, and makes assertions on the events that are received by a test app.
 *
 * These tests associate the playback input device with a virtual display to make these tests
 * agnostic to the device form factor.
 *
 * New recordings can be taken using the `evemu-record` shell command.
 */
@RunWith(Parameterized::class)
class UinputRecordingIntegrationTests {

    companion object {
        /**
         * Add new test cases by adding a new [TestData] to the following list.
         */
        @JvmStatic
        @Parameterized.Parameters(name = "{0}")
        fun data(): Iterable<Any> =
            listOf(
                TestData(
                    "GooglePixelTabletTouchscreen", R.raw.google_pixel_tablet_touchscreen,
                    R.raw.google_pixel_tablet_touchscreen_events, Size(1600, 2560),
                    vendorId = 0x0603, productId = 0x7806
                ),
            )

        const val INPUT_DEVICE_SOURCE_ALL = -1
    }

    class TestData(
        val name: String,
        val uinputRecordingResource: Int,
        val expectedEventsResource: Int,
        val displaySize: Size,
        val vendorId: Int,
        val productId: Int,
    ) {
        override fun toString(): String = name
    }

    private lateinit var instrumentation: Instrumentation
    private lateinit var parser: InputJsonParser

    @get:Rule
    val testName = TestName()

    @Parameterized.Parameter(0)
    lateinit var testData: TestData

    @Before
    fun setUp() {
        instrumentation = InstrumentationRegistry.getInstrumentation()
        parser = InputJsonParser(instrumentation.context)
    }

    @Test
    fun testEvemuRecording() {
        VirtualDisplayActivityScenario.AutoClose<CaptureEventActivity>(
            testName,
            size = testData.displaySize
        ).use { scenario ->
            scenario.activity.window.decorView.requestUnbufferedDispatch(INPUT_DEVICE_SOURCE_ALL)

            try {
                instrumentation.uiAutomation.adoptShellPermissionIdentity(
                    ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
                )

                val inputPort = "uinput:1:${testData.vendorId}:${testData.productId}"
                val inputManager =
                    instrumentation.context.getSystemService(InputManager::class.java)!!
                try {
                    inputManager.addUniqueIdAssociationByPort(
                        inputPort,
                        scenario.virtualDisplay.display.uniqueId!!,
                    )

                    val verifier =
                        EventVerifier(BatchedEventSplitter { scenario.activity.getInputEvent() })
                    injectUinputEvents()
                    verifyEvents(verifier)
                    scenario.activity.assertNoEvents()
                } finally {
                    inputManager.removeUniqueIdAssociationByPort(inputPort)
                }
            } finally {
                instrumentation.uiAutomation.dropShellPermissionIdentity()
            }
        }
    }

    private fun injectUinputEvents() {
        val fds = instrumentation.uiAutomation!!.executeShellCommandRw("uinput -")

        ParcelFileDescriptor.AutoCloseOutputStream(fds[1]).use { stdIn ->
            val inputStream: InputStream = instrumentation.context.resources.openRawResource(
                testData.uinputRecordingResource,
            )
            stdIn.write(inputStream.readBytes())
        }
    }

    private fun verifyEvents(verifier: EventVerifier) {
        val uinputTestData = parser.getUinputTestData(testData.expectedEventsResource)
        for (test in uinputTestData) {
            for ((index, expectedEvent) in test.events.withIndex()) {
                if (expectedEvent is MotionEvent) {
                    verifier.assertReceivedMotion(
                        allOf(
                            withMotionAction(expectedEvent.action),
                            withSource(expectedEvent.source),
                            withButtonState(expectedEvent.buttonState),
                            withRawCoords(PointF(expectedEvent.rawX, expectedEvent.rawY)),
                            withPressure(expectedEvent.pressure),
                            isResampled(false),
                            withHistorySize(0),
                        ),
                        "${test.name}: Expected event at index $index",
                    )
                }
            }
        }
    }
}