Android AccessibilityService: how to record and replay click view?

I am trying to implement an AccessibilityService that logs user actions (only an event clicked at that point) and stores them so that they can be played back at a later point in time.

To do this, I log events AccessibilityEvent.TYPE_VIEW_CLICKED

and check if I can somehow restore them later (when all I have is a link to the window / root node of the activity) using two strategies:

  • Get id with mouse click and find that id in root tree node
  • Get clicked text and find that text in the root tree node

I tested this in various applications and different parts of the Android system and the results were very confusing. About half of these were not recovered by either of the two strategies, and some views were sometimes reported as recoverable and sometimes not. I found out that the latter is due to a race condition, as the accessibility service is running in a different process than the application that the viewed views belong to.

My question now is if there is a better way to get the handle of a view in accessibility and find that view again when the application is later executed.

Below you will find the code for my AccessiblityService class:

public class RecorderService extends AccessibilityService {

    private static final String TAG = "RecorderService";

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {

        switch (event.getEventType()) {
        case AccessibilityEvent.TYPE_VIEW_CLICKED:
            AccessibilityNodeInfo node = event.getSource();

            if (node == null) {
                Log.i(TAG, "node is null");
                return;
            }

            AccessibilityNodeInfo root = getRootInActiveWindow();
            if (root == null) {
                Log.i(TAG, "root is null");
                return;
            }

            // Strategy #1: locate node via its id
            String id = node.getViewIdResourceName();
            if (id == null) {
                Log.i(TAG, "id is null");
            } else {

                List<AccessibilityNodeInfo> rootNodes = root.findAccessibilityNodeInfosByViewId(id);
                if (rootNodes.size() == 1) {
                    Log.i(TAG, "success (via id)");
                    return;
                } else {
                    Log.i(TAG, "multiple nodes with that id");
                }
            }

            // Strategy #2: locate node via its text
            CharSequence text = node.getText();
            if (text == null) {
                Log.i(TAG, "text is null");
            } else {
                List<AccessibilityNodeInfo> rootNodes = root.findAccessibilityNodeInfosByText(text.toString());
                if (rootNodes.size() == 1) {
                    Log.i(TAG, "success (via text)");
                    return;
                }
            }

            Log.i(TAG, "failed, node was not recoverable");
        }
    }

    @Override
    protected boolean onKeyEvent(KeyEvent event) {
        Log.i("Key", event.getKeyCode() + "");

        return true;
        // return super.onKeyEvent(event);
    }

    @Override
    public void onInterrupt() {
    }
}

      

I am developing this on SDK version 21 (Lollipop) and testing it on HTC Nexus M8 and Samsung Galaxy Note2 showing similar results.

+3


source to share





All Articles