diff options
5 files changed, 447 insertions, 0 deletions
diff --git a/tests/Input/AndroidManifest.xml b/tests/Input/AndroidManifest.xml index a05d08ccceba..914adc40194d 100644 --- a/tests/Input/AndroidManifest.xml +++ b/tests/Input/AndroidManifest.xml @@ -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" diff --git a/tests/Input/res/raw/google_pixel_tablet_touchscreen.evemu b/tests/Input/res/raw/google_pixel_tablet_touchscreen.evemu new file mode 100644 index 000000000000..1a9112b97301 --- /dev/null +++ b/tests/Input/res/raw/google_pixel_tablet_touchscreen.evemu @@ -0,0 +1,150 @@ +# 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 diff --git a/tests/Input/res/raw/google_pixel_tablet_touchscreen_events.json b/tests/Input/res/raw/google_pixel_tablet_touchscreen_events.json new file mode 100644 index 000000000000..df4f9fb4e1df --- /dev/null +++ b/tests/Input/res/raw/google_pixel_tablet_touchscreen_events.json @@ -0,0 +1,34 @@ +[ + { + "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":[]} + ] + } +] diff --git a/tests/Input/src/com/android/test/input/CaptureEventActivity.kt b/tests/Input/src/com/android/test/input/CaptureEventActivity.kt new file mode 100644 index 000000000000..d54e3470d9c4 --- /dev/null +++ b/tests/Input/src/com/android/test/input/CaptureEventActivity.kt @@ -0,0 +1,85 @@ +/* + * 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" + } +} diff --git a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt new file mode 100644 index 000000000000..5d45c44a53cd --- /dev/null +++ b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt @@ -0,0 +1,170 @@ +/* + * 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", + ) + } + } + } + } +} |