如何使用辅助功能服务"为用户采取行动"?

and*_*per 12 android accessibilityservice

背景

遥想几年前,我问的TeamViewer如何允许用户控制设备,而无需与设备正常交互.我被告知这是一个特殊的"后门",制造商专门为这个应用程序提供,并且只能使用root权限用于其他应用程序.

看到像"飞行模式快捷方式"这样的应用程序允许切换飞行模式,通过自动导航到其屏幕并切换开关,它让我意识到这种情况已经改变.

问题

文档中说:

从Android 4.0(API级别14)开始,辅助功能服务可以代表用户执行操作,包括更改输入焦点和选择(激活)用户界面元素.在Android 4.1(API级别16)中,操作范围已扩展为包括滚动列表和与文本字段交互.辅助功能服务还可以执行全局操作,例如导航到主屏幕,按"返回"按钮,打开通知屏幕和最近的应用程序列表.Android 4.1还包括一种新型焦点Accessibilty Focus,它使所有可见元素都可由辅助功能服务选择.

这些新功能使辅助功能服务的开发人员可以创建替代导航模式,如手势导航,并为残障用户提供对其Android设备的更好控制.

但是没有关于如何使用它的更多信息.只有我发现的样本位于底部,但这些样本非常陈旧,是apiDemos包的一部分.

这个问题

如何创建可以查询,聚焦,单击,输入文本以及执行其他UI相关操作的服务?

Nik*_*ski 13

通过实施AccessibilityService(https://developer.android.com/training/accessibility/service.html),您可以访问这些功能.

您可以检查或对用户最后交互的元素执行操作,也可以检查当前活动的整个应用程序.

通过实现拦截用户事件onAccessibilityEvent(AccessibilityEvent event),在这里您可以检索虚拟视图(表示原始视图),event.getSource()然后使用getClassName()getText()在文档中找到的任何内容进行检查.

通过调用getRootInActiveWindow()并遍历virtaul视图树来检查整个应用程序getRootInActiveWindow().getChild(index).

both getRootInActiveWindow()event.getSource()return AccessibilityNodeInfo,你可以在其上调用performAction(action)并执行类似Click,Set Text等操作.

示例:Play商店

一旦您打开了Play商店应用,搜索"facebook"应用并在Play商店中打开它的页面.

    @Override
    public void onAccessibilityEvent(final AccessibilityEvent event) {

        AccessibilityNodeInfo rootInActiveWindow = getRootInActiveWindow();
        //Inspect app elements if ready
        if (rootInActiveWindow != null) {
            //Search bar is covered with textview which need to be clicked
            List<AccessibilityNodeInfo> searchBarIdle = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.android.vending:id/search_box_idle_text");
            if (searchBarIdle.size() > 0) {
                AccessibilityNodeInfo searchBar = searchBarIdle.get(0);
                searchBar.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            }
            //Check is search bar is visible
            List<AccessibilityNodeInfo> searchBars = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.android.vending:id/search_box_text_input");
            if (searchBars.size() > 0) {
                AccessibilityNodeInfo searchBar = searchBars.get(0);
                //Check is searchbar have the required text, if not set the text
                if (searchBar.getText() == null || !searchBar.getText().toString().equalsIgnoreCase("facebook")) {
                    Bundle args = new Bundle();
                    args.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "facebook");
                    searchBar.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args);
                } else {
                    //There is no way to press Enter to perform search, so find corresponding suggestion and click
                    List<AccessibilityNodeInfo> searchSuggestions = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.android.vending:id/suggest_text");
                    for (AccessibilityNodeInfo suggestion : searchSuggestions) {
                        if(suggestion.getText().toString().equals("Facebook")) {
                            //We found textview, but its not clickable, so we should perform the click on the parent
                            AccessibilityNodeInfo clickableParent = suggestion.getParent();
                            clickableParent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                        }
                    }
                }
            }


        }
   }
Run Code Online (Sandbox Code Playgroud)

编辑:完整代码如下:

MyAccessibilityService

public class MyAccessibilityService extends AccessibilityService {

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("MyAccessibilityService", "onCreate");
    }

    @Override
    public void onAccessibilityEvent(final AccessibilityEvent event) {
        Log.d("MyAccessibilityService", "onAccessibilityEvent");
        AccessibilityNodeInfo rootInActiveWindow = getRootInActiveWindow();
        //Inspect app elements if ready
        if (rootInActiveWindow != null) {
            //Search bar is covered with textview which need to be clicked
            List<AccessibilityNodeInfo> searchBarIdle = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.android.vending:id/search_box_idle_text");
            if (searchBarIdle.size() > 0) {
                AccessibilityNodeInfo searchBar = searchBarIdle.get(0);
                searchBar.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            }
            //Check is search bar is visible
            List<AccessibilityNodeInfo> searchBars = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.android.vending:id/search_box_text_input");
            if (searchBars.size() > 0) {
                AccessibilityNodeInfo searchBar = searchBars.get(0);
                //Check is searchbar have the required text, if not set the text
                if (searchBar.getText() == null || !searchBar.getText().toString().equalsIgnoreCase("facebook")) {
                    Bundle args = new Bundle();
                    args.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "facebook");
                    searchBar.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args);
                } else {
                    //There is no way to press Enter to perform search, so find corresponding suggestion and click
                    List<AccessibilityNodeInfo> searchSuggestions = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.android.vending:id/suggest_text");
                    for (AccessibilityNodeInfo suggestion : searchSuggestions) {
                        if (suggestion.getText().toString().equals("Facebook")) {
                            //We found textview, but its not clickable, so we should perform the click on the parent
                            AccessibilityNodeInfo clickableParent = suggestion.getParent();
                            clickableParent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                        }
                    }
                }
            }
        }
    }

    @Override
    public void onInterrupt() {
    }
}
Run Code Online (Sandbox Code Playgroud)

AndroidManifest.xml中

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.findfacebookapp">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <service
            android:name=".MyAccessibilityService"
            android:label="@string/accessibility_service_label"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService"/>
            </intent-filter>

            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/accessibility_service_config"/>
        </service>
    </application>

</manifest>
Run Code Online (Sandbox Code Playgroud)

RES/XML/accessibility_service_config.xml

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:accessibilityFlags="flagDefault"
    android:canRequestEnhancedWebAccessibility="true"
    android:canRetrieveWindowContent="true"
    android:description="@string/app_name"
    android:notificationTimeout="100"/>
Run Code Online (Sandbox Code Playgroud)

主要活动

public class MainActivity extends AppCompatActivity {

    public void onEnableAccClick(View view) {
        startActivityForResult(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 1);
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 好问题`canRequestEnhancedWebAccessibility`.它没有很好的文档记录,所以几个月前我需要阅读[TalkBack](https://github.com/google/talkback)的代码来了解它的用途.有了这个,你可以检查webview(浏览器)中的html元素,并对它们执行操作(click,settext,..).要检查html元素,您应该使用ACTION_NEXT_HTML_ELEMENT和ACTION_PREVIOUS_HTML_ELEMENT等操作,请对此const进行一些搜索.关于TalkBack代码,所以你会看到它是如何使用的. (2认同)
  • 是的,这个解决方案不是很稳定,但它可以工作,并且有两个可点击的 el. 的情况,一个又一个,即使用户需要点击它也可能很奇怪,但是您可以找到可点击的内容`isClickable( )`。我们能否继续进行 IM 聊天并为您提供有关要实施的特定用例的帮助? (2认同)