1 /* 2 * Copyright (C) 2023 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 android.input.cts 18 19 import android.cts.input.EventVerifier 20 import android.graphics.Point 21 import android.graphics.PointF 22 import android.view.InputDevice 23 import android.view.MotionEvent 24 import androidx.test.ext.junit.runners.AndroidJUnit4 25 import androidx.test.filters.MediumTest 26 import androidx.test.platform.app.InstrumentationRegistry 27 import com.android.cts.input.UinputDrawingTablet 28 import com.android.cts.input.UinputTouchDevice 29 import com.android.cts.input.VirtualDisplayActivityScenario 30 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.DEFAULT_HEIGHT 31 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.DEFAULT_WIDTH 32 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.ORIENTATION_0 33 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.ORIENTATION_180 34 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.ORIENTATION_270 35 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.ORIENTATION_90 36 import com.android.cts.input.inputeventmatchers.withCoords 37 import com.android.cts.input.inputeventmatchers.withMotionAction 38 import com.android.cts.input.inputeventmatchers.withSource 39 import com.android.cts.input.inputeventmatchers.withToolType 40 import org.hamcrest.Matchers.allOf 41 import org.junit.After 42 import org.junit.Assert.assertEquals 43 import org.junit.Before 44 import org.junit.Rule 45 import org.junit.Test 46 import org.junit.rules.TestName 47 import org.junit.runner.RunWith 48 49 @MediumTest 50 @RunWith(AndroidJUnit4::class) 51 class DrawingTabletTest { 52 private lateinit var drawingTablet: UinputTouchDevice 53 private lateinit var verifier: EventVerifier 54 55 @get:Rule 56 val testName = TestName() 57 @get:Rule 58 val virtualDisplayRule = VirtualDisplayActivityScenario.Rule<CaptureEventActivity>(testName) 59 60 @Before setUpnull61 fun setUp() { 62 drawingTablet = UinputDrawingTablet( 63 InstrumentationRegistry.getInstrumentation(), virtualDisplayRule.virtualDisplay.display) 64 verifier = EventVerifier(virtualDisplayRule.activity::getInputEvent) 65 } 66 67 @After tearDownnull68 fun tearDown() { 69 if (this::drawingTablet.isInitialized) { 70 drawingTablet.close() 71 } 72 } 73 74 @Test testCoordinateMappingOrientation0null75 fun testCoordinateMappingOrientation0() { 76 virtualDisplayRule.runInDisplayOrientation(ORIENTATION_0) { 77 verifyTaps(EXPECTED_POINTS_UNROTATED, ::transformForUnrotatedDrawingTablet) 78 } 79 } 80 81 @Test testCoordinateMappingOrientation90null82 fun testCoordinateMappingOrientation90() { 83 virtualDisplayRule.runInDisplayOrientation(ORIENTATION_90) { 84 verifyTaps(EXPECTED_POINTS_ROTATED, ::transformForRotatedDrawingTablet) 85 } 86 } 87 88 @Test testCoordinateMappingOrientation180null89 fun testCoordinateMappingOrientation180() { 90 virtualDisplayRule.runInDisplayOrientation(ORIENTATION_180) { 91 verifyTaps(EXPECTED_POINTS_UNROTATED, ::transformForUnrotatedDrawingTablet) 92 } 93 } 94 95 @Test testCoordinateMappingOrientation270null96 fun testCoordinateMappingOrientation270() { 97 virtualDisplayRule.runInDisplayOrientation(ORIENTATION_270) { 98 verifyTaps(EXPECTED_POINTS_ROTATED, ::transformForRotatedDrawingTablet) 99 } 100 } 101 102 @Test testHovernull103 fun testHover() { 104 val pointerId = 0 105 val commonMatcher = 106 allOf( 107 withSource(InputDevice.SOURCE_STYLUS or InputDevice.SOURCE_MOUSE), 108 withToolType(MotionEvent.TOOL_TYPE_STYLUS), 109 ) 110 111 // Inject and verify HOVER_ENTER 112 drawingTablet.sendBtnTouch(false) 113 drawingTablet.sendDown(pointerId, INJECTION_POINTS[0]) 114 drawingTablet.sync() 115 116 verifier.assertReceivedMotion( 117 allOf( 118 withMotionAction(MotionEvent.ACTION_HOVER_ENTER), 119 withCoords(transformForUnrotatedDrawingTablet(INJECTION_POINTS[0])!!), 120 commonMatcher 121 ) 122 ) 123 verifier.assertReceivedMotion( 124 allOf( 125 withMotionAction(MotionEvent.ACTION_HOVER_MOVE), 126 withCoords(transformForUnrotatedDrawingTablet(INJECTION_POINTS[0])!!), 127 commonMatcher 128 ) 129 ) 130 131 // Inject and verify HOVER_MOVE 132 drawingTablet.sendMove(pointerId, INJECTION_POINTS[1]) 133 drawingTablet.sync() 134 135 verifier.assertReceivedMotion( 136 allOf( 137 withMotionAction(MotionEvent.ACTION_HOVER_MOVE), 138 withCoords(transformForUnrotatedDrawingTablet(INJECTION_POINTS[1])!!), 139 commonMatcher 140 ) 141 ) 142 143 // Inject and verify HOVER_EXIT 144 drawingTablet.sendUp(pointerId) 145 drawingTablet.sync() 146 147 verifier.assertReceivedMotion( 148 allOf( 149 withMotionAction(MotionEvent.ACTION_HOVER_EXIT), 150 withCoords(transformForUnrotatedDrawingTablet(INJECTION_POINTS[1])!!), 151 commonMatcher 152 ) 153 ) 154 } 155 156 /** 157 * Taps at each point in [INJECTION_POINTS] and ensures that the event is received at the 158 * corresponding point in [expectedPoints]. 159 */ verifyTapsnull160 private fun verifyTaps( 161 expectedPoints: Array<PointF?>, 162 transformToExpectedPoint: (Point) -> PointF? 163 ) { 164 for (i in INJECTION_POINTS.indices) { 165 val pointerId = 0 166 drawingTablet.sendBtnTouch(true) 167 drawingTablet.sendDown(pointerId, INJECTION_POINTS[i]) 168 drawingTablet.sync() 169 170 drawingTablet.sendBtnTouch(false) 171 drawingTablet.sendUp(pointerId) 172 drawingTablet.sync() 173 174 val expected = expectedPoints[i] 175 // Ensure the hard-coded expected points and the transformation function agree for the 176 // injected point. 177 assertEquals(transformToExpectedPoint(INJECTION_POINTS[i]), expected) 178 179 if (expected != null) { 180 verifier.assertReceivedMotion(allOf( 181 withMotionAction(MotionEvent.ACTION_DOWN), 182 withSource(InputDevice.SOURCE_STYLUS or InputDevice.SOURCE_MOUSE), 183 withToolType(MotionEvent.TOOL_TYPE_STYLUS), 184 withCoords(expected) 185 )) 186 187 verifier.assertReceivedMotion( 188 allOf(withMotionAction(MotionEvent.ACTION_MOVE), withCoords(expected)) 189 ) 190 verifier.assertReceivedMotion( 191 allOf(withMotionAction(MotionEvent.ACTION_UP), withCoords(expected)) 192 ) 193 } 194 } 195 virtualDisplayRule.activity.assertNoEvents() 196 } 197 198 private companion object { 199 const val EPSILON = 0.001f 200 201 /** Coordinates in the drawing tablet injected in [verifyTaps]. */ 202 val INJECTION_POINTS = arrayOf( 203 Point(0, 0), // top-left corner of drawing tablet 204 Point(DEFAULT_WIDTH - 1, 0), // top-right corner of drawing tablet 205 Point(DEFAULT_WIDTH - 1, DEFAULT_HEIGHT - 1), // bottom-right corner of drawing tablet 206 Point(0, DEFAULT_HEIGHT - 1), // bottom-left corner of drawing tablet 207 Point(200, 200), // point inside drawing tablet 208 Point(200, 600), // point inside drawing tablet 209 ) 210 211 /** 212 * The points that each of the [INJECTION_POINTS] are expected to map to when the display 213 * is rotated to 0 or 180 degrees. 214 */ 215 val EXPECTED_POINTS_UNROTATED = arrayOf<PointF?>( 216 PointF(INJECTION_POINTS[0]), 217 PointF(INJECTION_POINTS[1]), 218 PointF(INJECTION_POINTS[2]), 219 PointF(INJECTION_POINTS[3]), 220 PointF(INJECTION_POINTS[4]), 221 PointF(INJECTION_POINTS[5]), 222 ) 223 transformForUnrotatedDrawingTabletnull224 fun transformForUnrotatedDrawingTablet(p: Point): PointF? { 225 return PointF(p) 226 } 227 228 /** 229 * The points that each of the [INJECTION_POINTS] are expected to map to when the display 230 * is rotated to 90 or 270 degrees. 231 */ 232 val EXPECTED_POINTS_ROTATED = arrayOf<PointF?>( 233 PointF(0f, 0f), 234 PointF(798.3333f, 0f), 235 null, 236 null, 237 PointF(333.3333f, 333.3333f), 238 null, 239 ) 240 transformForRotatedDrawingTabletnull241 fun transformForRotatedDrawingTablet(p: Point): PointF? { 242 val rotatedScale = DEFAULT_HEIGHT.toFloat() / DEFAULT_WIDTH.toFloat() 243 val scaled = PointF(p.x * rotatedScale, p.y * rotatedScale) 244 if (scaled.x < 0 || scaled.x >= DEFAULT_HEIGHT.toFloat() || 245 scaled.y < 0 || scaled.y >= DEFAULT_WIDTH.toFloat()) { 246 return null 247 } 248 return scaled 249 } 250 } 251 } 252