14

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.

thecr0w
  • 2,148
  • 4
  • 33
  • 59
  • 1
    Is your service receiving events? Does getSource() return non-null values? Why are you trying to perform click and scroll actions on the source of the window state changed event (which is always the root view of the window)? – alanv Oct 29 '14 at 18:29
  • I removed some confusing code(perform click and scroll) :) – thecr0w Oct 30 '14 at 06:19
  • 1
    Are you sure that there is actually a View with that ID? The string "force_stop_button" doesn't show up anywhere in the Android source tree. – alanv Oct 30 '14 at 19:44
  • Yes, It's my mistake, the string shuould be "com.android.settings:id/left_button", and it works. – thecr0w Nov 01 '14 at 08:55

3 Answers3

25

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

    }
}
thecr0w
  • 2,148
  • 4
  • 33
  • 59
  • 1
    It should be noted that this seems working on API levels 14+ only. And what about `recycle`ing? According to documentation you should recycle both events and node objects. – Stan Dec 26 '14 at 14:57
  • 2
    findAccessibilityNodeInfosByViewId is available for API 18+. Using findAccessibilityNodeInfosByText is a bit tricky since the text can change depending on locale. Any idea how to overcome this issue? – guy.gc Jul 10 '15 at 20:12
  • findAccessibilityNodeInfosByViewId("android:id/button1"); Is this always get same ID coz i have one mobile LeEco which dont caught this ID – Bhanu Sharma Nov 18 '16 at 12:13
  • how do i click clear data? force close is leftbutton what is clear data's name? – usr30911 Oct 27 '17 at 00:44
  • I'm getting nodeInfo as null when I'm trying to click on a notification using accessibility service – Siva Apr 09 '18 at 12:23
  • @thecr0w can you answer this question. https://stackoverflow.com/questions/49734263/click-on-the-notification-using-accessibility-service-programmatically – Siva Apr 09 '18 at 14:15
  • @Siva K Reddy C Shiva What OS Version, device are you testing? – thecr0w Dec 29 '18 at 08:45
4

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;
}
Community
  • 1
  • 1
guy.gc
  • 3,359
  • 2
  • 24
  • 39
  • `findAccessibilityNodeInfosByViewId` can be replaced by `AccessibilityNodeInfoCompat.wrap (getRootInActiveWindow ()).findAccessibilityNodeInfosByViewId` for older targets –  Jan 01 '18 at 23:29
1

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)
        ...
android developer
  • 114,585
  • 152
  • 739
  • 1,270