• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.cts.input
18 
19 import android.app.Instrumentation
20 import android.content.Context
21 import android.graphics.Point
22 import android.hardware.input.InputManager
23 import android.util.Size
24 import android.view.Display
25 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
26 import org.json.JSONArray
27 import org.json.JSONObject
28 
29 /**
30  * Helper class for configuring and interacting with a [UinputDevice] that uses the evdev
31  * multitouch protocol.
32  */
33 class UinputTouchDevice(
34     instrumentation: Instrumentation,
35     display: Display,
36     size: Size,
37     private val rawResource: Int,
38     private val source: Int,
39 ) :
40     AutoCloseable {
41 
42     private val uinputDevice: UinputDevice
43     private lateinit var port: String
44     private val inputManager: InputManager
45 
46     init {
47         uinputDevice = createDevice(instrumentation, size)
48         inputManager = instrumentation.targetContext.getSystemService(InputManager::class.java)!!
49         associateWith(display)
50     }
51 
injectEventnull52     private fun injectEvent(events: IntArray) {
53         uinputDevice.injectEvents(events.joinToString(prefix = "[", postfix = "]",
54                 separator = ","))
55     }
56 
sendBtnTouchnull57     fun sendBtnTouch(isDown: Boolean) {
58         injectEvent(intArrayOf(EV_KEY, BTN_TOUCH, if (isDown) 1 else 0))
59     }
60 
sendBtnnull61     fun sendBtn(btnCode: Int, isDown: Boolean) {
62         injectEvent(intArrayOf(EV_KEY, btnCode, if (isDown) 1 else 0))
63     }
64 
sendDownnull65     fun sendDown(id: Int, location: Point, toolType: Int? = null) {
66         injectEvent(intArrayOf(EV_ABS, ABS_MT_SLOT, id))
67         injectEvent(intArrayOf(EV_ABS, ABS_MT_TRACKING_ID, id))
68         if (toolType != null) injectEvent(intArrayOf(EV_ABS, ABS_MT_TOOL_TYPE, toolType))
69         injectEvent(intArrayOf(EV_ABS, ABS_MT_POSITION_X, location.x))
70         injectEvent(intArrayOf(EV_ABS, ABS_MT_POSITION_Y, location.y))
71         injectEvent(intArrayOf(EV_SYN, SYN_REPORT, 0))
72     }
73 
sendMovenull74     fun sendMove(id: Int, location: Point) {
75         // Use same events of down.
76         sendDown(id, location)
77     }
78 
sendUpnull79     fun sendUp(id: Int) {
80         injectEvent(intArrayOf(EV_ABS, ABS_MT_SLOT, id))
81         injectEvent(intArrayOf(EV_ABS, ABS_MT_TRACKING_ID, INVALID_TRACKING_ID))
82         injectEvent(intArrayOf(EV_SYN, SYN_REPORT, 0))
83     }
84 
sendToolTypenull85     fun sendToolType(id: Int, toolType: Int) {
86         injectEvent(intArrayOf(EV_ABS, ABS_MT_SLOT, id))
87         injectEvent(intArrayOf(EV_ABS, ABS_MT_TOOL_TYPE, toolType))
88         injectEvent(intArrayOf(EV_SYN, SYN_REPORT, 0))
89     }
90 
readRawResourcenull91     private fun readRawResource(context: Context): String {
92         return context.resources
93             .openRawResource(rawResource)
94             .bufferedReader().use { it.readText() }
95     }
96 
createDevicenull97     private fun createDevice(instrumentation: Instrumentation, size: Size): UinputDevice {
98         val json = JSONObject(readRawResource(instrumentation.targetContext))
99         val resourceDeviceId: Int = json.getInt("id")
100         val vendorId = json.getInt("vid")
101         val productId = json.getInt("pid")
102         port = json.getString("port")
103 
104         // Use the display size to set maximum values
105         val absInfo: JSONArray = json.getJSONArray("abs_info")
106         for (i in 0 until absInfo.length()) {
107             val item = absInfo.getJSONObject(i)
108             if (item.get("code") == ABS_MT_POSITION_X) {
109                 item.getJSONObject("info").put("maximum", size.width - 1)
110             }
111             if (item.get("code") == ABS_MT_POSITION_Y) {
112                 item.getJSONObject("info").put("maximum", size.height - 1)
113             }
114         }
115 
116         // Create the uinput device.
117         val registerCommand = json.toString()
118         return UinputDevice(instrumentation, resourceDeviceId,
119                 vendorId, productId, source, registerCommand)
120     }
121 
associateWithnull122     private fun associateWith(display: Display) {
123         runWithShellPermissionIdentity(
124                 { inputManager.addUniqueIdAssociation(port, display.uniqueId!!) },
125                 "android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY")
126     }
127 
closenull128     override fun close() {
129         runWithShellPermissionIdentity(
130                 { inputManager.removeUniqueIdAssociation(port) },
131                 "android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY")
132         uinputDevice.close()
133     }
134 
135     companion object {
136         const val EV_SYN = 0
137         const val EV_KEY = 1
138         const val EV_ABS = 3
139         const val ABS_MT_SLOT = 0x2f
140         const val ABS_MT_POSITION_X = 0x35
141         const val ABS_MT_POSITION_Y = 0x36
142         const val ABS_MT_TOOL_TYPE = 0x37
143         const val ABS_MT_TRACKING_ID = 0x39
144         const val BTN_TOUCH = 0x14a
145         const val SYN_REPORT = 0
146         const val MT_TOOL_FINGER = 0
147         const val MT_TOOL_PEN = 1
148         const val MT_TOOL_PALM = 2
149         const val INVALID_TRACKING_ID = -1
150     }
151 }
152