• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm.flicker.helpers
18 
19 import android.app.Instrumentation
20 import android.os.SystemClock
21 import android.view.ContentInfo.Source
22 import android.view.InputDevice.SOURCE_MOUSE
23 import android.view.InputDevice.SOURCE_STYLUS
24 import android.view.InputDevice.SOURCE_TOUCHSCREEN
25 import android.view.MotionEvent
26 import android.view.MotionEvent.ACTION_DOWN
27 import android.view.MotionEvent.ACTION_MOVE
28 import android.view.MotionEvent.ACTION_UP
29 import android.view.MotionEvent.TOOL_TYPE_FINGER
30 import android.view.MotionEvent.TOOL_TYPE_MOUSE
31 import android.view.MotionEvent.TOOL_TYPE_STYLUS
32 import android.view.MotionEvent.ToolType
33 
34 /**
35  * Helper class for injecting a custom motion event and performing some actions. This is used for
36  * instrumenting input injections like stylus, mouse and touchpad.
37  */
38 class MotionEventHelper(
39     private val instr: Instrumentation,
40     val inputMethod: InputMethod
41 ) {
42     enum class InputMethod(@ToolType val toolType: Int, @Source val source: Int) {
43         STYLUS(TOOL_TYPE_STYLUS, SOURCE_STYLUS),
44         MOUSE(TOOL_TYPE_MOUSE, SOURCE_MOUSE),
45         TOUCHPAD(TOOL_TYPE_FINGER, SOURCE_MOUSE),
46         TOUCH(TOOL_TYPE_FINGER, SOURCE_TOUCHSCREEN)
47     }
48 
actionDownnull49     fun actionDown(x: Int, y: Int, time: Long = SystemClock.uptimeMillis()) {
50         injectMotionEvent(ACTION_DOWN, x, y, downTime = time, eventTime = time)
51     }
52 
actionUpnull53     fun actionUp(x: Int, y: Int, downTime: Long) {
54         injectMotionEvent(ACTION_UP, x, y, downTime = downTime)
55     }
56 
actionMovenull57     fun actionMove(
58         startX: Int,
59         startY: Int,
60         endX: Int,
61         endY: Int,
62         steps: Int,
63         downTime: Long,
64         withMotionEventInjectDelay: Boolean = false
65     ) {
66         val incrementX = (endX - startX).toFloat() / (steps - 1)
67         val incrementY = (endY - startY).toFloat() / (steps - 1)
68 
69         for (i in 0..steps) {
70             val time = SystemClock.uptimeMillis()
71             val x = startX + incrementX * i
72             val y = startY + incrementY * i
73 
74             val moveEvent = getMotionEvent(downTime, time, ACTION_MOVE, x, y)
75             injectMotionEvent(moveEvent)
76             if (withMotionEventInjectDelay) {
77                 SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS)
78             }
79         }
80     }
81 
82     /**
83      * Drag from [startX], [startY] to [endX], [endY] with a "hold" period after touching down
84      * and before moving.
85      */
holdToDragnull86     fun holdToDrag(startX: Int, startY: Int, endX: Int, endY: Int, steps: Int) {
87         val downTime = SystemClock.uptimeMillis()
88         actionDown(startX, startY, time = downTime)
89         SystemClock.sleep(100L) // Hold before dragging.
90         actionMove(
91             startX,
92             startY,
93             endX,
94             endY,
95             steps,
96             downTime,
97             withMotionEventInjectDelay = true
98         )
99         SystemClock.sleep(REGULAR_CLICK_LENGTH)
100         actionUp(startX, endX, downTime)
101     }
102 
injectMotionEventnull103     private fun injectMotionEvent(
104         action: Int,
105         x: Int,
106         y: Int,
107         downTime: Long = SystemClock.uptimeMillis(),
108         eventTime: Long = SystemClock.uptimeMillis()
109     ): MotionEvent {
110         val event = getMotionEvent(downTime, eventTime, action, x.toFloat(), y.toFloat())
111         injectMotionEvent(event)
112         return event
113     }
114 
injectMotionEventnull115     private fun injectMotionEvent(event: MotionEvent) {
116         instr.uiAutomation.injectInputEvent(event, true, false)
117     }
118 
getMotionEventnull119     private fun getMotionEvent(
120         downTime: Long,
121         eventTime: Long,
122         action: Int,
123         x: Float,
124         y: Float,
125     ): MotionEvent {
126         val properties = MotionEvent.PointerProperties.createArray(1)
127         properties[0].toolType = inputMethod.toolType
128         properties[0].id = 1
129 
130         val coords = MotionEvent.PointerCoords.createArray(1)
131         coords[0].x = x
132         coords[0].y = y
133         coords[0].pressure = 1f
134 
135         val event =
136             MotionEvent.obtain(
137                 downTime,
138                 eventTime,
139                 action,
140                 /* pointerCount= */ 1,
141                 properties,
142                 coords,
143                 /* metaState= */ 0,
144                 /* buttonState= */ 0,
145                 /* xPrecision = */ 1f,
146                 /* yPrecision = */ 1f,
147                 /* deviceId = */ 0,
148                 /* edgeFlags = */ 0,
149                 inputMethod.source,
150                 /* flags = */ 0
151             )
152         event.displayId = 0
153         return event
154     }
155 
156     companion object {
157         private const val MOTION_EVENT_INJECTION_DELAY_MILLIS = 5L
158         private const val REGULAR_CLICK_LENGTH = 100L
159     }
160 }