/* * Copyright (C) 2021 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.view.InputDevice.SOURCE_MOUSE import android.view.InputDevice.SOURCE_STYLUS import android.view.InputDevice.SOURCE_TOUCHPAD import android.view.InputDevice.SOURCE_TOUCHSCREEN 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 */ fun createMotionEvent(action: Int, eventTime: Long, source: Int): MotionEvent { val downTime: Long = 10 val x = 1f val y = 2f val pressure = 3f val size = 1f val metaState = 0 val xPrecision = 0f val yPrecision = 0f val deviceId = 1 val edgeFlags = 0 val displayId = 0 return MotionEvent.obtain( downTime, eventTime, action, x, y, pressure, size, metaState, xPrecision, yPrecision, deviceId, edgeFlags, source, displayId, ) } private fun createKeyEvent(action: Int, eventTime: Long): KeyEvent { val code = KeyEvent.KEYCODE_A val repeat = 0 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 = 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 event should be assigned to the next available frame. */ @Test 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) ) } /** * 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 testTouchDownAndMove() { testDownAndMove(SOURCE_TOUCHSCREEN) } @Test fun testMouseDownAndMove() { testDownAndMove(SOURCE_MOUSE) } @Test fun testStylusDownAndMove() { testDownAndMove(SOURCE_STYLUS) } @Test fun testTouchpadDownAndMove() { testDownAndMove(SOURCE_TOUCHPAD) } /** After an up event, motion events should be assigned their own event id */ @Test 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), ) } /** KeyEvents are processed immediately, so the latest event should be returned. */ @Test fun testKeyEvent() { val assigner = InputEventAssigner() val down = createKeyEvent(KeyEvent.ACTION_DOWN, 20) var eventId = assigner.processEvent(down) assertEquals(down.id, eventId) val up = createKeyEvent(KeyEvent.ACTION_UP, 21) eventId = assigner.processEvent(up) // DOWN is only sticky for Motions, not for keys assertEquals(up.id, eventId) assigner.notifyFrameProcessed() val down2 = createKeyEvent(KeyEvent.ACTION_DOWN, 22) eventId = assigner.processEvent(down2) assertEquals(down2.id, eventId) } }