| /* |
| * Copyright 2011, The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.commands.monkey; |
| |
| import static com.android.commands.monkey.MonkeySourceNetwork.EARG; |
| |
| import android.accessibilityservice.UiTestAutomationBridge; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.IPackageManager; |
| import android.graphics.Rect; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.UserId; |
| import android.view.accessibility.AccessibilityEvent; |
| import android.view.accessibility.AccessibilityNodeInfo; |
| |
| import com.android.commands.monkey.MonkeySourceNetwork.CommandQueue; |
| import com.android.commands.monkey.MonkeySourceNetwork.MonkeyCommand; |
| import com.android.commands.monkey.MonkeySourceNetwork.MonkeyCommandReturn; |
| |
| import dalvik.system.DexClassLoader; |
| |
| import java.lang.reflect.Field; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Utility class that enables Monkey to perform view introspection when issued Monkey Network |
| * Script commands over the network. |
| */ |
| public class MonkeySourceNetworkViews { |
| protected static UiTestAutomationBridge sUiTestAutomationBridge; |
| private static IPackageManager sPm = |
| IPackageManager.Stub.asInterface(ServiceManager.getService("package")); |
| private static Map<String, Class<?>> sClassMap = new HashMap<String, Class<?>>(); |
| |
| private static final String REMOTE_ERROR = |
| "Unable to retrieve application info from PackageManager"; |
| private static final String CLASS_NOT_FOUND = "Error retrieving class information"; |
| private static final String NO_ACCESSIBILITY_EVENT = "No accessibility event has occured yet"; |
| private static final String NO_NODE = "Node with given ID does not exist"; |
| private static final String NO_CONNECTION = "Failed to connect to AccessibilityService, " |
| + "try restarting Monkey"; |
| |
| private static final Map<String, ViewIntrospectionCommand> COMMAND_MAP = |
| new HashMap<String, ViewIntrospectionCommand>(); |
| |
| /* Interface for view queries */ |
| private static interface ViewIntrospectionCommand { |
| /** |
| * Get the response to the query |
| * @return the response to the query |
| */ |
| public MonkeyCommandReturn query(AccessibilityNodeInfo node, List<String> args); |
| } |
| |
| static { |
| COMMAND_MAP.put("getlocation", new GetLocation()); |
| COMMAND_MAP.put("gettext", new GetText()); |
| COMMAND_MAP.put("getclass", new GetClass()); |
| COMMAND_MAP.put("getchecked", new GetChecked()); |
| COMMAND_MAP.put("getenabled", new GetEnabled()); |
| COMMAND_MAP.put("getselected", new GetSelected()); |
| COMMAND_MAP.put("setselected", new SetSelected()); |
| COMMAND_MAP.put("getfocused", new GetFocused()); |
| COMMAND_MAP.put("setfocused", new SetFocused()); |
| COMMAND_MAP.put("getparent", new GetParent()); |
| COMMAND_MAP.put("getchildren", new GetChildren()); |
| COMMAND_MAP.put("getaccessibilityids", new GetAccessibilityIds()); |
| } |
| |
| /** |
| * Registers the event listener for AccessibilityEvents. |
| * Also sets up a communication connection so we can query the |
| * accessibility service. |
| */ |
| public static void setup() { |
| sUiTestAutomationBridge = new UiTestAutomationBridge(); |
| sUiTestAutomationBridge.connect(); |
| } |
| |
| /** |
| * Get the ID class for the given package. |
| * This will cause issues if people reload a package with different |
| * resource identifiers, but don't restart the Monkey server. |
| * |
| * @param packageName The package that we want to retrieve the ID class for |
| * @return The ID class for the given package |
| */ |
| private static Class<?> getIdClass(String packageName, String sourceDir) |
| throws ClassNotFoundException { |
| // This kind of reflection is expensive, so let's only do it |
| // if we need to |
| Class<?> klass = sClassMap.get(packageName); |
| if (klass == null) { |
| DexClassLoader classLoader = new DexClassLoader( |
| sourceDir, "/data/local/tmp", |
| null, ClassLoader.getSystemClassLoader()); |
| klass = classLoader.loadClass(packageName + ".R$id"); |
| sClassMap.put(packageName, klass); |
| } |
| return klass; |
| } |
| |
| private static String getPositionFromNode(AccessibilityNodeInfo node) { |
| Rect nodePosition = new Rect(); |
| node.getBoundsInScreen(nodePosition); |
| StringBuilder positions = new StringBuilder(); |
| positions.append(nodePosition.left).append(" ").append(nodePosition.top); |
| positions.append(" ").append(nodePosition.right-nodePosition.left).append(" "); |
| positions.append(nodePosition.bottom-nodePosition.top); |
| return positions.toString(); |
| } |
| |
| |
| /** |
| * Converts a resource identifier into it's generated integer ID |
| * |
| * @param stringId the string identifier |
| * @return the generated integer identifier. |
| */ |
| private static int getId(String stringId, AccessibilityEvent event) |
| throws MonkeyViewException { |
| try { |
| AccessibilityNodeInfo node = event.getSource(); |
| String packageName = node.getPackageName().toString(); |
| ApplicationInfo appInfo = sPm.getApplicationInfo(packageName, 0, UserId.myUserId()); |
| Class<?> klass; |
| klass = getIdClass(packageName, appInfo.sourceDir); |
| return klass.getField(stringId).getInt(null); |
| } catch (RemoteException e) { |
| throw new MonkeyViewException(REMOTE_ERROR); |
| } catch (ClassNotFoundException e){ |
| throw new MonkeyViewException(e.getMessage()); |
| } catch (NoSuchFieldException e){ |
| throw new MonkeyViewException("No such node with given id"); |
| } catch (IllegalAccessException e){ |
| throw new MonkeyViewException("Private identifier"); |
| } catch (NullPointerException e) { |
| // AccessibilityServiceConnection throws a NullPointerException if you hand it |
| // an ID that doesn't exist onscreen |
| throw new MonkeyViewException("No node with given id exists onscreen"); |
| } |
| } |
| |
| private static AccessibilityNodeInfo getNodeByAccessibilityIds( |
| String windowString, String viewString) { |
| int windowId = Integer.parseInt(windowString); |
| int viewId = Integer.parseInt(viewString); |
| return sUiTestAutomationBridge.findAccessibilityNodeInfoByAccessibilityId(windowId, |
| viewId); |
| } |
| |
| private static AccessibilityNodeInfo getNodeByViewId(String viewId, AccessibilityEvent event) |
| throws MonkeyViewException { |
| int id = getId(viewId, event); |
| return sUiTestAutomationBridge.findAccessibilityNodeInfoByViewId( |
| UiTestAutomationBridge.ACTIVE_WINDOW_ID, UiTestAutomationBridge.ROOT_NODE_ID, id); |
| } |
| |
| /** |
| * Command to list all possible view ids for the given application. |
| * This lists all view ids regardless if they are on screen or not. |
| */ |
| public static class ListViewsCommand implements MonkeyCommand { |
| //listviews |
| public MonkeyCommandReturn translateCommand(List<String> command, |
| CommandQueue queue) { |
| AccessibilityEvent lastEvent = sUiTestAutomationBridge.getLastAccessibilityEvent(); |
| if (lastEvent == null) { |
| return new MonkeyCommandReturn(false, NO_ACCESSIBILITY_EVENT); |
| } |
| AccessibilityNodeInfo node = lastEvent.getSource(); |
| /* Occasionally the API will generate an event with no source, which is essentially the |
| * same as it generating no event at all */ |
| if (node == null) { |
| return new MonkeyCommandReturn(false, NO_ACCESSIBILITY_EVENT); |
| } |
| String packageName = node.getPackageName().toString(); |
| try{ |
| Class<?> klass; |
| ApplicationInfo appInfo = sPm.getApplicationInfo(packageName, 0, UserId.myUserId()); |
| klass = getIdClass(packageName, appInfo.sourceDir); |
| StringBuilder fieldBuilder = new StringBuilder(); |
| Field[] fields = klass.getFields(); |
| for (Field field : fields) { |
| fieldBuilder.append(field.getName() + " "); |
| } |
| return new MonkeyCommandReturn(true, fieldBuilder.toString()); |
| } catch (RemoteException e){ |
| return new MonkeyCommandReturn(false, REMOTE_ERROR); |
| } catch (ClassNotFoundException e){ |
| return new MonkeyCommandReturn(false, CLASS_NOT_FOUND); |
| } |
| } |
| } |
| |
| /** |
| * A command that allows for querying of views. It takes an id type, the requisite ids, |
| * and the command for querying the view. |
| */ |
| public static class QueryViewCommand implements MonkeyCommand { |
| //queryview [id type] [id(s)] [command] |
| //queryview viewid button1 gettext |
| //queryview accessibilityids 12 5 getparent |
| public MonkeyCommandReturn translateCommand(List<String> command, |
| CommandQueue queue) { |
| if (command.size() > 2) { |
| if (!sUiTestAutomationBridge.isConnected()) { |
| return new MonkeyCommandReturn(false, NO_CONNECTION); |
| } |
| AccessibilityEvent lastEvent = sUiTestAutomationBridge.getLastAccessibilityEvent(); |
| if (lastEvent == null) { |
| return new MonkeyCommandReturn(false, NO_ACCESSIBILITY_EVENT); |
| } |
| String idType = command.get(1); |
| AccessibilityNodeInfo node; |
| String viewQuery; |
| List<String> args; |
| if ("viewid".equals(idType)) { |
| try { |
| node = getNodeByViewId(command.get(2), lastEvent); |
| viewQuery = command.get(3); |
| args = command.subList(4, command.size()); |
| } catch (MonkeyViewException e) { |
| return new MonkeyCommandReturn(false, e.getMessage()); |
| } |
| } else if (idType.equals("accessibilityids")) { |
| try { |
| node = getNodeByAccessibilityIds(command.get(2), command.get(3)); |
| viewQuery = command.get(4); |
| args = command.subList(5, command.size()); |
| } catch (NumberFormatException e) { |
| return EARG; |
| } |
| } else { |
| return EARG; |
| } |
| if (node == null) { |
| return new MonkeyCommandReturn(false, NO_NODE); |
| } |
| ViewIntrospectionCommand getter = COMMAND_MAP.get(viewQuery); |
| if (getter != null) { |
| return getter.query(node, args); |
| } else { |
| return EARG; |
| } |
| } |
| return EARG; |
| } |
| } |
| |
| /** |
| * A command that returns the accessibility ids of the root view. |
| */ |
| public static class GetRootViewCommand implements MonkeyCommand { |
| // getrootview |
| public MonkeyCommandReturn translateCommand(List<String> command, |
| CommandQueue queue) { |
| AccessibilityEvent lastEvent = sUiTestAutomationBridge.getLastAccessibilityEvent(); |
| if (lastEvent == null) { |
| return new MonkeyCommandReturn(false, NO_ACCESSIBILITY_EVENT); |
| } |
| AccessibilityNodeInfo node = lastEvent.getSource(); |
| return (new GetAccessibilityIds()).query(node, new ArrayList<String>()); |
| } |
| } |
| |
| /** |
| * A command that returns the accessibility ids of the views that contain the given text. |
| * It takes a string of text and returns the accessibility ids of the nodes that contain the |
| * text as a list of integers separated by spaces. |
| */ |
| public static class GetViewsWithTextCommand implements MonkeyCommand { |
| // getviewswithtext [text] |
| // getviewswithtext "some text here" |
| public MonkeyCommandReturn translateCommand(List<String> command, |
| CommandQueue queue) { |
| if (!sUiTestAutomationBridge.isConnected()) { |
| return new MonkeyCommandReturn(false, NO_CONNECTION); |
| } |
| if (command.size() == 2) { |
| String text = command.get(1); |
| List<AccessibilityNodeInfo> nodes = sUiTestAutomationBridge |
| .findAccessibilityNodeInfosByText(UiTestAutomationBridge.ACTIVE_WINDOW_ID, |
| UiTestAutomationBridge.ROOT_NODE_ID, text); |
| ViewIntrospectionCommand idGetter = new GetAccessibilityIds(); |
| List<String> emptyArgs = new ArrayList<String>(); |
| StringBuilder ids = new StringBuilder(); |
| for (AccessibilityNodeInfo node : nodes) { |
| MonkeyCommandReturn result = idGetter.query(node, emptyArgs); |
| if (!result.wasSuccessful()){ |
| return result; |
| } |
| ids.append(result.getMessage()).append(" "); |
| } |
| return new MonkeyCommandReturn(true, ids.toString()); |
| } |
| return EARG; |
| } |
| } |
| |
| /** |
| * Command to retrieve the location of the given node. |
| * Returns the x, y, width and height of the view, separated by spaces. |
| */ |
| public static class GetLocation implements ViewIntrospectionCommand { |
| //queryview [id type] [id] getlocation |
| //queryview viewid button1 getlocation |
| public MonkeyCommandReturn query(AccessibilityNodeInfo node, |
| List<String> args) { |
| if (args.size() == 0) { |
| Rect nodePosition = new Rect(); |
| node.getBoundsInScreen(nodePosition); |
| StringBuilder positions = new StringBuilder(); |
| positions.append(nodePosition.left).append(" ").append(nodePosition.top); |
| positions.append(" ").append(nodePosition.right-nodePosition.left).append(" "); |
| positions.append(nodePosition.bottom-nodePosition.top); |
| return new MonkeyCommandReturn(true, positions.toString()); |
| } |
| return EARG; |
| } |
| } |
| |
| |
| /** |
| * Command to retrieve the text of the given node |
| */ |
| public static class GetText implements ViewIntrospectionCommand { |
| //queryview [id type] [id] gettext |
| //queryview viewid button1 gettext |
| public MonkeyCommandReturn query(AccessibilityNodeInfo node, |
| List<String> args) { |
| if (args.size() == 0) { |
| if (node.isPassword()){ |
| return new MonkeyCommandReturn(false, "Node contains a password"); |
| } |
| /* Occasionally we get a null from the accessibility API, rather than an empty |
| * string */ |
| if (node.getText() == null) { |
| return new MonkeyCommandReturn(true, ""); |
| } |
| return new MonkeyCommandReturn(true, node.getText().toString()); |
| } |
| return EARG; |
| } |
| } |
| |
| |
| /** |
| * Command to retrieve the class name of the given node |
| */ |
| public static class GetClass implements ViewIntrospectionCommand { |
| //queryview [id type] [id] getclass |
| //queryview viewid button1 getclass |
| public MonkeyCommandReturn query(AccessibilityNodeInfo node, |
| List<String> args) { |
| if (args.size() == 0) { |
| return new MonkeyCommandReturn(true, node.getClassName().toString()); |
| } |
| return EARG; |
| } |
| } |
| /** |
| * Command to retrieve the checked status of the given node |
| */ |
| public static class GetChecked implements ViewIntrospectionCommand { |
| //queryview [id type] [id] getchecked |
| //queryview viewid button1 getchecked |
| public MonkeyCommandReturn query(AccessibilityNodeInfo node, |
| List<String> args) { |
| if (args.size() == 0) { |
| return new MonkeyCommandReturn(true, Boolean.toString(node.isChecked())); |
| } |
| return EARG; |
| } |
| } |
| |
| /** |
| * Command to retrieve whether the given node is enabled |
| */ |
| public static class GetEnabled implements ViewIntrospectionCommand { |
| //queryview [id type] [id] getenabled |
| //queryview viewid button1 getenabled |
| public MonkeyCommandReturn query(AccessibilityNodeInfo node, |
| List<String> args) { |
| if (args.size() == 0) { |
| return new MonkeyCommandReturn(true, Boolean.toString(node.isEnabled())); |
| } |
| return EARG; |
| } |
| } |
| |
| /** |
| * Command to retrieve whether the given node is selected |
| */ |
| public static class GetSelected implements ViewIntrospectionCommand { |
| //queryview [id type] [id] getselected |
| //queryview viewid button1 getselected |
| public MonkeyCommandReturn query(AccessibilityNodeInfo node, |
| List<String> args) { |
| if (args.size() == 0) { |
| return new MonkeyCommandReturn(true, Boolean.toString(node.isSelected())); |
| } |
| return EARG; |
| } |
| } |
| |
| /** |
| * Command to set the selected status of the given node. Takes a boolean value as its only |
| * argument. |
| */ |
| public static class SetSelected implements ViewIntrospectionCommand { |
| //queryview [id type] [id] setselected [boolean] |
| //queryview viewid button1 setselected true |
| public MonkeyCommandReturn query(AccessibilityNodeInfo node, |
| List<String> args) { |
| if (args.size() == 1) { |
| boolean actionPerformed; |
| if (Boolean.valueOf(args.get(0))) { |
| actionPerformed = node.performAction(AccessibilityNodeInfo.ACTION_SELECT); |
| } else if (!Boolean.valueOf(args.get(0))) { |
| actionPerformed = |
| node.performAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION); |
| } else { |
| return EARG; |
| } |
| return new MonkeyCommandReturn(actionPerformed); |
| } |
| return EARG; |
| } |
| } |
| |
| /** |
| * Command to get whether the given node is focused. |
| */ |
| public static class GetFocused implements ViewIntrospectionCommand { |
| //queryview [id type] [id] getfocused |
| //queryview viewid button1 getfocused |
| public MonkeyCommandReturn query(AccessibilityNodeInfo node, |
| List<String> args) { |
| if (args.size() == 0) { |
| return new MonkeyCommandReturn(true, Boolean.toString(node.isFocused())); |
| } |
| return EARG; |
| } |
| } |
| |
| /** |
| * Command to set the focus status of the given node. Takes a boolean value |
| * as its only argument. |
| */ |
| public static class SetFocused implements ViewIntrospectionCommand { |
| //queryview [id type] [id] setfocused [boolean] |
| //queryview viewid button1 setfocused false |
| public MonkeyCommandReturn query(AccessibilityNodeInfo node, |
| List<String> args) { |
| if (args.size() == 1) { |
| boolean actionPerformed; |
| if (Boolean.valueOf(args.get(0))) { |
| actionPerformed = node.performAction(AccessibilityNodeInfo.ACTION_FOCUS); |
| } else if (!Boolean.valueOf(args.get(0))) { |
| actionPerformed = node.performAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS); |
| } else { |
| return EARG; |
| } |
| return new MonkeyCommandReturn(actionPerformed); |
| } |
| return EARG; |
| } |
| } |
| |
| /** |
| * Command to get the accessibility ids of the given node. Returns the accessibility ids as a |
| * space separated pair of integers with window id coming first, followed by the accessibility |
| * view id. |
| */ |
| public static class GetAccessibilityIds implements ViewIntrospectionCommand { |
| //queryview [id type] [id] getaccessibilityids |
| //queryview viewid button1 getaccessibilityids |
| public MonkeyCommandReturn query(AccessibilityNodeInfo node, |
| List<String> args) { |
| if (args.size() == 0) { |
| int viewId; |
| try { |
| Class<?> klass = node.getClass(); |
| Field field = klass.getDeclaredField("mAccessibilityViewId"); |
| field.setAccessible(true); |
| viewId = ((Integer) field.get(node)).intValue(); |
| } catch (NoSuchFieldException e) { |
| return new MonkeyCommandReturn(false, NO_NODE); |
| } catch (IllegalAccessException e) { |
| return new MonkeyCommandReturn(false, "Access exception"); |
| } |
| String ids = node.getWindowId() + " " + viewId; |
| return new MonkeyCommandReturn(true, ids); |
| } |
| return EARG; |
| } |
| } |
| |
| /** |
| * Command to get the accessibility ids of the parent of the given node. Returns the |
| * accessibility ids as a space separated pair of integers with window id coming first followed |
| * by the accessibility view id. |
| */ |
| public static class GetParent implements ViewIntrospectionCommand { |
| //queryview [id type] [id] getparent |
| //queryview viewid button1 getparent |
| public MonkeyCommandReturn query(AccessibilityNodeInfo node, |
| List<String> args) { |
| if (args.size() == 0) { |
| AccessibilityNodeInfo parent = node.getParent(); |
| if (parent == null) { |
| return new MonkeyCommandReturn(false, "Given node has no parent"); |
| } |
| return (new GetAccessibilityIds()).query(parent, new ArrayList<String>()); |
| } |
| return EARG; |
| } |
| } |
| |
| /** |
| * Command to get the accessibility ids of the children of the given node. Returns the |
| * children's ids as a space separated list of integer pairs. Each of the pairs consists of the |
| * window id, followed by the accessibility id. |
| */ |
| public static class GetChildren implements ViewIntrospectionCommand { |
| //queryview [id type] [id] getchildren |
| //queryview viewid button1 getchildren |
| public MonkeyCommandReturn query(AccessibilityNodeInfo node, |
| List<String> args) { |
| if (args.size() == 0) { |
| ViewIntrospectionCommand idGetter = new GetAccessibilityIds(); |
| List<String> emptyArgs = new ArrayList<String>(); |
| StringBuilder ids = new StringBuilder(); |
| int totalChildren = node.getChildCount(); |
| for (int i = 0; i < totalChildren; i++) { |
| MonkeyCommandReturn result = idGetter.query(node.getChild(i), emptyArgs); |
| if (!result.wasSuccessful()) { |
| return result; |
| } else { |
| ids.append(result.getMessage()).append(" "); |
| } |
| } |
| return new MonkeyCommandReturn(true, ids.toString()); |
| } |
| return EARG; |
| } |
| } |
| } |