1package foo.bar.testback;
2
3import android.accessibilityservice.AccessibilityService;
4import android.accessibilityservice.AccessibilityServiceInfo;
5import android.content.Context;
6import android.util.ArraySet;
7import android.util.Log;
8import android.view.WindowManager;
9import android.view.accessibility.AccessibilityEvent;
10import android.view.accessibility.AccessibilityNodeInfo;
11import android.view.accessibility.AccessibilityWindowInfo;
12import android.widget.Button;
13
14import java.util.List;
15import java.util.Set;
16
17public class TestBackService extends AccessibilityService {
18
19    private static final String LOG_TAG = TestBackService.class.getSimpleName();
20
21    private Button mButton;
22
23    @Override
24    public void onCreate() {
25        super.onCreate();
26        mButton = new Button(this);
27        mButton.setText("Button 1");
28    }
29
30    @Override
31    public void onAccessibilityEvent(AccessibilityEvent event) {
32//        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
33//            Log.i(LOG_TAG, event.getText().toString());
34//            //dumpWindows();
35//        }
36        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER) {
37//            Log.i(LOG_TAG, "Click event.isChecked()=" + event.isChecked()
38//                    + ((event.getSource() != null) ? " node.isChecked()="
39//                    + event.getSource().isChecked() : " node=null"));
40
41            AccessibilityNodeInfo source = event.getSource();
42            dumpWindow(source);
43//            AccessibilityNodeInfo node = event.getSource();
44//            if (node != null) {
45//                node.refresh();
46//                Log.i(LOG_TAG, "Clicked: " + node.getClassName() + " clicked:" + node.isChecked());
47//            }
48        }
49    }
50
51    @Override
52    protected boolean onGesture(int gestureId) {
53        switch (gestureId) {
54            case AccessibilityService.GESTURE_SWIPE_DOWN: {
55                showAccessibilityOverlay();
56            } break;
57            case AccessibilityService.GESTURE_SWIPE_UP: {
58                hideAccessibilityOverlay();
59            } break;
60        }
61        return super.onGesture(gestureId);
62    }
63
64    private void showAccessibilityOverlay() {
65        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
66        params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
67                | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
68                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
69                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
70        params.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
71
72        WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
73        windowManager.addView(mButton, params);
74    }
75
76    private void hideAccessibilityOverlay() {
77        WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
78        windowManager.removeView(mButton);
79    }
80
81    private void dumpWindows() {
82        List<AccessibilityWindowInfo> windows = getWindows();
83        final int windowCount = windows.size();
84        for (int i = 0; i < windowCount; i++) {
85            AccessibilityWindowInfo window = windows.get(i);
86            Log.i(LOG_TAG, "=============================");
87            Log.i(LOG_TAG, window.toString());
88
89        }
90    }
91
92    private void dumpWindow(AccessibilityNodeInfo source) {
93        AccessibilityNodeInfo root = source;
94        while (true) {
95            AccessibilityNodeInfo parent = root.getParent();
96            if (parent == null) {
97                break;
98            } else if (parent.equals(root)) {
99                Log.i(LOG_TAG, "Node is own parent:" + root);
100            }
101            root = parent;
102        }
103        dumpTree(root, new ArraySet<AccessibilityNodeInfo>());
104    }
105
106    private void dumpTree(AccessibilityNodeInfo root, Set<AccessibilityNodeInfo> visited) {
107        if (root == null) {
108            return;
109        }
110
111        if (!visited.add(root)) {
112            Log.i(LOG_TAG, "Cycle detected to node:" + root);
113        }
114
115        final int childCount = root.getChildCount();
116        for (int i = 0; i < childCount; i++) {
117            AccessibilityNodeInfo child = root.getChild(i);
118            if (child != null) {
119                AccessibilityNodeInfo parent = child.getParent();
120                if (parent == null) {
121                    Log.e(LOG_TAG, "Child of a node has no parent");
122                } else if (!parent.equals(root)) {
123                    Log.e(LOG_TAG, "Child of a node has wrong parent");
124                }
125                Log.i(LOG_TAG, child.toString());
126            }
127        }
128
129        for (int i = 0; i < childCount; i++) {
130            AccessibilityNodeInfo child = root.getChild(i);
131            dumpTree(child, visited);
132        }
133    }
134
135    @Override
136    public void onInterrupt() {
137        /* ignore */
138    }
139
140    @Override
141    public void onServiceConnected() {
142        AccessibilityServiceInfo info = getServiceInfo();
143        info.flags |= AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
144        info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
145        info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
146        setServiceInfo(info);
147    }
148}
149