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

Commit 71e567c1 authored by jioana's avatar jioana
Browse files

Validate Event Assigner for input devices other than touchscreen

Extended the logic for processing MOVE events that happen after DOWN events in the same frame to all types of Motion events and other input devices.
If a sequence like DOWN -> MOVE happens in a single frame we want to provide the eventId of the DOWN event.
Before this CL, this was implemented only for Move events coming from Touchscreen. This CL implements this behaviour for all Motion Events and other input devices such as Stylus, Mouse, Touchpad

Added tests for this functionality and also to test that if a DOWN -> EVENT -> UP/CANCEL happens the DOWN event is processed.

Bug: b/234026254
Test: atest InputTests
Flag: EXEMPT bugfix
Change-Id: I50b6035bd5de44845ef88484e148120b201a149e
parent 15ac2ef9
Loading
Loading
Loading
Loading
+7 −5
Original line number Diff line number Diff line
@@ -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.
@@ -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();
+147 −39
Original line number Diff line number Diff line
@@ -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
 */
@@ -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)
        )
    }

    /**