如何从 AccessibilityService 处理 MotionEvents游戏手柄操纵杆移动等

问题描述

我有一个 AccessibilityService,它接收来自游戏控制器(ps5 控制器、xBox 控制器等)的输入。

我使用 onKeyEvent() 方法来处理按钮的按下和释放,所以我可以轻松处理这些。我面临的问题是如何处理操纵杆移动和顶部触发器按下,因为我不知道如何通过 AccessibilityService 处理它们。

通常,我会简单地使用 onGenericMotionEvent() 来处理这些 MotionEvents,但不幸的是 AccessibilityService 没有为我提供这样的方法。我已经看了将近 3 周的文档和官方代码实验室,但没有运气,如果有人能告诉我如何通过 AccessibilityService 处理 MotionEvents,我会很欣慰。

我想要处理的 MotionEvents 是这些:

AXIS_X,AXIS_Y,AXIS_RZ,AXIS_RY,AXIS_RX,AXIS_HAT_X,AXIS_HAT_Y,AXIS_LTRIGGER,AXIS_RTRIGGER,AXIS_BRAKE,AXIS_GAS

可能还有其他控制器取决于控制器,但这些是我需要处理来自控制器的输入的主要控制器。

问候, 0xB01b

解决方法

要创建InputMethodService,可以参考this官方文档。

首先,在Manifest.xml中添加服务:

<service android:name=".IMService"
        android:permission="android.permission.BIND_INPUT_METHOD">
        <intent-filter>
            <action android:name="android.view.InputMethod"/>
        </intent-filter>
        <meta-data
            android:name="android.view.im"
            android:resource="@xml/method"/>
    </service>

接下来需要在res/xml/文件夹下创建method.xml

    <?xml version="1.0" encoding="utf-8"?>
<input-method xmlns:android="http://schemas.android.com/apk/res/android"
    android:isDefault="false"
    android:settingsActivity=".MainActivity">
    <subtype
        android:icon="@mipmap/ic_launcher"
        android:imeSubtypeLocale="en_US"
        android:imeSubtypeMode="keyboard"
        android:label="@string/app_name"/>
</input-method>

在这种情况下不需要键盘布局。

然后,创建实现 InputMethodService 的类 IMService

以下代码是用 Kotlin 编写的(例如):

class IMService: InputMethodService() {
companion object {
    private const val TAG = "IMService"
}

override fun onKeyDown(keyCode: Int,event: KeyEvent): Boolean {
    if(event.action == KeyEvent.ACTION_DOWN) {
        if (event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD) {
            // process key down event here
            return true   // return true if you want the key event to be filtered
        }
    }
    return super.onKeyDown(keyCode,event)
}

override fun onKeyUp(keyCode: Int,event: KeyEvent): Boolean {
    if(event.action == KeyEvent.ACTION_UP) {
        if (event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD) {
            // process key up event here
            return true
        }
    }
    return super.onKeyUp(keyCode,event)
}

override fun onGenericMotionEvent(event: MotionEvent): Boolean {
    if(event.action == MotionEvent.ACTION_MOVE) {
        if (event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK) {
            // process motion event here
            return true
        }
    }
    return super.onGenericMotionEvent(event)
}
}

在Settings/../Language and keyboard/Keyboard列表和默认中启用并更改默认输入法后,服务生命周期开始。

,

API 不支持这些,因为 AccessibilityServices 只能过滤 KeyEvents,而你所说的按钮不会产生 KeyEvents。

你能解释一下你在建造什么吗?了解添加此 API 对残障人士生活的影响将有助于我们确定这项工作以及我们正在计划的其他工作的优先级。

,

在 Accessibility Service 中,您只能获取和过滤 KeyEvents,无法在该 Service 内部处理 MotionEvents

一种可能的方法是创建一个 Service 实现 InputMethodService 接口。它类似于创建一个虚拟键盘。您可以全局处理 Service 中的 KeyEvent 或 MotionEvent。 我的意思是,在启用 InputMethodService(例如更改键盘输入)后,您可以知道每个活动中的输入是什么。 然后,无障碍服务可用于发送触摸或滑动。

这种组合(InputMethodService + AccessibilityService)使您能够将操纵杆映射到全局触摸和滑动。

但是,为了让它像实际的操纵杆映射器一样工作,我认为辅助功能服务无法帮助您像预期的那样模拟 MotionEvent。也许只有 InjectInputEvent 可以完全满足您的要求。但是涉及到一个老问题:INJECT_EVENTS 权限。

,

要创建可与输入法服务结合的无障碍服务,您可以尝试以下代码段:

var tapService: TapService? = null

class TapService : AccessibilityService() {
    companion object {
        private const val TAG = "TapService"
    }

    override fun onAccessibilityEvent(event: AccessibilityEvent?) { }

    override fun onInterrupt() { }

    override fun onServiceConnected() {
        super.onServiceConnected()
        Log.d(TAG,"onServiceConnected")

        tapService = this

        startActivity(Intent(this,MainActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
    }

    override fun onUnbind(intent: Intent?): Boolean {
        Log.d(TAG,"onUnbind")
        tapService = null
        return super.onUnbind(intent)
    }

    override fun onDestroy() {
        Log.d(TAG,"onDestroy")
        tapService = null
        super.onDestroy()
    }

    fun tap(x: Int,y: Int,hold: Boolean) {
        // create path and build the GestureDescription then dispatch it
    }

    fun swipe(fromX: Int,fromY: Int,toX: Int,toY: Int,duration: Long) {
        // similar to tap function
    }
}

当用户在辅助功能设置菜单中启动 TapService 时,它​​会调用函数onServiceConnected 并将 tapService 对象分配给该服务。

因此,您可以在输入法服务的其他地方调用方法 tapService.tap()tapService.swipe() 来注入触摸动作。

但是,由于 Accessibility Service 的限制,触摸动作的模拟并不如预期。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...