如何使用 Android 中的辅助功能服务访问弹出窗口中的元素

问题描述 投票:0回答:1

我一直在开发辅助功能服务,以使用以下辅助功能配置来访问应用程序的元素数据,例如文本、描述、资源 ID 等:

<accessibility-service
    android:accessibilityEventTypes="typeViewClicked|typeViewLongClicked|typeViewScrolled"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagIncludeNotImportantViews|flagRetrieveInteractiveWindows|flagReportViewIds"
    android:canRetrieveWindowContent="true"
    xmlns:android="http://schemas.android.com/apk/res/android"/>

通过此设置,我可以在与应用程序中的元素交互时捕获事件。但是,当弹出窗口打开并且我尝试与弹出窗口中的按钮(例如搜索按钮、星号消息、回复按钮)进行交互时, onAccessibilityEvent() 函数中不会触发任何事件。 (弹出窗口如所附屏幕截图所示)。

enter image description here

我还尝试通过将 android:accessibilityFlags 更改为来修改配置:

android:accessibilityFlags="flagRequestTouchExplorationMode|flagIncludeNotImportantViews|flagRetrieveInteractiveWindows|flagReportViewIds"

但是,我仍然只收到屏幕的根元素。我知道此功能是可能的,因为像 TalkBack 这样的服务可以轻松处理此类交互。为了实现所需的功能我可能会缺少什么?

bottomSheet 代码:

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="2dp">

        <!--image view for displaying course image-->
        <ImageView
            android:id="@+id/idIVCourse"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_margin="10dp" />

        <!--text view for displaying course name-->
        <TextView
            android:id="@+id/idTVCourseName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:layout_toEndOf="@id/idIVCourse"
            android:layout_toRightOf="@id/idIVCourse"
            android:text="DSA Self Paced Course"
            android:textColor="@color/black"
            android:textSize="18sp"
            android:textStyle="bold" />

        <!--text view for displaying course tracks-->
        <TextView
            android:id="@+id/idTVCourseTracks"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/idTVCourseName"
            android:layout_marginTop="10dp"
            android:layout_toEndOf="@id/idIVCourse"
            android:layout_toRightOf="@id/idIVCourse"
            android:text="Course Tracks : 30"
            android:textColor="@color/black"
            android:textSize="15sp" />

        <!--text view for displaying course duration-->
        <TextView
            android:id="@+id/idTVCourseDuration"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/idTVCourseTracks"
            android:layout_marginTop="10dp"
            android:layout_toEndOf="@id/idIVCourse"
            android:layout_toRightOf="@id/idIVCourse"
            android:text="Course Duration : 4 Months"
            android:textColor="@color/black"
            android:textSize="15sp" />

        <!--button for dismissing our dialog-->
        <Button
            android:id="@+id/idBtnDismiss"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/idIVCourse"
            android:layout_margin="10dp"
            android:text="Dismiss dialog"
            android:textAllCaps="false" />

    </RelativeLayout>

</androidx.cardview.widget.CardView>

从活动中打开底页的代码:

val openBottomSheetButton: Button = findViewById(R.id.open_bottom_sheet_button)
        
        openBottomSheetButton.setOnClickListener {
            val dialog = BottomSheetDialog(this)
            
            val view = layoutInflater.inflate(R.layout.bottomsheet, null)
            
            val btnClose = view.findViewById<Button>(R.id.idBtnDismiss)
            
            btnClose.setOnClickListener {

                dialog.dismiss()
            }

            dialog.setCancelable(false)

            dialog.setContentView(view)
            
            dialog.show()
        }
android mobile accessibility accessibilityservice accessibility-api
1个回答
1
投票

编辑

然后我回去回顾了这个问题,最重要的部分:

我尝试与弹出窗口中的按钮(例如搜索按钮、星标消息、回复按钮)进行交互... onAccessibilityEvent() 函数中没有触发任何事件

我的答案表明我们绝对可以与弹出按钮进行交互,但是一旦打开弹出窗口,它就不会处理事件注册表,例如TYPE_VIEW_CLICKED

