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