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

Commit 00f0da0e authored by Prabir Pradhan's avatar Prabir Pradhan
Browse files

Introduce UinputRecordingIntegrationTests

UinputRecordingIntegrationsTests is a new integration testing framework
that will make it easy to add tests that inject an evemu recording taken
from a physical device, and verify that the app receives the expected
events.

Bug: 310997010
Test: Presubmit
Flag: TEST_ONLY
Change-Id: I8c7a63de8d133cf2175ac5636e6c2abf7f78aeb7
parent b384461f
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",
                    )
                }
            }
        }
    }
}