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 }