blob: 3eb49d9da8ed0b550a0697c7eda3f1890e3d8071 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cts.input
import android.app.Instrumentation
import android.content.Context
import android.graphics.Point
import android.hardware.input.InputManager
import android.util.Size
import android.view.Display
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import org.json.JSONArray
import org.json.JSONObject
/**
* Helper class for configuring and interacting with a [UinputDevice] that uses the evdev
* multitouch protocol.
*/
class UinputTouchDevice(
instrumentation: Instrumentation,
display: Display,
size: Size,
private val rawResource: Int,
private val source: Int,
) :
AutoCloseable {
private val uinputDevice: UinputDevice
private lateinit var port: String
private val inputManager: InputManager
init {
uinputDevice = createDevice(instrumentation, size)
inputManager = instrumentation.targetContext.getSystemService(InputManager::class.java)!!
associateWith(display)
}
private fun injectEvent(events: IntArray) {
uinputDevice.injectEvents(events.joinToString(prefix = "[", postfix = "]",
separator = ","))
}
fun sendBtnTouch(isDown: Boolean) {
injectEvent(intArrayOf(EV_KEY, BTN_TOUCH, if (isDown) 1 else 0))
}
fun sendBtn(btnCode: Int, isDown: Boolean) {
injectEvent(intArrayOf(EV_KEY, btnCode, if (isDown) 1 else 0))
}
fun sendDown(id: Int, location: Point, toolType: Int? = null) {
injectEvent(intArrayOf(EV_ABS, ABS_MT_SLOT, id))
injectEvent(intArrayOf(EV_ABS, ABS_MT_TRACKING_ID, id))
if (toolType != null) injectEvent(intArrayOf(EV_ABS, ABS_MT_TOOL_TYPE, toolType))
injectEvent(intArrayOf(EV_ABS, ABS_MT_POSITION_X, location.x))
injectEvent(intArrayOf(EV_ABS, ABS_MT_POSITION_Y, location.y))
injectEvent(intArrayOf(EV_SYN, SYN_REPORT, 0))
}
fun sendMove(id: Int, location: Point) {
// Use same events of down.
sendDown(id, location)
}
fun sendUp(id: Int) {
injectEvent(intArrayOf(EV_ABS, ABS_MT_SLOT, id))
injectEvent(intArrayOf(EV_ABS, ABS_MT_TRACKING_ID, INVALID_TRACKING_ID))
injectEvent(intArrayOf(EV_SYN, SYN_REPORT, 0))
}
fun sendToolType(id: Int, toolType: Int) {
injectEvent(intArrayOf(EV_ABS, ABS_MT_SLOT, id))
injectEvent(intArrayOf(EV_ABS, ABS_MT_TOOL_TYPE, toolType))
injectEvent(intArrayOf(EV_SYN, SYN_REPORT, 0))
}
private fun readRawResource(context: Context): String {
return context.resources
.openRawResource(rawResource)
.bufferedReader().use { it.readText() }
}
private fun createDevice(instrumentation: Instrumentation, size: Size): UinputDevice {
val json = JSONObject(readRawResource(instrumentation.targetContext))
val resourceDeviceId: Int = json.getInt("id")
val vendorId = json.getInt("vid")
val productId = json.getInt("pid")
port = json.getString("port")
// Use the display size to set maximum values
val absInfo: JSONArray = json.getJSONArray("abs_info")
for (i in 0 until absInfo.length()) {
val item = absInfo.getJSONObject(i)
if (item.get("code") == ABS_MT_POSITION_X) {
item.getJSONObject("info").put("maximum", size.width - 1)
}
if (item.get("code") == ABS_MT_POSITION_Y) {
item.getJSONObject("info").put("maximum", size.height - 1)
}
}
// Create the uinput device.
val registerCommand = json.toString()
return UinputDevice(instrumentation, resourceDeviceId,
vendorId, productId, source, registerCommand)
}
private fun associateWith(display: Display) {
runWithShellPermissionIdentity(
{ inputManager.addUniqueIdAssociation(port, display.uniqueId!!) },
"android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY")
}
override fun close() {
runWithShellPermissionIdentity(
{ inputManager.removeUniqueIdAssociation(port) },
"android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY")
uinputDevice.close()
}
companion object {
const val EV_SYN = 0
const val EV_KEY = 1
const val EV_ABS = 3
const val ABS_MT_SLOT = 0x2f
const val ABS_MT_POSITION_X = 0x35
const val ABS_MT_POSITION_Y = 0x36
const val ABS_MT_TOOL_TYPE = 0x37
const val ABS_MT_TRACKING_ID = 0x39
const val BTN_TOUCH = 0x14a
const val SYN_REPORT = 0
const val MT_TOOL_FINGER = 0
const val MT_TOOL_PEN = 1
const val MT_TOOL_PALM = 2
const val INVALID_TRACKING_ID = -1
}
}