1 2 package com.android.testing.uiautomation; 3 4 import android.accessibilityservice.AccessibilityServiceInfo; 5 import android.accessibilityservice.IAccessibilityServiceConnection; 6 import android.accessibilityservice.IEventListener; 7 import android.content.Context; 8 import android.graphics.Rect; 9 import android.os.RemoteException; 10 import android.os.ServiceManager; 11 import android.os.SystemProperties; 12 import android.util.Log; 13 import android.view.accessibility.AccessibilityEvent; 14 import android.view.accessibility.AccessibilityManager; 15 import android.view.accessibility.AccessibilityNodeInfo; 16 import android.view.accessibility.IAccessibilityManager; 17 import android.widget.EditText; 18 19 import java.util.List; 20 21 public class ProviderImpl extends Provider.Stub { 22 23 private static final String LOGTAG = "ProviderImpl"; 24 25 private static final String TYPE_CLASSNAME = "classname"; 26 27 private static final String TYPE_TEXT = "text"; 28 29 private Context mContext; 30 31 private InteractionProvider mInteractionProvider; 32 33 private IAccessibilityServiceConnection mAccessibilityServiceConnection; 34 35 protected AccessibilityNodeInfo mCurrentWindow = null; 36 37 protected AccessibilityNodeInfo mCurrentFocused = null; 38 39 protected String mCurrentActivityName = null; 40 41 protected String mCurrentActivityClass = null; 42 43 protected String mCurrentActivityPackage = null; 44 ProviderImpl(Context context)45 public ProviderImpl(Context context) throws RemoteException { 46 IEventListener listener = new IEventListener.Stub() { 47 @Override 48 public void setConnection(IAccessibilityServiceConnection connection) 49 throws RemoteException { 50 AccessibilityServiceInfo info = new AccessibilityServiceInfo(); 51 info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; 52 info.feedbackType = AccessibilityServiceInfo.FEEDBACK_VISUAL; 53 info.notificationTimeout = 0; 54 info.flags = AccessibilityServiceInfo.DEFAULT; 55 connection.setServiceInfo(info); 56 } 57 58 @Override 59 public void onInterrupt() { 60 } 61 62 @Override 63 public void onAccessibilityEvent(AccessibilityEvent event) throws RemoteException { 64 // delegate the call to parent 65 ProviderImpl.this.onAccessibilityEvent(event); 66 } 67 }; 68 IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(ServiceManager 69 .getService(Context.ACCESSIBILITY_SERVICE)); 70 mContext = context; 71 mAccessibilityServiceConnection = manager.registerEventListener(listener); 72 mInteractionProvider = new InteractionProvider(); 73 } 74 getConnection()75 private IAccessibilityServiceConnection getConnection() { 76 return mAccessibilityServiceConnection; 77 } 78 onAccessibilityEvent(AccessibilityEvent event)79 private void onAccessibilityEvent(AccessibilityEvent event) throws RemoteException { 80 Log.d(LOGTAG, "ProviderImpl=" + this.toString()); 81 Log.d(LOGTAG, event.toString()); 82 switch (event.getEventType()) { 83 case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: 84 if (mCurrentWindow != null) { 85 mCurrentWindow.recycle(); 86 } 87 mCurrentWindow = event.getSource(); 88 if (shouldDumpWindow()) 89 AccessibilityNodeInfoHelper.dumpWindowToFile(mCurrentWindow); 90 mCurrentActivityClass = event.getClassName().toString(); 91 mCurrentActivityPackage = event.getPackageName().toString(); 92 if (event.getText().size() > 0) { 93 mCurrentActivityName = event.getText().get(0).toString(); 94 } else { 95 mCurrentActivityName = null; 96 } 97 break; 98 case AccessibilityEvent.TYPE_VIEW_FOCUSED: 99 if (mCurrentFocused != null) { 100 mCurrentFocused.recycle(); 101 } 102 mCurrentFocused = event.getSource(); 103 default: 104 break; 105 } 106 } 107 108 @Override isEnabled(String selector)109 public boolean isEnabled(String selector) throws RemoteException { 110 AccessibilityNodeInfo node = findNodeOrThrow(getConnection(), selector); 111 boolean b = node.isEnabled(); 112 node.recycle(); 113 return b; 114 } 115 116 @Override isFocused(String selector)117 public boolean isFocused(String selector) throws RemoteException { 118 AccessibilityNodeInfo node = findNodeOrThrow(getConnection(), selector); 119 boolean b = node.isFocused(); 120 node.recycle(); 121 return b; 122 } 123 124 @Override getChildCount(String selector)125 public int getChildCount(String selector) throws RemoteException { 126 AccessibilityNodeInfo node = findNodeOrThrow(getConnection(), selector); 127 int count = node.getChildCount(); 128 node.recycle(); 129 return count; 130 } 131 132 @Override getText(String selector)133 public String getText(String selector) throws RemoteException { 134 AccessibilityNodeInfo node = findNode(getConnection(), selector); 135 if (node == null) { 136 Log.w(LOGTAG, "node not found, selector=" + selector); 137 return null; 138 } else { 139 String s = node.getText().toString(); 140 node.recycle(); 141 return s; 142 } 143 } 144 145 @Override getClassName(String selector)146 public String getClassName(String selector) throws RemoteException { 147 AccessibilityNodeInfo node = findNode(getConnection(), selector); 148 if (node == null) { 149 Log.w(LOGTAG, "node not found, selector=" + selector); 150 return null; 151 } else { 152 String s = node.getClassName().toString(); 153 node.recycle(); 154 return s; 155 } 156 } 157 158 @Override click(String selector)159 public boolean click(String selector) throws RemoteException { 160 AccessibilityNodeInfo node = findNode(getConnection(), selector); 161 if (node == null) { 162 Log.w(LOGTAG, "node not found, selector=" + selector); 163 return false; 164 } 165 return click(node); 166 } 167 click(AccessibilityNodeInfo node)168 protected boolean click(AccessibilityNodeInfo node) throws RemoteException { 169 // TODO: do a click here 170 Rect b = new Rect(); 171 node.getBoundsInScreen(b); 172 return mInteractionProvider.tap(b.centerX(), b.centerY()); 173 } 174 175 @Override getCurrentActivityName()176 public String getCurrentActivityName() throws RemoteException { 177 return mCurrentActivityName; 178 } 179 180 @Override getCurrentActivityPackage()181 public String getCurrentActivityPackage() throws RemoteException { 182 return mCurrentActivityPackage; 183 } 184 185 @Override getCurrentActivityClass()186 public String getCurrentActivityClass() throws RemoteException { 187 return mCurrentActivityClass; 188 } 189 190 @Override sendText(String text)191 public boolean sendText(String text) throws RemoteException { 192 return mInteractionProvider.sendText(text); 193 } 194 195 @Override setTextFieldByLabel(String label, String text)196 public boolean setTextFieldByLabel(String label, String text) throws RemoteException { 197 // first index of a text field, first index of a text field after the 198 // matching label 199 int firstIndex = -1, firstAfterIndex = -1, labelIndex = -1; 200 Log.d(LOGTAG, "I'm here..."); 201 AccessibilityNodeInfo node = findNode(getConnection(), "text:" + label); 202 if (node == null) { 203 Log.w(LOGTAG, "label node not found: " + label); 204 return false; 205 } 206 AccessibilityNodeInfo parent = node.getParent(); 207 node.recycle(); 208 node = null; 209 int count = parent.getChildCount(); 210 for (int i = 0; i < count; i++) { 211 AccessibilityNodeInfo child = parent.getChild(i); 212 CharSequence csText = child.getText(); 213 CharSequence csClass = child.getClassName(); 214 if (csText != null && label.contentEquals(csText)) { 215 labelIndex = i; 216 } 217 if (csClass != null && EditText.class.getName().contentEquals(csClass)) { 218 if (labelIndex == -1) { 219 firstIndex = i; 220 } else { 221 firstAfterIndex = i; 222 } 223 } 224 child.recycle(); 225 } 226 if (firstAfterIndex != -1) 227 node = parent.getChild(firstAfterIndex); 228 else if (firstIndex != -1) 229 node = parent.getChild(firstIndex); 230 parent.recycle(); 231 if (node == null) { 232 Log.w(LOGTAG, "Cannot find an EditorText for label: " + label); 233 return false; 234 } 235 AccessibilityNodeInfoHelper.dumpNode(node); 236 click(node); 237 node.recycle(); 238 return mInteractionProvider.sendText(text); 239 } 240 241 @Override checkUiVerificationEnabled()242 public boolean checkUiVerificationEnabled() throws RemoteException { 243 return AccessibilityManager.getInstance(mContext).isEnabled(); 244 } 245 findNodeOrThrow(IAccessibilityServiceConnection connection, String selector)246 private AccessibilityNodeInfo findNodeOrThrow(IAccessibilityServiceConnection connection, 247 String selector) throws RemoteException { 248 AccessibilityNodeInfo node = findNode(connection, selector); 249 if (node == null) { 250 Log.e(LOGTAG, "node not found, selector=" + selector); 251 throw new RemoteException(); 252 } 253 return node; 254 } 255 findNode(IAccessibilityServiceConnection connection, String selector)256 private AccessibilityNodeInfo findNode(IAccessibilityServiceConnection connection, 257 String selector) throws RemoteException { 258 // a selector should be in the format of "[selector type]:[matcher] 259 // example: 260 // classname:android.widget.Text 261 // text:Click Me 262 // id:id/username 263 int pos = selector.indexOf(':'); 264 if (pos != -1) { 265 String selectorType = selector.substring(0, pos); 266 String matcher = selector.substring(pos + 1); 267 if (TYPE_TEXT.equals(selectorType)) { 268 List<AccessibilityNodeInfo> nodes = connection 269 .findAccessibilityNodeInfosByViewTextInActiveWindow(matcher); 270 if (nodes != null && nodes.size() > 0) { 271 // keep the first one, recycle the rest 272 // TODO: find better way to handle multiple matches 273 for (int i = 1; i < nodes.size(); i++) { 274 nodes.get(i).recycle(); 275 } 276 return nodes.get(0); 277 } 278 } // more type matchers to be added here 279 } 280 return null; 281 } 282 shouldDumpWindow()283 private static boolean shouldDumpWindow() { 284 return SystemProperties.getBoolean("uiauto.dump", false); 285 } 286 } 287