Loading core/java/android/view/InputEventAssigner.java +7 −5 Original line number Diff line number Diff line Loading @@ -17,7 +17,8 @@ package android.view; import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.InputDevice.SOURCE_CLASS_POINTER; import static android.view.InputDevice.SOURCE_CLASS_POSITION; /** * Process input events and assign input event id to a specific frame. Loading Loading @@ -64,18 +65,19 @@ public class InputEventAssigner { public int processEvent(InputEvent event) { if (event instanceof MotionEvent) { MotionEvent motionEvent = (MotionEvent) event; if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN)) { if (motionEvent.isFromSource(SOURCE_CLASS_POINTER) || motionEvent.isFromSource( SOURCE_CLASS_POSITION)) { final int action = motionEvent.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { mHasUnprocessedDown = true; mDownEventId = event.getId(); } if (mHasUnprocessedDown && action == MotionEvent.ACTION_MOVE) { return mDownEventId; } if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { mHasUnprocessedDown = false; } if (mHasUnprocessedDown) { return mDownEventId; } } } return event.getId(); Loading tests/Input/src/com/android/test/input/InputEventAssignerTest.kt +147 −39 Original line number Diff line number Diff line Loading @@ -18,12 +18,20 @@ package com.android.test.input import android.view.InputDevice.SOURCE_MOUSE import android.view.InputDevice.SOURCE_TOUCHSCREEN import android.view.InputDevice.SOURCE_STYLUS import android.view.InputDevice.SOURCE_TOUCHPAD import android.view.InputEventAssigner import android.view.KeyEvent import android.view.MotionEvent import org.junit.Assert.assertEquals import org.junit.Test sealed class StreamEvent private data object Vsync : StreamEvent() data class MotionEventData(val action: Int, val source: Int, val id: Int, val expectedId: Int) : StreamEvent() /** * Create a MotionEvent with the provided action, eventTime, and source */ Loading @@ -49,64 +57,164 @@ private fun createKeyEvent(action: Int, eventTime: Long): KeyEvent { return KeyEvent(eventTime, eventTime, action, code, repeat) } /** * Check that the correct eventIds are assigned in a stream. The stream consists of motion * events or vsync (processed frame) * Each streamEvent should have unique ids when writing tests * The test passes even if two events get assigned the same eventId, since the mapping is * streamEventId -> motionEventId and streamEvents have unique ids */ private fun checkEventStream(vararg streamEvents: StreamEvent) { val assigner = InputEventAssigner() var eventTime = 10L // Maps MotionEventData.id to MotionEvent.id // We can't control the event id of the generated motion events but for testing it's easier // to label the events with a custom id for readability val eventIdMap: HashMap<Int, Int> = HashMap() for (streamEvent in streamEvents) { when (streamEvent) { is MotionEventData -> { val event = createMotionEvent(streamEvent.action, eventTime, streamEvent.source) eventIdMap[streamEvent.id] = event.id val eventId = assigner.processEvent(event) assertEquals(eventIdMap[streamEvent.expectedId], eventId) } is Vsync -> assigner.notifyFrameProcessed() } eventTime += 1 } } class InputEventAssignerTest { companion object { private const val TAG = "InputEventAssignerTest" } /** * A single MOVE event should be assigned to the next available frame. * A single event should be assigned to the next available frame. */ @Test fun testTouchGesture() { val assigner = InputEventAssigner() val event = createMotionEvent(MotionEvent.ACTION_MOVE, 10, SOURCE_TOUCHSCREEN) val eventId = assigner.processEvent(event) assertEquals(event.id, eventId) fun testTouchMove() { checkEventStream( MotionEventData(MotionEvent.ACTION_MOVE, SOURCE_TOUCHSCREEN, id = 1, expectedId = 1) ) } @Test fun testMouseMove() { checkEventStream( MotionEventData(MotionEvent.ACTION_MOVE, SOURCE_MOUSE, id = 1, expectedId = 1) ) } @Test fun testMouseScroll() { checkEventStream( MotionEventData(MotionEvent.ACTION_SCROLL, SOURCE_MOUSE, id = 1, expectedId = 1) ) } @Test fun testStylusMove() { checkEventStream( MotionEventData(MotionEvent.ACTION_MOVE, SOURCE_STYLUS, id = 1, expectedId = 1) ) } @Test fun testStylusHover() { checkEventStream( MotionEventData(MotionEvent.ACTION_HOVER_MOVE, SOURCE_STYLUS, id = 1, expectedId = 1) ) } @Test fun testTouchpadMove() { checkEventStream( MotionEventData(MotionEvent.ACTION_MOVE, SOURCE_STYLUS, id = 1, expectedId = 1) ) } /** * DOWN event should be used until a vsync comes in. After vsync, the latest event should be * produced. * Test that before a VSYNC the event id generated by input event assigner for move events is * the id of the down event. Move events coming after a VSYNC should be assigned their own event * id */ private fun testDownAndMove(source: Int) { checkEventStream( MotionEventData(MotionEvent.ACTION_DOWN, source, id = 1, expectedId = 1), MotionEventData(MotionEvent.ACTION_MOVE, source, id = 2, expectedId = 1), Vsync, MotionEventData(MotionEvent.ACTION_MOVE, source, id = 4, expectedId = 4) ) } @Test fun testTouchDownWithMove() { val assigner = InputEventAssigner() val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_TOUCHSCREEN) val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_TOUCHSCREEN) val move2 = createMotionEvent(MotionEvent.ACTION_MOVE, 13, SOURCE_TOUCHSCREEN) val move3 = createMotionEvent(MotionEvent.ACTION_MOVE, 14, SOURCE_TOUCHSCREEN) val move4 = createMotionEvent(MotionEvent.ACTION_MOVE, 15, SOURCE_TOUCHSCREEN) var eventId = assigner.processEvent(down) assertEquals(down.id, eventId) eventId = assigner.processEvent(move1) assertEquals(down.id, eventId) eventId = assigner.processEvent(move2) // Even though we already had 2 move events, there was no choreographer callback yet. // Therefore, we should still get the id of the down event assertEquals(down.id, eventId) fun testTouchDownAndMove() { testDownAndMove(SOURCE_TOUCHSCREEN) } // Now send CALLBACK_INPUT to the assigner. It should provide the latest motion event assigner.notifyFrameProcessed() eventId = assigner.processEvent(move3) assertEquals(move3.id, eventId) eventId = assigner.processEvent(move4) assertEquals(move4.id, eventId) @Test fun testMouseDownAndMove() { testDownAndMove(SOURCE_MOUSE) } @Test fun testStylusDownAndMove() { testDownAndMove(SOURCE_STYLUS) } @Test fun testTouchpadDownAndMove() { testDownAndMove(SOURCE_TOUCHPAD) } /** * Similar to the above test, but with SOURCE_MOUSE. Since we don't have down latency * concept for non-touchscreens, the latest input event will be used. * After an up event, motion events should be assigned their own event id */ @Test fun testMouseDownWithMove() { val assigner = InputEventAssigner() val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_MOUSE) val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_MOUSE) var eventId = assigner.processEvent(down) assertEquals(down.id, eventId) eventId = assigner.processEvent(move1) assertEquals(move1.id, eventId) fun testMouseDownUpAndScroll() { checkEventStream( MotionEventData(MotionEvent.ACTION_DOWN, SOURCE_MOUSE, id = 1, expectedId = 1), MotionEventData(MotionEvent.ACTION_UP, SOURCE_MOUSE, id = 2, expectedId = 2), MotionEventData(MotionEvent.ACTION_SCROLL, SOURCE_MOUSE, id = 3, expectedId = 3) ) } /** * After an up event, motion events should be assigned their own event id */ @Test fun testStylusDownUpAndHover() { checkEventStream( MotionEventData(MotionEvent.ACTION_DOWN, SOURCE_STYLUS, id = 1, expectedId = 1), MotionEventData(MotionEvent.ACTION_UP, SOURCE_STYLUS, id = 2, expectedId = 2), MotionEventData(MotionEvent.ACTION_HOVER_ENTER, SOURCE_STYLUS, id = 3, expectedId = 3) ) } /** * After a cancel event, motion events should be assigned their own event id */ @Test fun testMouseDownCancelAndScroll() { checkEventStream( MotionEventData(MotionEvent.ACTION_DOWN, SOURCE_MOUSE, id = 1, expectedId = 1), MotionEventData(MotionEvent.ACTION_CANCEL, SOURCE_MOUSE, id = 2, expectedId = 2), MotionEventData(MotionEvent.ACTION_SCROLL, SOURCE_MOUSE, id = 3, expectedId = 3) ) } /** * After a cancel event, motion events should be assigned their own event id */ @Test fun testStylusDownCancelAndHover() { checkEventStream( MotionEventData(MotionEvent.ACTION_DOWN, SOURCE_STYLUS, id = 1, expectedId = 1), MotionEventData(MotionEvent.ACTION_CANCEL, SOURCE_STYLUS, id = 2, expectedId = 2), MotionEventData(MotionEvent.ACTION_HOVER_ENTER, SOURCE_STYLUS, id = 3, expectedId = 3) ) } /** Loading Loading
core/java/android/view/InputEventAssigner.java +7 −5 Original line number Diff line number Diff line Loading @@ -17,7 +17,8 @@ package android.view; import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.InputDevice.SOURCE_CLASS_POINTER; import static android.view.InputDevice.SOURCE_CLASS_POSITION; /** * Process input events and assign input event id to a specific frame. Loading Loading @@ -64,18 +65,19 @@ public class InputEventAssigner { public int processEvent(InputEvent event) { if (event instanceof MotionEvent) { MotionEvent motionEvent = (MotionEvent) event; if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN)) { if (motionEvent.isFromSource(SOURCE_CLASS_POINTER) || motionEvent.isFromSource( SOURCE_CLASS_POSITION)) { final int action = motionEvent.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { mHasUnprocessedDown = true; mDownEventId = event.getId(); } if (mHasUnprocessedDown && action == MotionEvent.ACTION_MOVE) { return mDownEventId; } if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { mHasUnprocessedDown = false; } if (mHasUnprocessedDown) { return mDownEventId; } } } return event.getId(); Loading
tests/Input/src/com/android/test/input/InputEventAssignerTest.kt +147 −39 Original line number Diff line number Diff line Loading @@ -18,12 +18,20 @@ package com.android.test.input import android.view.InputDevice.SOURCE_MOUSE import android.view.InputDevice.SOURCE_TOUCHSCREEN import android.view.InputDevice.SOURCE_STYLUS import android.view.InputDevice.SOURCE_TOUCHPAD import android.view.InputEventAssigner import android.view.KeyEvent import android.view.MotionEvent import org.junit.Assert.assertEquals import org.junit.Test sealed class StreamEvent private data object Vsync : StreamEvent() data class MotionEventData(val action: Int, val source: Int, val id: Int, val expectedId: Int) : StreamEvent() /** * Create a MotionEvent with the provided action, eventTime, and source */ Loading @@ -49,64 +57,164 @@ private fun createKeyEvent(action: Int, eventTime: Long): KeyEvent { return KeyEvent(eventTime, eventTime, action, code, repeat) } /** * Check that the correct eventIds are assigned in a stream. The stream consists of motion * events or vsync (processed frame) * Each streamEvent should have unique ids when writing tests * The test passes even if two events get assigned the same eventId, since the mapping is * streamEventId -> motionEventId and streamEvents have unique ids */ private fun checkEventStream(vararg streamEvents: StreamEvent) { val assigner = InputEventAssigner() var eventTime = 10L // Maps MotionEventData.id to MotionEvent.id // We can't control the event id of the generated motion events but for testing it's easier // to label the events with a custom id for readability val eventIdMap: HashMap<Int, Int> = HashMap() for (streamEvent in streamEvents) { when (streamEvent) { is MotionEventData -> { val event = createMotionEvent(streamEvent.action, eventTime, streamEvent.source) eventIdMap[streamEvent.id] = event.id val eventId = assigner.processEvent(event) assertEquals(eventIdMap[streamEvent.expectedId], eventId) } is Vsync -> assigner.notifyFrameProcessed() } eventTime += 1 } } class InputEventAssignerTest { companion object { private const val TAG = "InputEventAssignerTest" } /** * A single MOVE event should be assigned to the next available frame. * A single event should be assigned to the next available frame. */ @Test fun testTouchGesture() { val assigner = InputEventAssigner() val event = createMotionEvent(MotionEvent.ACTION_MOVE, 10, SOURCE_TOUCHSCREEN) val eventId = assigner.processEvent(event) assertEquals(event.id, eventId) fun testTouchMove() { checkEventStream( MotionEventData(MotionEvent.ACTION_MOVE, SOURCE_TOUCHSCREEN, id = 1, expectedId = 1) ) } @Test fun testMouseMove() { checkEventStream( MotionEventData(MotionEvent.ACTION_MOVE, SOURCE_MOUSE, id = 1, expectedId = 1) ) } @Test fun testMouseScroll() { checkEventStream( MotionEventData(MotionEvent.ACTION_SCROLL, SOURCE_MOUSE, id = 1, expectedId = 1) ) } @Test fun testStylusMove() { checkEventStream( MotionEventData(MotionEvent.ACTION_MOVE, SOURCE_STYLUS, id = 1, expectedId = 1) ) } @Test fun testStylusHover() { checkEventStream( MotionEventData(MotionEvent.ACTION_HOVER_MOVE, SOURCE_STYLUS, id = 1, expectedId = 1) ) } @Test fun testTouchpadMove() { checkEventStream( MotionEventData(MotionEvent.ACTION_MOVE, SOURCE_STYLUS, id = 1, expectedId = 1) ) } /** * DOWN event should be used until a vsync comes in. After vsync, the latest event should be * produced. * Test that before a VSYNC the event id generated by input event assigner for move events is * the id of the down event. Move events coming after a VSYNC should be assigned their own event * id */ private fun testDownAndMove(source: Int) { checkEventStream( MotionEventData(MotionEvent.ACTION_DOWN, source, id = 1, expectedId = 1), MotionEventData(MotionEvent.ACTION_MOVE, source, id = 2, expectedId = 1), Vsync, MotionEventData(MotionEvent.ACTION_MOVE, source, id = 4, expectedId = 4) ) } @Test fun testTouchDownWithMove() { val assigner = InputEventAssigner() val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_TOUCHSCREEN) val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_TOUCHSCREEN) val move2 = createMotionEvent(MotionEvent.ACTION_MOVE, 13, SOURCE_TOUCHSCREEN) val move3 = createMotionEvent(MotionEvent.ACTION_MOVE, 14, SOURCE_TOUCHSCREEN) val move4 = createMotionEvent(MotionEvent.ACTION_MOVE, 15, SOURCE_TOUCHSCREEN) var eventId = assigner.processEvent(down) assertEquals(down.id, eventId) eventId = assigner.processEvent(move1) assertEquals(down.id, eventId) eventId = assigner.processEvent(move2) // Even though we already had 2 move events, there was no choreographer callback yet. // Therefore, we should still get the id of the down event assertEquals(down.id, eventId) fun testTouchDownAndMove() { testDownAndMove(SOURCE_TOUCHSCREEN) } // Now send CALLBACK_INPUT to the assigner. It should provide the latest motion event assigner.notifyFrameProcessed() eventId = assigner.processEvent(move3) assertEquals(move3.id, eventId) eventId = assigner.processEvent(move4) assertEquals(move4.id, eventId) @Test fun testMouseDownAndMove() { testDownAndMove(SOURCE_MOUSE) } @Test fun testStylusDownAndMove() { testDownAndMove(SOURCE_STYLUS) } @Test fun testTouchpadDownAndMove() { testDownAndMove(SOURCE_TOUCHPAD) } /** * Similar to the above test, but with SOURCE_MOUSE. Since we don't have down latency * concept for non-touchscreens, the latest input event will be used. * After an up event, motion events should be assigned their own event id */ @Test fun testMouseDownWithMove() { val assigner = InputEventAssigner() val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_MOUSE) val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_MOUSE) var eventId = assigner.processEvent(down) assertEquals(down.id, eventId) eventId = assigner.processEvent(move1) assertEquals(move1.id, eventId) fun testMouseDownUpAndScroll() { checkEventStream( MotionEventData(MotionEvent.ACTION_DOWN, SOURCE_MOUSE, id = 1, expectedId = 1), MotionEventData(MotionEvent.ACTION_UP, SOURCE_MOUSE, id = 2, expectedId = 2), MotionEventData(MotionEvent.ACTION_SCROLL, SOURCE_MOUSE, id = 3, expectedId = 3) ) } /** * After an up event, motion events should be assigned their own event id */ @Test fun testStylusDownUpAndHover() { checkEventStream( MotionEventData(MotionEvent.ACTION_DOWN, SOURCE_STYLUS, id = 1, expectedId = 1), MotionEventData(MotionEvent.ACTION_UP, SOURCE_STYLUS, id = 2, expectedId = 2), MotionEventData(MotionEvent.ACTION_HOVER_ENTER, SOURCE_STYLUS, id = 3, expectedId = 3) ) } /** * After a cancel event, motion events should be assigned their own event id */ @Test fun testMouseDownCancelAndScroll() { checkEventStream( MotionEventData(MotionEvent.ACTION_DOWN, SOURCE_MOUSE, id = 1, expectedId = 1), MotionEventData(MotionEvent.ACTION_CANCEL, SOURCE_MOUSE, id = 2, expectedId = 2), MotionEventData(MotionEvent.ACTION_SCROLL, SOURCE_MOUSE, id = 3, expectedId = 3) ) } /** * After a cancel event, motion events should be assigned their own event id */ @Test fun testStylusDownCancelAndHover() { checkEventStream( MotionEventData(MotionEvent.ACTION_DOWN, SOURCE_STYLUS, id = 1, expectedId = 1), MotionEventData(MotionEvent.ACTION_CANCEL, SOURCE_STYLUS, id = 2, expectedId = 2), MotionEventData(MotionEvent.ACTION_HOVER_ENTER, SOURCE_STYLUS, id = 3, expectedId = 3) ) } /** Loading