blob: db13824e573e3232fe13fc063fc447ab23194405 [file] [log] [blame]
package com.android.testing.uiautomation;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Calendar;
/**
* A convenient class for accessing UI automation service
*
* This class hides implementation details of starting Android services.
* It provides identical interface to the service. However each function
* still throws {@link RemoteException}
*
*/
public class AutomationProvider {
private static final String LOGTAG = "UiAutomationClientLibrary";
private static final String SERVICE_NAME = "com.android.testing.uiautomation";
private static final long SERVICE_BIND_TIMEOUT = 3000;
private Provider mService = null;
private Object mServiceLock = null;
private TraceLogger mTraceLogger = new TraceLogger();
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mServiceLock) {
mService = null;
Log.e(LOGTAG, "Provider service disconnected unexptectedly.");
mServiceLock.notifyAll();
}
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mServiceLock) {
mService = Provider.Stub.asInterface(service);
mServiceLock.notifyAll();
}
}
};
/**
*
* Initializes the UI automation provider instance.
*
* The constructor will attempt to bind the UI automation service
*
* @param context
* @throws IOException if the provider failed to bind the service
*/
public AutomationProvider(Context context) throws IOException {
mServiceLock = new Object();
if (!context.bindService(new Intent(SERVICE_NAME),
mServiceConnection, Context.BIND_AUTO_CREATE)) {
throw new IOException("Failed to connect to Provider service");
}
synchronized (mServiceLock) {
if (mService == null) {
try {
// wait 3s for the service to finish connecting, should take less than that
mServiceLock.wait(SERVICE_BIND_TIMEOUT);
} catch (InterruptedException ie) {
}
}
}
if (mService == null) {
throw new IOException("Failed to connect to Provider service");
}
try {
if (!mService.checkUiVerificationEnabled()) {
throw new IOException("dependent services are not running");
}
} catch (RemoteException re) {
throw new IOException("error checking dependent services", re);
}
}
/**
* Retrieves the name (or textual information) of current foreground activity
* @return the name
* @throws RemoteException
*/
public String getCurrentActivityName() throws RemoteException {
String result = null;
try {
result = mService.getCurrentActivityName();
} catch (RemoteException re) {
mTraceLogger.logTrace(TraceLogger.FUNC_GET_CURR_ACTIVITY_NAME, re);
throw re;
}
mTraceLogger.logTrace(TraceLogger.FUNC_GET_CURR_ACTIVITY_NAME, result);
return result;
}
/**
* Retrieves the Android package name of current foreground activity
* @return the Android package name
* @throws RemoteException
*/
public String getCurrentActivityPackage() throws RemoteException {
String result = null;
try {
result = mService.getCurrentActivityPackage();
} catch (RemoteException re) {
mTraceLogger.logTrace(TraceLogger.FUNC_GET_CURR_ACTIVITY_PKG, re);
throw re;
}
mTraceLogger.logTrace(TraceLogger.FUNC_GET_CURR_ACTIVITY_PKG, result);
return result;
}
/**
* Retrieves the class name of current foreground activity
* @return the class name
* @throws RemoteException
*/
public String getCurrentActivityClass() throws RemoteException {
String result = null;
try {
result = mService.getCurrentActivityClass();
} catch (RemoteException re) {
mTraceLogger.logTrace(TraceLogger.FUNC_GET_CURR_ACTIVITY_CLASS, re);
throw re;
}
mTraceLogger.logTrace(TraceLogger.FUNC_GET_CURR_ACTIVITY_CLASS, result);
return result;
}
/**
* Check if the UI widget is enabled
* @param selector a selector to identify the UI widget
* @return if it's enabled or not
* @throws RemoteException when the UI widget cannot be found or other service errors
*/
public boolean isEnabled(String selector) throws RemoteException {
boolean result = false;
try {
result = mService.isEnabled(selector);
} catch (RemoteException re) {
mTraceLogger.logTrace(TraceLogger.FUNC_IS_ENABLED, selector, re);
throw re;
}
mTraceLogger.logTrace(TraceLogger.FUNC_IS_ENABLED, selector, result);
return result;
}
/**
* Check if the UI widget is focused
* @param selector a selector to identify the UI widget
* @return if it's focused or not
* @throws RemoteException when the UI widget cannot be found or other service errors
*/
public boolean isFocused(String selector) throws RemoteException {
boolean result = false;
try {
result = mService.isFocused(selector);
} catch (RemoteException re) {
mTraceLogger.logTrace(TraceLogger.FUNC_IS_FOCUSED, selector, re);
throw re;
}
mTraceLogger.logTrace(TraceLogger.FUNC_IS_FOCUSED, selector, result);
return result;
}
/**
* Get the number of children of the UI widget
* @param selector a selector to identify the UI widget
* @return the number of children
* @throws RemoteException when the UI widget cannot be found or other service errors
*/
public int getChildCount(String selector) throws RemoteException {
int result = 0;
try {
result = mService.getChildCount(selector);
} catch (RemoteException re) {
mTraceLogger.logTrace(TraceLogger.FUNC_GET_CHILD_COUNT, selector, re);
throw re;
}
mTraceLogger.logTrace(TraceLogger.FUNC_GET_CHILD_COUNT, selector, result);
return result;
}
/**
* Get the text of the UI widget
* @param selector a selector to identify the UI widget
* @return the text, or null when the UI widget cannot be found
* @throws RemoteException
*/
public String getText(String selector) throws RemoteException {
String result = null;
try {
result = mService.getText(selector);
} catch (RemoteException re) {
mTraceLogger.logTrace(TraceLogger.FUNC_GET_TEXT, selector, re);
throw re;
}
mTraceLogger.logTrace(TraceLogger.FUNC_GET_TEXT, selector, result);
return result;
}
/**
* Get the class name of the UI widget
* @param selector a selector to identify the UI widget
* @return the class name, or null when the UI widget cannot be found
* @throws RemoteException
*/
public String getClassName(String selector) throws RemoteException {
String result = null;
try {
result = mService.getClassName(selector);
} catch (RemoteException re) {
mTraceLogger.logTrace(TraceLogger.FUNC_GET_CLASS_NAME, selector, re);
throw re;
}
mTraceLogger.logTrace(TraceLogger.FUNC_GET_CLASS_NAME, selector, result);
return result;
}
/**
* Perform a click on the UI widget
* @param selector a selector to identify the UI widget
* @return true if the click succeeded, false if the widget cannot be found or other errors
* @throws RemoteException
*/
public boolean click(String selector) throws RemoteException {
boolean result = false;
try {
result = mService.click(selector);
} catch (RemoteException re) {
mTraceLogger.logTrace(TraceLogger.FUNC_CLICK, selector, re);
throw re;
}
mTraceLogger.logTrace(TraceLogger.FUNC_CLICK, selector, result);
return result;
}
/**
* Set the text of a text field identified by the preceding label
* @param label the text content of the label preceding the text field
* @param text the text to fill into the text field
* @return true if setting text succeeded, false if the widget cannot be found or other errors
* @throws RemoteException
*/
public boolean setTextFieldByLabel(String label, String text) throws RemoteException {
boolean result = false;
try {
result = mService.setTextFieldByLabel(label, text);
} catch (RemoteException re) {
mTraceLogger.logTrace(TraceLogger.FUNC_SET_TEXT_BY_LABEL, label, text, re);
throw re;
}
mTraceLogger.logTrace(TraceLogger.FUNC_SET_TEXT_BY_LABEL, label, text, result);
return result;
}
/**
* Send the text via key presses, the caller is responsible for moving input focus to proper
* UI widget first
* @param text the text to fill into the text field
* @return true if input succeeded
* @throws RemoteException
*/
public boolean sendText(String text) throws RemoteException {
boolean result = false;
try {
result = mService.sendText(text);
} catch (RemoteException re) {
mTraceLogger.logTrace(TraceLogger.FUNC_INPUT_TEXT, text, re);
throw re;
}
mTraceLogger.logTrace(TraceLogger.FUNC_INPUT_TEXT, text, result);
return result;
}
/**
* Enable trace logging of all calls to UI automation service
* @param pw a {@link PrintWriter} to receive log entries
*/
public void setTraceLoggerOutput(Writer writer) {
mTraceLogger.setOutput(writer);
}
/**
* A utility class that logs calls to AutomationProvider
*
*/
private class TraceLogger {
private Writer mWriter;
private String mLastLog;
private boolean mWroteDot = false;
public static final int FUNC_CLICK = 0;
public static final int FUNC_GET_CHILD_COUNT = 1;
public static final int FUNC_GET_CLASS_NAME = 2;
public static final int FUNC_GET_CURR_ACTIVITY_CLASS = 3;
public static final int FUNC_GET_CURR_ACTIVITY_NAME = 4;
public static final int FUNC_GET_CURR_ACTIVITY_PKG = 5;
public static final int FUNC_GET_TEXT = 6;
public static final int FUNC_INPUT_TEXT = 7;
public static final int FUNC_IS_ENABLED = 8;
public static final int FUNC_IS_FOCUSED = 9;
public static final int FUNC_SET_TEXT_BY_LABEL = 10;
public void setOutput(Writer writer) {
mWriter = writer;
}
/**
* Log the function call
* @param function the int id to identify the call
* @param args arguments passed to the function AND the return value as last argument
*/
public void logTrace(int function, Object...args) {
if (mWriter != null) {
Object result = null;
// strip out argument list
String argList = "[]";
if (args != null && args.length > 0) {
result = args[args.length - 1];
if (args.length > 1) {
StringBuffer sb = new StringBuffer();
sb.append('[');
for (int i = 0; i < args.length - 1; i++) {
if (args[i] != null) {
sb.append(args[i].toString());
} else {
sb.append("null");
}
sb.append(',');
}
sb.append(']');
argList = sb.toString();
}
}
// separate out return value, or exception
String str = null;
if (result == null) {
result = "null";
} else if (result instanceof Throwable) {
Throwable t = (Throwable)result;
str = String.format("%s(%s)", t.getClass().toString(), t.getMessage());
} else {
str = result.toString();
}
try {
// avoid spamming with duplicate log entries
String log = String.format("func:[%s] args:%s return:[%s]",
getFunctionName(function), argList, str);
if (log.equals(mLastLog)) {
mWriter.write('.');
mWroteDot = true;
} else {
if (mWroteDot) mWriter.write('\n');
mWroteDot = false;
mWriter.write(String.format("%s %s\n", now(), log));
mWriter.flush();
}
mLastLog = log;
} catch (IOException e) {
}
}
}
private String now() {
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("MM-dd hh:mm:ss.SSS");
return sdf.format(cal.getTime());
}
private String getFunctionName(int func) {
switch (func) {
case FUNC_CLICK:
return "click";
case FUNC_GET_CHILD_COUNT:
return "getChildCount";
case FUNC_GET_CLASS_NAME:
return "getClassName";
case FUNC_GET_CURR_ACTIVITY_CLASS:
return "getCurrentActivityClass";
case FUNC_GET_CURR_ACTIVITY_NAME:
return "getCurrentActivityName";
case FUNC_GET_CURR_ACTIVITY_PKG:
return "getCurrentActivityPackage";
case FUNC_GET_TEXT:
return "getText";
case FUNC_INPUT_TEXT:
return "inputText";
case FUNC_IS_ENABLED:
return "isEnabled";
case FUNC_IS_FOCUSED:
return "isFocused";
case FUNC_SET_TEXT_BY_LABEL:
return "setTextFieldByLabel";
default:
return "unknown";
}
}
}
}