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