【问题标题】:How to click button in settings using AccessibilityService?如何使用 AccessibilityService 在设置中单击按钮?
【发布时间】:2014-10-29 10:42:12
【问题描述】:

我想像 greenify 一样使用 AccessibilityService 在 android 设置中单击按钮,但我找不到特定按钮。请帮帮我。

MyAccessibilityService .java:

public class MyAccessibilityService extends AccessibilityService {

    private static final String TAG = MyAccessibilityService.class
            .getSimpleName();

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        Log.i(TAG, "ACC::onAccessibilityEvent: " + event.getEventType());

        //TYPE_WINDOW_STATE_CHANGED = 32, 
        if (AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED == event.getEventType()) { 
            AccessibilityNodeInfo nodeInfo = event.getSource();
            Log.i(TAG, "ACC::onAccessibilityEvent: nodeInfo=" + nodeInfo.getText());

            List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId("com.android.settings:id/left_button");
            for (AccessibilityNodeInfo node : list) {
                Log.i(TAG, "ACC::onAccessibilityEvent: " + event.getEventType()
                        + " " + node);
            }

编辑:

只有当 type 为 TYPE_WINDOW_STATE_CHANGED 时,我才能得到 nodeInfo 对象。

【问题讨论】:

  • 您的服务是否接收事件? getSource() 是否返回非空值?为什么要尝试对窗口状态更改事件的源(始终是窗口的根视图)执行单击和滚动操作?
  • 我删除了一些令人困惑的代码(执行点击和滚动):)
  • 您确定确实存在具有该 ID 的视图吗?字符串“force_stop_button”不会出现在 Android 源代码树中的任何位置。
  • 是的,这是我的错误,字符串应该是“com.android.settings:id/left_button”,它可以工作。

标签: android accessibility


【解决方案1】:

打开一个应用的 Appinfo 并启用强制关闭按钮进行测试:

public class MyAccessibilityService extends AccessibilityService {
    private static final String TAG = MyAccessibilityService.class
            .getSimpleName();

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        Log.i(TAG, "ACC::onAccessibilityEvent: " + event.getEventType());

        //TYPE_WINDOW_STATE_CHANGED == 32
        if (AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED == event
                .getEventType()) {
            AccessibilityNodeInfo nodeInfo = event.getSource();
            Log.i(TAG, "ACC::onAccessibilityEvent: nodeInfo=" + nodeInfo);
            if (nodeInfo == null) {
                return;
            }

            List<AccessibilityNodeInfo> list = nodeInfo
                    .findAccessibilityNodeInfosByViewId("com.android.settings:id/left_button");
            for (AccessibilityNodeInfo node : list) {
                Log.i(TAG, "ACC::onAccessibilityEvent: left_button " + node);
                node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            }

            list = nodeInfo
                    .findAccessibilityNodeInfosByViewId("android:id/button1");
            for (AccessibilityNodeInfo node : list) {
                Log.i(TAG, "ACC::onAccessibilityEvent: button1 " + node);
                node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            }
        }

    }

    @Override
    public void onServiceConnected() {
        Log.i(TAG, "ACC::onServiceConnected: ");
    }

    @Override
    public void onInterrupt() {
        // TODO Auto-generated method stub

    }
}

【讨论】:

  • 应该注意,这似乎只适用于 API 级别 14+。那么recycleing 呢?根据文档,您应该回收事件和节点对象。
  • findAccessibilityNodeInfosByViewId 适用于 API 18+。使用 findAccessibilityNodeInfosByText 有点棘手,因为文本可能会根据语言环境而变化。知道如何克服这个问题吗?
  • findAccessibilityNodeInfosByViewId("android:id/button1");这是否总是得到相同的 ID,因为我有一个手机 LeEco,它没有捕捉到这个 ID
  • 如何点击清除数据? force close is leftbutton 什么是清除数据的名称?
  • 当我尝试使用无障碍服务点击通知时,我将 nodeInfo 设为 null
【解决方案2】:

所选答案适用于 API 18 及更高版本,因为它依赖于 API 18 中添加的 findAccessibilityNodeInfosByViewId。 我最终编写了这个类来支持 API 17 及更低版本。

ResourcesCompat 类查找用给定活动标识的资源,在我们的例子中应该是 Android 的设置活动。在Accessibility Service中处理accessibility事件时,可以调用该函数获取settingsactivity的ComponentName。

    public static ComponentName getForegroundActivity(Context context) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);
        ComponentName topActivity = taskInfo.get(0).topActivity;
        return topActivity;
    }

当您第一次按照thecr0w 描述的那样处理 TYPE_WINDOW_STATE_CHANGED 事件时,调用init(...) 的好地方是onAccessibilityEvent

public class ResourcesCompat {
    private final static String RESOURCE_TYPE = "string";

    private String mResourcesPackageName;
    private Resources mResources;

    /**
     * Find the resource file for a specific activity
     *
     * @param context
     * @param settingsPackageName
     * @param settingsClassName
     */
    public void init(Context context, String settingsPackageName, String settingsClassName) {
        try {
            mResourcesPackageName = settingsPackageName;
            ComponentName settingsComponentName = new ComponentName(settingsPackageName, settingsPackageName + settingsClassName);
            mResources = context.getPackageManager().getResourcesForActivity(settingsComponentName);
        } catch (PackageManager.NameNotFoundException e) {
        }
    }

    /**
     * Return the localised string for the given resource name.
     * @param resourceName The name of the resource definition in strings.xml
     */
    public String getString(String resourceName) {
        int resourceId = getIdentifier(resourceName);
        return resourceId > 0 ? mResources.getString(resourceId) : null;
    }

    /**
     * Return a resource identifier for the given resource name.
     * @param resourceName The name of the desired resource.
     * @return int The associated resource identifier. Returns 0 if no such resource was found. (0 is not a valid resource ID.)
     */
    private int getIdentifier(String resourceName) {
        return mResources.getIdentifier(resourceName, RESOURCE_TYPE, mResourcesPackageName);
    }
}

一些制造商喜欢移动类并重命名默认字符串(cough Samsung cough Xiomi cough)所以请确保您涵盖所有情况并处理错误和异常。

最后,按名称找到您的视图。这里,id 可以是 'force_stop' 例如

private List<AccessibilityNodeInfo> findAccessibilityNodeInfosByName(AccessibilityNodeInfo source, String id) {
        String nodeText = mResourcesCompat.getString(id);
        if (nodeText != null) {
            return source.findAccessibilityNodeInfosByText(nodeText);
        }

        return null;
}

【讨论】:

  • findAccessibilityNodeInfosByViewId 可以用 AccessibilityNodeInfoCompat.wrap (getRootInActiveWindow ()).findAccessibilityNodeInfosByViewId 替换旧目标
【解决方案3】:

这是我用过的:

    override fun onAccessibilityEvent(event: AccessibilityEvent) {
        val eventPackageName = event.packageName
        val className = event.className
        val source: AccessibilityNodeInfo? = event.source
        val targetAppPackageName=...
        val targetViewId=...
        val viewsToCheck = rootInActiveWindow?.findAccessibilityNodeInfosByViewId("$targetAppPackageName:id/targetViewId")?.getOrNull(0)
        viewsToCheck?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
        ...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-31
    • 1970-01-01
    • 2015-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多