• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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