【问题标题】:How to handle MotionEvents (Gamepad Joystick Movement, etc) from AccessibilityService如何处理来自 AccessibilityService 的 MotionEvents(游戏手柄操纵杆移动等)
【发布时间】:2021-05-08 23:15:26
【问题描述】:

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

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

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

我要处理的 MotionEvents 如下:

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

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

问候, 0xB01b

【问题讨论】:

    标签: android xamarin.android accessibilityservice


    【解决方案1】:

    要创建可与输入法服务结合的辅助功能服务,可以尝试以下sn-p:

    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
        }
    }
    

    当用户在 Accessibility Setting Menu 中启动 TapService 时,它​​会调用函数 onServiceConnected 并将 tapService 对象分配给该服务。

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

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

    【讨论】:

      【解决方案2】:

      创建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 列表中的默认输入法和默认值后开始。

      【讨论】:

      • 我做到了,这很有效,我可以从键盘列表中设置它,我还将它翻译到我正在使用的 C# 框架。但是,由于某种原因,当我用断点检查它们时,当它们是键盘上的 keydown 和 keyup 事件时,这些方法没有触发?
      • 我不确定如何转换为 Xamarin android。您可以查看此示例以供参考:github.com/Vaikesh/CustomKeyboard
      • 我的意思是对于普通的 android sdk,当我按下键盘上的向下按钮时,我在 android studio 中设置的断点不会被击中,你有同样的问题吗?另外,感谢该链接
      • 嗨聪。还没有,但幸运的是它给了我时间来处理我需要的其他代码
      • 嗨,我的任何键盘/控制器输入都没有调用 onGenericMotionEvent、onKeyDown 和 onKeyUp。我怀疑 InputMethodService 仅在作为 IME 输入(例如在 TextView 中)调用时才处于活动状态。我不确定这是否是原因。你知道为什么它不适合我吗?
      【解决方案3】:

      在Accessibility Service中,你只能获取和过滤KeyEvents,MotionEvents不能在那个Service里面处理

      一种可能的方法是创建一个 Service 实现 InputMethodService 接口。它类似于创建虚拟键盘。您可以全局处理 Service 中的 KeyEvent 或 MotionEvent。 我的意思是,在启用 InputMethodService 后,您可以知道每个活动中的输入内容(例如更改键盘输入)。 然后,Accessibility Service 可用于调度 Touch 或 Swipe。

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

      但是,为了使它像实际的操纵杆映射器一样,我认为 Accessibility Service 无法帮助您按预期模拟 MotionEvent。也许只有 InjectInputEvent 可以完全按照您的意愿行事。但是涉及到一个老问题:INJECT_EVENTS 权限。

      【讨论】:

      • 所以我创建了另一个扩展 InputMethodService 的服务,然后在我的 AccessibilityService 中使用它?并使用InjectInputEvent() 而不是DispatchGesture()?
      • 没有。两个服务是独立的。在 AccessibilityService 中,可以使用带有 GestureDescription.StrokeDescription 的 dispatchGesture 来模拟 Tap 和 Swipe 动作。 InputMethodService 可用于处理您从操纵杆获取的输入(KeyEvent 和 MotionEvent)。在您用来处理它的方法中,您可以调用该方法在 AccessibilityService 中调度手势。关于 InjectInputEvent,我仍然不知道如何在没有 root 或特定权限的情况下注入它。
      • AccessibilityService 的另一个问题是无法创建一个 Path 来模拟 ACTION_UP 事件中的释放操作。我的意思是 moveto() 和 lineto() 可以在 ACTION_DOWN 中使用 willContinue 值点击并按住,但是如何在 ACTION_UP 中释放点击?
      • 为了释放水龙头,我正在使用continueStroke() 与没有持续时间的冲程,它似乎有点错误,但现在正在工作。此外,如果我使用同时按下的两个音量摇杆激活我的辅助功能服务,我可以通过编程方式激活 InputMethodService 吗?还是用户必须分别激活这两项服务?另外,我如何将它们称为从我的 InputMethodService 调度手势的方法,我不能有一个静态方法,我不确定我是否可以只制作一个对象。
      • InputMethodService 也需要单独激活,但不需要像无障碍服务那样每次启动都激活。它类似于虚拟键盘,因此如果您将默认输入法更改为此,用户无需再次激活它。
      【解决方案4】:

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

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

      【讨论】:

      • 我正在开发一个键盘映射器,仅用于使用游戏手柄、键盘、鼠标等映射触摸和其他手势。我认为这对于一般人来说更容易,这样他们就可以操纵他们的电话更容易。操纵杆可用于为残障人士将光标映射到屏幕上,并可用于通过鼠标和键盘复制桌面环境。
      猜你喜欢
      • 1970-01-01
      • 2017-12-09
      • 1970-01-01
      • 2022-12-09
      • 2019-12-03
      • 1970-01-01
      • 2022-11-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多