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