Updating the monkey to use a dedicated UI test bridge.

1. The MonkeySourceNetworkViews was registering a raw event
   listener for accessibility events and hence perform
   connection and cache management in similar way to an
   AccessibilityService. This is fragile and requires
   the Monkey implementor to know internal framework
   stuff. Now the functionality required by the class
   is accessed via the new UiTestAutomationBridge.

Change-Id: Idd5820e42a4b4dfc1d9eed8977ec5ccd96485daa
This commit is contained in:
Svetoslav Ganov
2012-01-23 18:14:56 -08:00
parent fb4ffd37de
commit 4ce026b4b1

View File

@@ -18,49 +18,33 @@ package com.android.commands.monkey;
import static com.android.commands.monkey.MonkeySourceNetwork.EARG; import static com.android.commands.monkey.MonkeySourceNetwork.EARG;
import android.accessibilityservice.IAccessibilityServiceConnection; import android.accessibilityservice.UiTestAutomationBridge;
import android.accessibilityservice.IEventListener;
import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.ServiceManager;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.SystemClock; import android.os.ServiceManager;
import android.util.Log;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityManager;
import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import dalvik.system.DexClassLoader;
import com.android.commands.monkey.MonkeySourceNetwork.CommandQueue; import com.android.commands.monkey.MonkeySourceNetwork.CommandQueue;
import com.android.commands.monkey.MonkeySourceNetwork.MonkeyCommand; import com.android.commands.monkey.MonkeySourceNetwork.MonkeyCommand;
import com.android.commands.monkey.MonkeySourceNetwork.MonkeyCommandReturn; import com.android.commands.monkey.MonkeySourceNetwork.MonkeyCommandReturn;
import dalvik.system.DexClassLoader;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicReference; import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.ArrayList; import java.util.Map;
/** /**
* Utility class that enables Monkey to perform view introspection when issued Monkey Network * Utility class that enables Monkey to perform view introspection when issued Monkey Network
* Script commands over the network. * Script commands over the network.
*/ */
public class MonkeySourceNetworkViews { public class MonkeySourceNetworkViews {
private static final String TAG = "MonkeyViews"; protected static UiTestAutomationBridge sUiTestAutomationBridge;
private static final int TIMEOUT_REGISTER_EVENT_LISTENER = 2000;
private static final int NO_ID = -1;
private static volatile AtomicReference<AccessibilityEvent> sLastAccessibilityEvent
= new AtomicReference<AccessibilityEvent>();
protected static int sConnectionId;
private static IPackageManager sPm = private static IPackageManager sPm =
IPackageManager.Stub.asInterface(ServiceManager.getService("package")); IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
private static Map<String, Class<?>> sClassMap = new HashMap<String, Class<?>>(); private static Map<String, Class<?>> sClassMap = new HashMap<String, Class<?>>();
@@ -100,78 +84,16 @@ public class MonkeySourceNetworkViews {
COMMAND_MAP.put("getaccessibilityids", new GetAccessibilityIds()); COMMAND_MAP.put("getaccessibilityids", new GetAccessibilityIds());
} }
private static int getConnection() throws RemoteException {
if (sConnectionId != NO_ID) {
return sConnectionId;
}
IEventListener listener = new IEventListener.Stub() {
public void setConnection(IAccessibilityServiceConnection connection,
int connectionId) {
sConnectionId = connectionId;
if (connection != null) {
AccessibilityInteractionClient.getInstance().addConnection(connectionId,
connection);
} else {
AccessibilityInteractionClient.getInstance().removeConnection(connectionId);
}
synchronized (MonkeySourceNetworkViews.class) {
notifyAll();
}
}
public void onInterrupt() {}
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.d(TAG, "Accessibility Event");
sLastAccessibilityEvent.set(AccessibilityEvent.obtain(event));
synchronized (MonkeySourceNetworkViews.class) {
notifyAll();
}
}
};
IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
final long beginTime = SystemClock.uptimeMillis();
synchronized (MonkeySourceNetworkViews.class) {
manager.registerEventListener(listener);
while (true) {
if (sConnectionId != NO_ID) {
return sConnectionId;
}
final long elapsedTime = (SystemClock.uptimeMillis() - beginTime);
final long remainingTime = TIMEOUT_REGISTER_EVENT_LISTENER - elapsedTime;
if (remainingTime <= 0) {
if (sConnectionId == NO_ID) {
throw new IllegalStateException("Cound not register IEventListener.");
}
return sConnectionId;
}
try {
MonkeySourceNetworkViews.class.wait(remainingTime);
} catch (InterruptedException ie) {
/* ignore */
}
}
}
}
/** /**
* Registers the event listener for AccessibilityEvents. * Registers the event listener for AccessibilityEvents.
* Also sets up a communication connection so we can query the * Also sets up a communication connection so we can query the
* accessibility service. * accessibility service.
*/ */
public static void setup() { public static void setup() {
try { sUiTestAutomationBridge = new UiTestAutomationBridge();
sConnectionId = getConnection(); sUiTestAutomationBridge.connect();
} catch (RemoteException re) {
Log.e(TAG,"Remote Exception encountered when"
+ " attempting to connect to Accessibility Service");
}
} }
/** /**
* Get the ID class for the given package. * Get the ID class for the given package.
* This will cause issues if people reload a package with different * This will cause issues if people reload a package with different
@@ -181,7 +103,7 @@ public class MonkeySourceNetworkViews {
* @return The ID class for the given package * @return The ID class for the given package
*/ */
private static Class<?> getIdClass(String packageName, String sourceDir) private static Class<?> getIdClass(String packageName, String sourceDir)
throws RemoteException, ClassNotFoundException { throws ClassNotFoundException {
// This kind of reflection is expensive, so let's only do it // This kind of reflection is expensive, so let's only do it
// if we need to // if we need to
Class<?> klass = sClassMap.get(packageName); Class<?> klass = sClassMap.get(packageName);
@@ -240,15 +162,15 @@ public class MonkeySourceNetworkViews {
String windowString, String viewString) { String windowString, String viewString) {
int windowId = Integer.parseInt(windowString); int windowId = Integer.parseInt(windowString);
int viewId = Integer.parseInt(viewString); int viewId = Integer.parseInt(viewString);
return AccessibilityInteractionClient.getInstance() return sUiTestAutomationBridge.findAccessibilityNodeInfoByAccessibilityId(windowId,
.findAccessibilityNodeInfoByAccessibilityId(sConnectionId, windowId, viewId); viewId);
} }
private static AccessibilityNodeInfo getNodeByViewId(String viewId, AccessibilityEvent event) private static AccessibilityNodeInfo getNodeByViewId(String viewId, AccessibilityEvent event)
throws MonkeyViewException { throws MonkeyViewException {
int id = getId(viewId, event); int id = getId(viewId, event);
return AccessibilityInteractionClient.getInstance() return sUiTestAutomationBridge.findAccessibilityNodeInfoByViewId(
.findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, id); UiTestAutomationBridge.ACTIVE_WINDOW_ID, UiTestAutomationBridge.ROOT_NODE_ID, id);
} }
/** /**
@@ -259,11 +181,10 @@ public class MonkeySourceNetworkViews {
//listviews //listviews
public MonkeyCommandReturn translateCommand(List<String> command, public MonkeyCommandReturn translateCommand(List<String> command,
CommandQueue queue) { CommandQueue queue) {
AccessibilityEvent lastEvent = sLastAccessibilityEvent.get(); AccessibilityEvent lastEvent = sUiTestAutomationBridge.getLastAccessibilityEvent();
if (lastEvent == null) { if (lastEvent == null) {
return new MonkeyCommandReturn(false, NO_ACCESSIBILITY_EVENT); return new MonkeyCommandReturn(false, NO_ACCESSIBILITY_EVENT);
} }
lastEvent.setSealed(true);
AccessibilityNodeInfo node = lastEvent.getSource(); AccessibilityNodeInfo node = lastEvent.getSource();
/* Occasionally the API will generate an event with no source, which is essentially the /* Occasionally the API will generate an event with no source, which is essentially the
* same as it generating no event at all */ * same as it generating no event at all */
@@ -300,14 +221,13 @@ public class MonkeySourceNetworkViews {
public MonkeyCommandReturn translateCommand(List<String> command, public MonkeyCommandReturn translateCommand(List<String> command,
CommandQueue queue) { CommandQueue queue) {
if (command.size() > 2) { if (command.size() > 2) {
if (sConnectionId < 0) { if (!sUiTestAutomationBridge.isConnected()) {
return new MonkeyCommandReturn(false, NO_CONNECTION); return new MonkeyCommandReturn(false, NO_CONNECTION);
} }
AccessibilityEvent lastEvent = sLastAccessibilityEvent.get(); AccessibilityEvent lastEvent = sUiTestAutomationBridge.getLastAccessibilityEvent();
if (lastEvent == null) { if (lastEvent == null) {
return new MonkeyCommandReturn(false, NO_ACCESSIBILITY_EVENT); return new MonkeyCommandReturn(false, NO_ACCESSIBILITY_EVENT);
} }
lastEvent.setSealed(true);
String idType = command.get(1); String idType = command.get(1);
AccessibilityNodeInfo node; AccessibilityNodeInfo node;
String viewQuery; String viewQuery;
@@ -352,11 +272,10 @@ public class MonkeySourceNetworkViews {
// getrootview // getrootview
public MonkeyCommandReturn translateCommand(List<String> command, public MonkeyCommandReturn translateCommand(List<String> command,
CommandQueue queue) { CommandQueue queue) {
AccessibilityEvent lastEvent = sLastAccessibilityEvent.get(); AccessibilityEvent lastEvent = sUiTestAutomationBridge.getLastAccessibilityEvent();
if (lastEvent == null) { if (lastEvent == null) {
return new MonkeyCommandReturn(false, NO_ACCESSIBILITY_EVENT); return new MonkeyCommandReturn(false, NO_ACCESSIBILITY_EVENT);
} }
lastEvent.setSealed(true);
AccessibilityNodeInfo node = lastEvent.getSource(); AccessibilityNodeInfo node = lastEvent.getSource();
return (new GetAccessibilityIds()).query(node, new ArrayList<String>()); return (new GetAccessibilityIds()).query(node, new ArrayList<String>());
} }
@@ -372,13 +291,14 @@ public class MonkeySourceNetworkViews {
// getviewswithtext "some text here" // getviewswithtext "some text here"
public MonkeyCommandReturn translateCommand(List<String> command, public MonkeyCommandReturn translateCommand(List<String> command,
CommandQueue queue) { CommandQueue queue) {
if (sConnectionId < 0) { if (!sUiTestAutomationBridge.isConnected()) {
return new MonkeyCommandReturn(false, NO_CONNECTION); return new MonkeyCommandReturn(false, NO_CONNECTION);
} }
if (command.size() == 2) { if (command.size() == 2) {
String text = command.get(1); String text = command.get(1);
List<AccessibilityNodeInfo> nodes = AccessibilityInteractionClient.getInstance() List<AccessibilityNodeInfo> nodes = sUiTestAutomationBridge
.findAccessibilityNodeInfosByTextInActiveWindow(sConnectionId, text); .findAccessibilityNodeInfosByText(UiTestAutomationBridge.ACTIVE_WINDOW_ID,
UiTestAutomationBridge.ROOT_NODE_ID, text);
ViewIntrospectionCommand idGetter = new GetAccessibilityIds(); ViewIntrospectionCommand idGetter = new GetAccessibilityIds();
List<String> emptyArgs = new ArrayList<String>(); List<String> emptyArgs = new ArrayList<String>();
StringBuilder ids = new StringBuilder(); StringBuilder ids = new StringBuilder();
@@ -550,7 +470,6 @@ public class MonkeySourceNetworkViews {
//queryview viewid button1 setfocused false //queryview viewid button1 setfocused false
public MonkeyCommandReturn query(AccessibilityNodeInfo node, public MonkeyCommandReturn query(AccessibilityNodeInfo node,
List<String> args) { List<String> args) {
node.setSealed(true);
if (args.size() == 1) { if (args.size() == 1) {
boolean actionPerformed; boolean actionPerformed;
if (Boolean.valueOf(args.get(0))) { if (Boolean.valueOf(args.get(0))) {
@@ -579,7 +498,7 @@ public class MonkeySourceNetworkViews {
if (args.size() == 0) { if (args.size() == 0) {
int viewId; int viewId;
try { try {
Class klass = node.getClass(); Class<?> klass = node.getClass();
Field field = klass.getDeclaredField("mAccessibilityViewId"); Field field = klass.getDeclaredField("mAccessibilityViewId");
field.setAccessible(true); field.setAccessible(true);
viewId = ((Integer) field.get(node)).intValue(); viewId = ((Integer) field.get(node)).intValue();