Home » Android » android – How to click button in settings using AccessibilityService?

android – How to click button in settings using AccessibilityService?

Posted by: admin June 15, 2020 Leave a comment

Questions:

I want to click button in android settings using AccessibilityService like greenify did, but I cannot find the specific button. please help me.

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

EDIT:

Only when type is TYPE_WINDOW_STATE_CHANGED , I could get the nodeInfo object.

How to&Answers:

Open one app’s Appinfo with force close button enabled to test:

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

    }
}

Answer:

The selected answer works on API 18 and above since it relays on findAccessibilityNodeInfosByViewId which was added in API 18.
I ended up writing this class to support API 17 and below.

ResourcesCompat class finds the resources identified with the given activty which in our case should be Android’s Setting activity. You can get the ComponentName of settings activity by calling this function when handling the accessibility event in you Accessibility Service.

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

A good place to call init(...) is in onAccessibilityEvent when you’re first handling the TYPE_WINDOW_STATE_CHANGED event as thecr0w described.

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

Some manufacturers like to move classes around and rename default strings (cough Samsung cough Xiomi cough) So make sure you cover all cases and handle errors and exceptions.

Finally, find your view by name. here, id can be ‘force_stop’ for example

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

        return null;
}

Answer:

This is what I’ve used:

    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)
        ...