我确实检查过,我们能够检索 WINDOW_STATE_CHANGE 上的所有内容:

    private fun AccessibilityNodeInfo.forAllChildren(depth:Int = 0, fn:(AccessibilityNodeInfo, Int) -> Unit) {
        for (index in 0..<this.childCount){
            this.getChild(index)?.let {
                fn(it, depth)
                it.forAllChildren(depth+1, fn)
            }
        }
    }

    // inside onAccessibilityEvent
    if (event?.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
        rootInActiveWindow.forAllChildren { node, depth ->
            Log("${" ".repeat(depth * 2)} ${node.viewIdResourceName} ${node.text}")
        }
    }

但是一旦点击视图我们就看不到了:

    // inside onAccessibilityEvent
    if (event?.eventType == AccessibilityEvent.TYPE_VIEW_CLICKED) {
        Log("onAccessibilityEvent CLICKED ${event.className ?: " "}")
    }

我稍后会花更多时间来解决这个问题,但这是一个问题,我们可能需要在问题跟踪器上提出错误

原答案

我花了一些时间对此进行了更多研究,说实话我仍然有点困惑,但我已经成功创建了一个小型演示。请理解,我仍然不知道我们为什么要这样做的实际意义。我希望至少进行一次讨论,这样我们才能找到答案。这里的评论根本不够。

我目前与此问题相关的问题:

    这对残疾人有何帮助?
  1. 这是否与辅助技术(例如对讲、语音访问等)结合使用?
但我不想继续提问,而是想根据我所掌握的信息尝试一下。现在这就是我所做的:

    使用尽可能多的代码编写一个打开底部工作表的自定义视图应用程序
  1. 编写跟踪用户移动的自定义辅助功能服务,如果显示底部工作表,看看是否可以找到关闭按钮并单击它

accessibility_service_config.xml

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/accessibility_service_description" android:accessibilityEventTypes="typeAllMask" android:accessibilityFlags="flagIncludeNotImportantViews|flagRetrieveInteractiveWindows|flagReportViewIds" android:accessibilityFeedbackType="feedbackGeneric" android:notificationTimeout="100" android:canRetrieveWindowContent="true" />

SampleAccessibilityService.kt

class SampleAccessibilityService: AccessibilityService() { override fun onInterrupt() { Log("onInterrupt") } override fun onAccessibilityEvent(event: AccessibilityEvent?) { Log("---------------------------------------------------------") Log("onAccessibilityEvent CLASS ${event?.className ?: " "} -- ${BottomSheetDialog::class.java.name}") Log("onAccessibilityEvent EVENT ${event?.eventType ?: " "}") Log("onAccessibilityEvent TEXT ${event?.text ?: " "}") if (event?.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && event.className == BottomSheetDialog::class.java.name) { rootInActiveWindow.findAccessibilityNodeInfosByText("Dismiss dialog").forEach { node -> Log(" onAccessibilityEvent CLICKING A NODE") node.performAction(AccessibilityNodeInfo.ACTION_CLICK) } } } override fun onServiceConnected() { Log("onServiceConnected") serviceInfo.apply { // I have found you have to do this in the config and onServiceConnected. Mine are actually mismatched but the service worked fine. ¯\_(ツ)_/¯ eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED or AccessibilityEvent.TYPE_VIEW_FOCUSED notificationTimeout = 100 } } } object Log { operator fun invoke(text: String) { android.util.Log.d("SAMPLEALLYSERVICE","SAMPLEALLYSERVICE: $text") } }
当我从“设置”菜单启用此服务时:

  1. Settings -> Accessibility -> Sample Accessibility Service -> Use Sample Accessibility Service
    
    
  2. 打开我的测试应用程序
  3. 打开底页
我可以看到它立即关闭了。


我真的希望这能为您带来一些启发 - 您必须在辅助功能服务中使用事件触发器才能知道某些方面发生了变化,例如

TYPE_WINDOW_STATE_CHANGED - 然后我通过文本而不是 ID 查找节点,因为 ID 是将会变得不那么流行(Jetpack Compose),我可以通过可访问性节点发送交互命令:

node.performAction(AccessibilityNodeInfo.ACTION_CLICK)


    

© www.soinside.com 2019 - 2024. All rights reserved.