• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2011, 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 com.android.commands.monkey;
18 
19 import static com.android.commands.monkey.MonkeySourceNetwork.EARG;
20 
21 import android.accessibilityservice.IAccessibilityServiceConnection;
22 import android.accessibilityservice.IEventListener;
23 import android.accessibilityservice.AccessibilityServiceInfo;
24 import android.content.Context;
25 import android.content.pm.IPackageManager;
26 import android.content.pm.ApplicationInfo;
27 import android.graphics.Rect;
28 import android.os.ServiceManager;
29 import android.os.RemoteException;
30 import android.util.Log;
31 import android.view.accessibility.AccessibilityInteractionClient;
32 import android.view.accessibility.AccessibilityNodeInfo;
33 import android.view.accessibility.IAccessibilityManager;
34 import android.view.accessibility.AccessibilityEvent;
35 
36 import dalvik.system.DexClassLoader;
37 
38 import com.android.commands.monkey.MonkeySourceNetwork.CommandQueue;
39 import com.android.commands.monkey.MonkeySourceNetwork.MonkeyCommand;
40 import com.android.commands.monkey.MonkeySourceNetwork.MonkeyCommandReturn;
41 
42 import java.lang.reflect.Field;
43 import java.util.concurrent.atomic.AtomicReference;
44 import java.util.Map;
45 import java.util.HashMap;
46 import java.util.List;
47 import java.util.ArrayList;
48 
49 
50 /**
51  * Utility class that enables Monkey to perform view introspection when issued Monkey Network
52  * Script commands over the network.
53  */
54 public class MonkeySourceNetworkViews {
55     private static final String TAG = "MonkeyViews";
56 
57     private static volatile AtomicReference<AccessibilityEvent> sLastAccessibilityEvent
58             = new AtomicReference<AccessibilityEvent>();
59     protected static IAccessibilityServiceConnection sConnection;
60     private static IPackageManager sPm =
61             IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
62     private static Map<String, Class<?>> sClassMap = new HashMap<String, Class<?>>();
63 
64     private static final String REMOTE_ERROR =
65             "Unable to retrieve application info from PackageManager";
66     private static final String CLASS_NOT_FOUND = "Error retrieving class information";
67     private static final String NO_ACCESSIBILITY_EVENT = "No accessibility event has occured yet";
68     private static final String NO_NODE = "Node with given ID does not exist";
69     private static final String NO_CONNECTION = "Failed to connect to AccessibilityService, "
70                                                 + "try restarting Monkey";
71 
72     private static final Map<String, ViewIntrospectionCommand> COMMAND_MAP =
73             new HashMap<String, ViewIntrospectionCommand>();
74 
75     /* Interface for view queries */
76     private static interface ViewIntrospectionCommand {
77         /**
78          * Get the response to the query
79          * @return the response to the query
80          */
query(AccessibilityNodeInfo node, List<String> args)81         public MonkeyCommandReturn query(AccessibilityNodeInfo node, List<String> args);
82     }
83 
84     static {
85         COMMAND_MAP.put("getlocation", new GetLocation());
86         COMMAND_MAP.put("gettext", new GetText());
87         COMMAND_MAP.put("getclass", new GetClass());
88         COMMAND_MAP.put("getchecked", new GetChecked());
89         COMMAND_MAP.put("getenabled", new GetEnabled());
90         COMMAND_MAP.put("getselected", new GetSelected());
91         COMMAND_MAP.put("setselected", new SetSelected());
92         COMMAND_MAP.put("getfocused", new GetFocused());
93         COMMAND_MAP.put("setfocused", new SetFocused());
94         COMMAND_MAP.put("getparent", new GetParent());
95         COMMAND_MAP.put("getchildren", new GetChildren());
96         COMMAND_MAP.put("getaccessibilityids", new GetAccessibilityIds());
97     }
98 
99     /* This registers our listener with accessibility services, and gives us a connection object */
getConnection()100     private static IAccessibilityServiceConnection getConnection() throws RemoteException {
101         IEventListener listener = new IEventListener.Stub() {
102             public void setConnection(IAccessibilityServiceConnection connection)
103                     throws RemoteException {
104                 AccessibilityServiceInfo info = new AccessibilityServiceInfo();
105                 info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
106                 info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
107                 info.notificationTimeout = 0;
108                 info.flags = AccessibilityServiceInfo.DEFAULT;
109                 connection.setServiceInfo(info);
110             }
111 
112             public void onInterrupt() {}
113 
114             public void onAccessibilityEvent(AccessibilityEvent event) {
115                 Log.d(TAG, "Accessibility Event");
116                 sLastAccessibilityEvent.set(AccessibilityEvent.obtain(event));
117                 synchronized(sConnection) {
118                     sConnection.notifyAll();
119                 }
120             }
121         };
122         IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
123                 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
124         return manager.registerEventListener(listener);
125     }
126 
127     /**
128      * Registers the event listener for AccessibilityEvents.
129      * Also sets up a communication connection so we can query the
130      * accessibility service.
131      */
setup()132     public static void setup() {
133         try {
134             sConnection = getConnection();
135         } catch (RemoteException re) {
136             Log.e(TAG,"Remote Exception encountered when"
137                   + " attempting to connect to Accessibility Service");
138         }
139     }
140 
141 
142     /**
143      * Get the ID class for the given package.
144      * This will cause issues if people reload a package with different
145      * resource identifiers, but don't restart the Monkey server.
146      *
147      * @param packageName The package that we want to retrieve the ID class for
148      * @return The ID class for the given package
149      */
getIdClass(String packageName, String sourceDir)150     private static Class<?> getIdClass(String packageName, String sourceDir)
151             throws RemoteException, ClassNotFoundException {
152         // This kind of reflection is expensive, so let's only do it
153         // if we need to
154         Class<?> klass = sClassMap.get(packageName);
155         if (klass == null) {
156             DexClassLoader classLoader = new DexClassLoader(
157                     sourceDir, "/data/local/tmp",
158                     null, ClassLoader.getSystemClassLoader());
159             klass = classLoader.loadClass(packageName + ".R$id");
160             sClassMap.put(packageName, klass);
161         }
162         return klass;
163     }
164 
getPositionFromNode(AccessibilityNodeInfo node)165     private static String getPositionFromNode(AccessibilityNodeInfo node) {
166         Rect nodePosition = new Rect();
167         node.getBoundsInScreen(nodePosition);
168         StringBuilder positions = new StringBuilder();
169         positions.append(nodePosition.left).append(" ").append(nodePosition.top);
170         positions.append(" ").append(nodePosition.right-nodePosition.left).append(" ");
171         positions.append(nodePosition.bottom-nodePosition.top);
172         return positions.toString();
173     }
174 
175 
176     /**
177      * Converts a resource identifier into it's generated integer ID
178      *
179      * @param stringId the string identifier
180      * @return the generated integer identifier.
181      */
getId(String stringId, AccessibilityEvent event)182     private static int getId(String stringId, AccessibilityEvent event)
183             throws MonkeyViewException {
184         try {
185             AccessibilityNodeInfo node = event.getSource();
186             String packageName = node.getPackageName().toString();
187             ApplicationInfo appInfo = sPm.getApplicationInfo(packageName, 0);
188             Class<?> klass;
189             klass = getIdClass(packageName, appInfo.sourceDir);
190             return klass.getField(stringId).getInt(null);
191         } catch (RemoteException e) {
192             throw new MonkeyViewException(REMOTE_ERROR);
193         } catch (ClassNotFoundException e){
194             throw new MonkeyViewException(e.getMessage());
195         } catch (NoSuchFieldException e){
196             throw new MonkeyViewException("No such node with given id");
197         } catch (IllegalAccessException e){
198             throw new MonkeyViewException("Private identifier");
199         } catch (NullPointerException e) {
200             // AccessibilityServiceConnection throws a NullPointerException if you hand it
201             // an ID that doesn't exist onscreen
202             throw new MonkeyViewException("No node with given id exists onscreen");
203         }
204     }
205 
getNodeByAccessibilityIds( String windowString, String viewString)206     private static AccessibilityNodeInfo getNodeByAccessibilityIds(
207             String windowString, String viewString) {
208         int windowId = Integer.parseInt(windowString);
209         int viewId = Integer.parseInt(viewString);
210         return AccessibilityInteractionClient.getInstance()
211             .findAccessibilityNodeInfoByAccessibilityId(sConnection, windowId, viewId);
212     }
213 
getNodeByViewId(String viewId, AccessibilityEvent event)214     private static AccessibilityNodeInfo getNodeByViewId(String viewId, AccessibilityEvent event)
215             throws MonkeyViewException {
216         int id = getId(viewId, event);
217         return AccessibilityInteractionClient.getInstance()
218             .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnection, id);
219     }
220 
221     /**
222      * Command to list all possible view ids for the given application.
223      * This lists all view ids regardless if they are on screen or not.
224      */
225     public static class ListViewsCommand implements MonkeyCommand {
226         //listviews
translateCommand(List<String> command, CommandQueue queue)227         public MonkeyCommandReturn translateCommand(List<String> command,
228                                                     CommandQueue queue) {
229             AccessibilityEvent lastEvent = sLastAccessibilityEvent.get();
230             if (lastEvent == null) {
231                 return new MonkeyCommandReturn(false, NO_ACCESSIBILITY_EVENT);
232             }
233             lastEvent.setSealed(true);
234             AccessibilityNodeInfo node = lastEvent.getSource();
235             /* Occasionally the API will generate an event with no source, which is essentially the
236              * same as it generating no event at all */
237             if (node == null) {
238                 return new MonkeyCommandReturn(false, NO_ACCESSIBILITY_EVENT);
239             }
240             String packageName = node.getPackageName().toString();
241             try{
242                 Class<?> klass;
243                 ApplicationInfo appInfo = sPm.getApplicationInfo(packageName, 0);
244                 klass = getIdClass(packageName, appInfo.sourceDir);
245                 StringBuilder fieldBuilder = new StringBuilder();
246                 Field[] fields = klass.getFields();
247                 for (Field field : fields) {
248                     fieldBuilder.append(field.getName() + " ");
249                 }
250                 return new MonkeyCommandReturn(true, fieldBuilder.toString());
251             } catch (RemoteException e){
252                 return new MonkeyCommandReturn(false, REMOTE_ERROR);
253             } catch (ClassNotFoundException e){
254                 return new MonkeyCommandReturn(false, CLASS_NOT_FOUND);
255             }
256         }
257     }
258 
259     /**
260      * A command that allows for querying of views. It takes an id type, the requisite ids,
261      * and the command for querying the view.
262      */
263     public static class QueryViewCommand implements MonkeyCommand {
264         //queryview [id type] [id(s)] [command]
265         //queryview viewid button1 gettext
266         //queryview accessibilityids 12 5 getparent
translateCommand(List<String> command, CommandQueue queue)267         public MonkeyCommandReturn translateCommand(List<String> command,
268                                                     CommandQueue queue) {
269             if (command.size() > 2) {
270                 if (sConnection == null) {
271                     return new MonkeyCommandReturn(false, NO_CONNECTION);
272                 }
273                 AccessibilityEvent lastEvent = sLastAccessibilityEvent.get();
274                 if (lastEvent == null) {
275                     return new MonkeyCommandReturn(false, NO_ACCESSIBILITY_EVENT);
276                 }
277                 lastEvent.setSealed(true);
278                 String idType = command.get(1);
279                 AccessibilityNodeInfo node;
280                 String viewQuery;
281                 List<String> args;
282                 if ("viewid".equals(idType)) {
283                     try {
284                         node = getNodeByViewId(command.get(2), lastEvent);
285                         viewQuery = command.get(3);
286                         args = command.subList(4, command.size());
287                     } catch (MonkeyViewException e) {
288                         return new MonkeyCommandReturn(false, e.getMessage());
289                     }
290                 } else if (idType.equals("accessibilityids")) {
291                     try {
292                         node = getNodeByAccessibilityIds(command.get(2), command.get(3));
293                         viewQuery = command.get(4);
294                         args = command.subList(5, command.size());
295                     } catch (NumberFormatException e) {
296                         return EARG;
297                     }
298                 } else {
299                     return EARG;
300                 }
301                 if (node == null) {
302                     return new MonkeyCommandReturn(false, NO_NODE);
303                 }
304                 ViewIntrospectionCommand getter = COMMAND_MAP.get(viewQuery);
305                 if (getter != null) {
306                     return getter.query(node, args);
307                 } else {
308                     return EARG;
309                 }
310             }
311             return EARG;
312         }
313     }
314 
315     /**
316      * A command that returns the accessibility ids of the root view.
317      */
318     public static class GetRootViewCommand implements MonkeyCommand {
319         // getrootview
translateCommand(List<String> command, CommandQueue queue)320         public MonkeyCommandReturn translateCommand(List<String> command,
321                                                     CommandQueue queue) {
322             AccessibilityEvent lastEvent = sLastAccessibilityEvent.get();
323             if (lastEvent == null) {
324                 return new MonkeyCommandReturn(false, NO_ACCESSIBILITY_EVENT);
325             }
326             lastEvent.setSealed(true);
327             AccessibilityNodeInfo node = lastEvent.getSource();
328             return (new GetAccessibilityIds()).query(node, new ArrayList<String>());
329         }
330     }
331 
332     /**
333      * A command that returns the accessibility ids of the views that contain the given text.
334      * It takes a string of text and returns the accessibility ids of the nodes that contain the
335      * text as a list of integers separated by spaces.
336      */
337     public static class GetViewsWithTextCommand implements MonkeyCommand {
338         // getviewswithtext [text]
339         // getviewswithtext "some text here"
translateCommand(List<String> command, CommandQueue queue)340         public MonkeyCommandReturn translateCommand(List<String> command,
341                                                     CommandQueue queue) {
342             if (sConnection == null) {
343                 return new MonkeyCommandReturn(false, NO_CONNECTION);
344             }
345             if (command.size() == 2) {
346                 String text = command.get(1);
347                 List<AccessibilityNodeInfo> nodes = AccessibilityInteractionClient.getInstance()
348                     .findAccessibilityNodeInfosByViewTextInActiveWindow(sConnection, text);
349                 ViewIntrospectionCommand idGetter = new GetAccessibilityIds();
350                 List<String> emptyArgs = new ArrayList<String>();
351                 StringBuilder ids = new StringBuilder();
352                 for (AccessibilityNodeInfo node : nodes) {
353                     MonkeyCommandReturn result = idGetter.query(node, emptyArgs);
354                     if (!result.wasSuccessful()){
355                         return result;
356                     }
357                     ids.append(result.getMessage()).append(" ");
358                 }
359                 return new MonkeyCommandReturn(true, ids.toString());
360             }
361             return EARG;
362         }
363     }
364 
365     /**
366      * Command to retrieve the location of the given node.
367      * Returns the x, y, width and height of the view, separated by spaces.
368      */
369     public static class GetLocation implements ViewIntrospectionCommand {
370         //queryview [id type] [id] getlocation
371         //queryview viewid button1 getlocation
query(AccessibilityNodeInfo node, List<String> args)372         public MonkeyCommandReturn query(AccessibilityNodeInfo node,
373                                          List<String> args) {
374             if (args.size() == 0) {
375                 Rect nodePosition = new Rect();
376                 node.getBoundsInScreen(nodePosition);
377                 StringBuilder positions = new StringBuilder();
378                 positions.append(nodePosition.left).append(" ").append(nodePosition.top);
379                 positions.append(" ").append(nodePosition.right-nodePosition.left).append(" ");
380                 positions.append(nodePosition.bottom-nodePosition.top);
381                 return new MonkeyCommandReturn(true, positions.toString());
382             }
383             return EARG;
384         }
385     }
386 
387 
388     /**
389      * Command to retrieve the text of the given node
390      */
391     public static class GetText implements ViewIntrospectionCommand {
392         //queryview [id type] [id] gettext
393         //queryview viewid button1 gettext
query(AccessibilityNodeInfo node, List<String> args)394         public MonkeyCommandReturn query(AccessibilityNodeInfo node,
395                                          List<String> args) {
396             if (args.size() == 0) {
397                 if (node.isPassword()){
398                     return new MonkeyCommandReturn(false, "Node contains a password");
399                 }
400                 /* Occasionally we get a null from the accessibility API, rather than an empty
401                  * string */
402                 if (node.getText() == null) {
403                     return new MonkeyCommandReturn(true, "");
404                 }
405                 return new MonkeyCommandReturn(true, node.getText().toString());
406             }
407             return EARG;
408         }
409     }
410 
411 
412     /**
413      * Command to retrieve the class name of the given node
414      */
415     public static class GetClass implements ViewIntrospectionCommand {
416         //queryview [id type] [id] getclass
417         //queryview viewid button1 getclass
query(AccessibilityNodeInfo node, List<String> args)418         public MonkeyCommandReturn query(AccessibilityNodeInfo node,
419                                          List<String> args) {
420             if (args.size() == 0) {
421                 return new MonkeyCommandReturn(true, node.getClassName().toString());
422             }
423             return EARG;
424         }
425     }
426     /**
427      * Command to retrieve the checked status of the given node
428      */
429     public static class GetChecked implements ViewIntrospectionCommand {
430         //queryview [id type] [id] getchecked
431         //queryview viewid button1 getchecked
query(AccessibilityNodeInfo node, List<String> args)432         public MonkeyCommandReturn query(AccessibilityNodeInfo node,
433                                          List<String> args) {
434             if (args.size() == 0) {
435                 return new MonkeyCommandReturn(true, Boolean.toString(node.isChecked()));
436             }
437             return EARG;
438         }
439     }
440 
441     /**
442      * Command to retrieve whether the given node is enabled
443      */
444     public static class GetEnabled implements ViewIntrospectionCommand {
445         //queryview [id type] [id] getenabled
446         //queryview viewid button1 getenabled
query(AccessibilityNodeInfo node, List<String> args)447         public MonkeyCommandReturn query(AccessibilityNodeInfo node,
448                                          List<String> args) {
449             if (args.size() == 0) {
450                 return new MonkeyCommandReturn(true, Boolean.toString(node.isEnabled()));
451             }
452             return EARG;
453         }
454     }
455 
456     /**
457      * Command to retrieve whether the given node is selected
458      */
459     public static class GetSelected implements ViewIntrospectionCommand {
460         //queryview [id type] [id] getselected
461         //queryview viewid button1 getselected
query(AccessibilityNodeInfo node, List<String> args)462         public MonkeyCommandReturn query(AccessibilityNodeInfo node,
463                                          List<String> args) {
464             if (args.size() == 0) {
465                 return new MonkeyCommandReturn(true, Boolean.toString(node.isSelected()));
466             }
467             return EARG;
468         }
469     }
470 
471     /**
472      * Command to set the selected status of the given node. Takes a boolean value as its only
473      * argument.
474      */
475     public static class SetSelected implements ViewIntrospectionCommand {
476         //queryview [id type] [id] setselected [boolean]
477         //queryview viewid button1 setselected true
query(AccessibilityNodeInfo node, List<String> args)478         public MonkeyCommandReturn query(AccessibilityNodeInfo node,
479                                          List<String> args) {
480             if (args.size() == 1) {
481                 boolean actionPerformed;
482                 if (Boolean.valueOf(args.get(0))) {
483                     actionPerformed = node.performAction(AccessibilityNodeInfo.ACTION_SELECT);
484                 } else if (!Boolean.valueOf(args.get(0))) {
485                     actionPerformed =
486                             node.performAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
487                 } else {
488                     return EARG;
489                 }
490                 return new MonkeyCommandReturn(actionPerformed);
491             }
492             return EARG;
493         }
494     }
495 
496     /**
497      * Command to get whether the given node is focused.
498      */
499     public static class GetFocused implements ViewIntrospectionCommand {
500         //queryview [id type] [id] getfocused
501         //queryview viewid button1 getfocused
query(AccessibilityNodeInfo node, List<String> args)502         public MonkeyCommandReturn query(AccessibilityNodeInfo node,
503                                          List<String> args) {
504             if (args.size() == 0) {
505                 return new MonkeyCommandReturn(true, Boolean.toString(node.isFocused()));
506             }
507             return EARG;
508         }
509     }
510 
511     /**
512      * Command to set the focus status of the given node. Takes a boolean value
513      * as its only argument.
514      */
515     public static class SetFocused implements ViewIntrospectionCommand {
516         //queryview [id type] [id] setfocused [boolean]
517         //queryview viewid button1 setfocused false
query(AccessibilityNodeInfo node, List<String> args)518         public MonkeyCommandReturn query(AccessibilityNodeInfo node,
519                                          List<String> args) {
520             node.setSealed(true);
521             if (args.size() == 1) {
522                 boolean actionPerformed;
523                 if (Boolean.valueOf(args.get(0))) {
524                     actionPerformed = node.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
525                 } else if (!Boolean.valueOf(args.get(0))) {
526                     actionPerformed = node.performAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS);
527                 } else {
528                     return EARG;
529                 }
530                 return new MonkeyCommandReturn(actionPerformed);
531             }
532             return EARG;
533         }
534     }
535 
536     /**
537      * Command to get the accessibility ids of the given node. Returns the accessibility ids as a
538      * space separated pair of integers with window id coming first, followed by the accessibility
539      * view id.
540      */
541     public static class GetAccessibilityIds implements ViewIntrospectionCommand {
542         //queryview [id type] [id] getaccessibilityids
543         //queryview viewid button1 getaccessibilityids
query(AccessibilityNodeInfo node, List<String> args)544         public MonkeyCommandReturn query(AccessibilityNodeInfo node,
545                                          List<String> args) {
546             if (args.size() == 0) {
547                 int viewId;
548                 try {
549                     Class klass = node.getClass();
550                     Field field = klass.getDeclaredField("mAccessibilityViewId");
551                     field.setAccessible(true);
552                     viewId = ((Integer) field.get(node)).intValue();
553                 } catch (NoSuchFieldException e) {
554                     return new MonkeyCommandReturn(false, NO_NODE);
555                 } catch (IllegalAccessException e) {
556                     return new MonkeyCommandReturn(false, "Access exception");
557                 }
558                 String ids = node.getWindowId() + " " + viewId;
559                 return new MonkeyCommandReturn(true, ids);
560             }
561             return EARG;
562         }
563     }
564 
565     /**
566      * Command to get the accessibility ids of the parent of the given node. Returns the
567      * accessibility ids as a space separated pair of integers with window id coming first followed
568      * by the accessibility view id.
569      */
570     public static class GetParent implements ViewIntrospectionCommand {
571         //queryview [id type] [id] getparent
572         //queryview viewid button1 getparent
query(AccessibilityNodeInfo node, List<String> args)573         public MonkeyCommandReturn query(AccessibilityNodeInfo node,
574                                          List<String> args) {
575             if (args.size() == 0) {
576                 AccessibilityNodeInfo parent = node.getParent();
577                 if (parent == null) {
578                   return new MonkeyCommandReturn(false, "Given node has no parent");
579                 }
580                 return (new GetAccessibilityIds()).query(parent, new ArrayList<String>());
581             }
582             return EARG;
583         }
584     }
585 
586     /**
587      * Command to get the accessibility ids of the children of the given node. Returns the
588      * children's ids as a space separated list of integer pairs. Each of the pairs consists of the
589      * window id, followed by the accessibility id.
590      */
591     public static class GetChildren implements ViewIntrospectionCommand {
592         //queryview [id type] [id] getchildren
593         //queryview viewid button1 getchildren
query(AccessibilityNodeInfo node, List<String> args)594         public MonkeyCommandReturn query(AccessibilityNodeInfo node,
595                                          List<String> args) {
596             if (args.size() == 0) {
597                 ViewIntrospectionCommand idGetter = new GetAccessibilityIds();
598                 List<String> emptyArgs = new ArrayList<String>();
599                 StringBuilder ids = new StringBuilder();
600                 int totalChildren = node.getChildCount();
601                 for (int i = 0; i < totalChildren; i++) {
602                     MonkeyCommandReturn result = idGetter.query(node.getChild(i), emptyArgs);
603                     if (!result.wasSuccessful()) {
604                         return result;
605                     } else {
606                         ids.append(result.getMessage()).append(" ");
607                     }
608                 }
609                 return new MonkeyCommandReturn(true, ids.toString());
610             }
611             return EARG;
612         }
613     }
614 }
615