• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package foo.bar.testback;
18 
19 import static foo.bar.testback.AccessibilityNodeInfoUtils.findChildDfs;
20 import static foo.bar.testback.AccessibilityNodeInfoUtils.findParent;
21 
22 import static foo.bar.testback.AccessibilityFocusManager.CAN_TAKE_A11Y_FOCUS;
23 
24 import android.accessibilityservice.AccessibilityService;
25 import android.accessibilityservice.AccessibilityServiceInfo;
26 import android.content.Context;
27 import android.graphics.Rect;
28 import android.util.ArraySet;
29 import android.util.Log;
30 import android.view.WindowManager;
31 import android.view.accessibility.AccessibilityEvent;
32 import android.view.accessibility.AccessibilityNodeInfo;
33 import android.view.accessibility.AccessibilityWindowInfo;
34 import android.widget.Button;
35 
36 import java.util.List;
37 import java.util.Set;
38 import java.util.function.Predicate;
39 
40 public class TestBackService extends AccessibilityService {
41 
42     private static final String LOG_TAG = TestBackService.class.getSimpleName();
43 
44     private Button mButton;
45 
46     int mRowIndexOfA11yFocus = -1;
47     int mColIndexOfA11yFocus = -1;
48     AccessibilityNodeInfo mCollectionWithAccessibiltyFocus;
49     AccessibilityNodeInfo mCurrentA11yFocus;
50 
51     @Override
onCreate()52     public void onCreate() {
53         super.onCreate();
54         mButton = new Button(this);
55         mButton.setText("Button 1");
56     }
57 
58     @Override
onAccessibilityEvent(AccessibilityEvent event)59     public void onAccessibilityEvent(AccessibilityEvent event) {
60         int eventType = event.getEventType();
61         AccessibilityNodeInfo source = event.getSource();
62         if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER) {
63             if (source != null) {
64                 AccessibilityNodeInfo focusNode =
65                         (CAN_TAKE_A11Y_FOCUS.test(source)) ? source : findParent(
66                                 source, AccessibilityFocusManager::canTakeAccessibilityFocus);
67                 if (focusNode != null) {
68                     focusNode.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
69                 }
70             }
71             return;
72         }
73         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
74             mCurrentA11yFocus = source;
75             AccessibilityNodeInfo.CollectionItemInfo itemInfo = source.getCollectionItemInfo();
76             if (itemInfo == null) {
77                 mRowIndexOfA11yFocus = -1;
78                 mColIndexOfA11yFocus = -1;
79                 mCollectionWithAccessibiltyFocus = null;
80             } else {
81                 mRowIndexOfA11yFocus = itemInfo.getRowIndex();
82                 mColIndexOfA11yFocus = itemInfo.getColumnIndex();
83                 mCollectionWithAccessibiltyFocus = findParent(source,
84                         (nodeInfo) -> nodeInfo.getCollectionInfo() != null);
85             }
86             Rect bounds = new Rect();
87             source.getBoundsInScreen(bounds);
88         }
89 
90         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED) {
91             mCurrentA11yFocus = null;
92             return;
93         }
94 
95         if (eventType == AccessibilityEvent.TYPE_WINDOWS_CHANGED) {
96             mRowIndexOfA11yFocus = -1;
97             mColIndexOfA11yFocus = -1;
98             mCollectionWithAccessibiltyFocus = null;
99         }
100 
101         if ((mCurrentA11yFocus == null) && (mCollectionWithAccessibiltyFocus != null)) {
102             // Look for a node to re-focus
103             AccessibilityNodeInfo focusRestoreTarget = findChildDfs(
104                     mCollectionWithAccessibiltyFocus, (nodeInfo) -> {
105                         AccessibilityNodeInfo.CollectionItemInfo collectionItemInfo =
106                                 nodeInfo.getCollectionItemInfo();
107                         return (collectionItemInfo != null)
108                                 && (collectionItemInfo.getRowIndex() == mRowIndexOfA11yFocus)
109                                 && (collectionItemInfo.getColumnIndex() == mColIndexOfA11yFocus);
110                     });
111             if (focusRestoreTarget != null) {
112                 focusRestoreTarget.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
113             }
114         }
115     }
116 
dumpWindows()117     private void dumpWindows() {
118         List<AccessibilityWindowInfo> windows = getWindows();
119         final int windowCount = windows.size();
120         for (int i = 0; i < windowCount; i++) {
121             AccessibilityWindowInfo window = windows.get(i);
122             Log.i(LOG_TAG, "=============================");
123             Log.i(LOG_TAG, window.toString());
124 
125         }
126     }
127 
dumpWindow(AccessibilityNodeInfo source)128     private void dumpWindow(AccessibilityNodeInfo source) {
129         AccessibilityNodeInfo root = source;
130         while (true) {
131             AccessibilityNodeInfo parent = root.getParent();
132             if (parent == null) {
133                 break;
134             } else if (parent.equals(root)) {
135                 Log.i(LOG_TAG, "Node is own parent:" + root);
136             }
137             root = parent;
138         }
139         dumpTree(root, new ArraySet<AccessibilityNodeInfo>());
140     }
141 
dumpTree(AccessibilityNodeInfo root, Set<AccessibilityNodeInfo> visited)142     private void dumpTree(AccessibilityNodeInfo root, Set<AccessibilityNodeInfo> visited) {
143         if (root == null) {
144             return;
145         }
146 
147         if (!visited.add(root)) {
148             Log.i(LOG_TAG, "Cycle detected to node:" + root);
149         }
150 
151         final int childCount = root.getChildCount();
152         for (int i = 0; i < childCount; i++) {
153             AccessibilityNodeInfo child = root.getChild(i);
154             if (child != null) {
155                 AccessibilityNodeInfo parent = child.getParent();
156                 if (parent == null) {
157                     Log.e(LOG_TAG, "Child of a node has no parent");
158                 } else if (!parent.equals(root)) {
159                     Log.e(LOG_TAG, "Child of a node has wrong parent");
160                 }
161                 Log.i(LOG_TAG, child.toString());
162             }
163         }
164 
165         for (int i = 0; i < childCount; i++) {
166             AccessibilityNodeInfo child = root.getChild(i);
167             dumpTree(child, visited);
168         }
169     }
170 
171     @Override
onInterrupt()172     public void onInterrupt() {
173         /* ignore */
174     }
175 
176     @Override
onServiceConnected()177     public void onServiceConnected() {
178         AccessibilityServiceInfo info = getServiceInfo();
179         info.flags |= AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
180         info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
181         info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
182         setServiceInfo(info);
183     }
184 }
185