android.app.Instrumentation Maven / Gradle / Ivy
Show all versions of android-all Show documentation
/*
* Copyright (C) 2006 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 android.app;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.hardware.input.InputManager;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
import android.os.Looper;
import android.os.MessageQueue;
import android.os.PerformanceCollector;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.AndroidRuntimeException;
import android.util.Log;
import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.Window;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Base class for implementing application instrumentation code. When running
* with instrumentation turned on, this class will be instantiated for you
* before any of the application code, allowing you to monitor all of the
* interaction the system has with the application. An Instrumentation
* implementation is described to the system through an AndroidManifest.xml's
* <instrumentation> tag.
*/
public class Instrumentation {
/**
* If included in the status or final bundle sent to an IInstrumentationWatcher, this key
* identifies the class that is writing the report. This can be used to provide more structured
* logging or reporting capabilities in the IInstrumentationWatcher.
*/
public static final String REPORT_KEY_IDENTIFIER = "id";
/**
* If included in the status or final bundle sent to an IInstrumentationWatcher, this key
* identifies a string which can simply be printed to the output stream. Using these streams
* provides a "pretty printer" version of the status & final packets. Any bundles including
* this key should also include the complete set of raw key/value pairs, so that the
* instrumentation can also be launched, and results collected, by an automated system.
*/
public static final String REPORT_KEY_STREAMRESULT = "stream";
private static final String TAG = "Instrumentation";
private final Object mSync = new Object();
private ActivityThread mThread = null;
private MessageQueue mMessageQueue = null;
private Context mInstrContext;
private Context mAppContext;
private ComponentName mComponent;
private Thread mRunner;
private List mWaitingActivities;
private List mActivityMonitors;
private IInstrumentationWatcher mWatcher;
private IUiAutomationConnection mUiAutomationConnection;
private boolean mAutomaticPerformanceSnapshots = false;
private PerformanceCollector mPerformanceCollector;
private Bundle mPerfMetrics = new Bundle();
private UiAutomation mUiAutomation;
public Instrumentation() {
}
/**
* Called when the instrumentation is starting, before any application code
* has been loaded. Usually this will be implemented to simply call
* {@link #start} to begin the instrumentation thread, which will then
* continue execution in {@link #onStart}.
*
* If you do not need your own thread -- that is you are writing your
* instrumentation to be completely asynchronous (returning to the event
* loop so that the application can run), you can simply begin your
* instrumentation here, for example call {@link Context#startActivity} to
* begin the appropriate first activity of the application.
*
* @param arguments Any additional arguments that were supplied when the
* instrumentation was started.
*/
public void onCreate(Bundle arguments) {
}
/**
* Create and start a new thread in which to run instrumentation. This new
* thread will call to {@link #onStart} where you can implement the
* instrumentation.
*/
public void start() {
if (mRunner != null) {
throw new RuntimeException("Instrumentation already started");
}
mRunner = new InstrumentationThread("Instr: " + getClass().getName());
mRunner.start();
}
/**
* Method where the instrumentation thread enters execution. This allows
* you to run your instrumentation code in a separate thread than the
* application, so that it can perform blocking operation such as
* {@link #sendKeySync} or {@link #startActivitySync}.
*
*
You will typically want to call finish() when this function is done,
* to end your instrumentation.
*/
public void onStart() {
}
/**
* This is called whenever the system captures an unhandled exception that
* was thrown by the application. The default implementation simply
* returns false, allowing normal system handling of the exception to take
* place.
*
* @param obj The client object that generated the exception. May be an
* Application, Activity, BroadcastReceiver, Service, or null.
* @param e The exception that was thrown.
*
* @return To allow normal system exception process to occur, return false.
* If true is returned, the system will proceed as if the exception
* didn't happen.
*/
public boolean onException(Object obj, Throwable e) {
return false;
}
/**
* Provide a status report about the application.
*
* @param resultCode Current success/failure of instrumentation.
* @param results Any results to send back to the code that started the instrumentation.
*/
public void sendStatus(int resultCode, Bundle results) {
if (mWatcher != null) {
try {
mWatcher.instrumentationStatus(mComponent, resultCode, results);
}
catch (RemoteException e) {
mWatcher = null;
}
}
}
/**
* Terminate instrumentation of the application. This will cause the
* application process to exit, removing this instrumentation from the next
* time the application is started.
*
* @param resultCode Overall success/failure of instrumentation.
* @param results Any results to send back to the code that started the
* instrumentation.
*/
public void finish(int resultCode, Bundle results) {
if (mAutomaticPerformanceSnapshots) {
endPerformanceSnapshot();
}
if (mPerfMetrics != null) {
results.putAll(mPerfMetrics);
}
if (mUiAutomation != null) {
mUiAutomation.disconnect();
mUiAutomation = null;
}
mThread.finishInstrumentation(resultCode, results);
}
public void setAutomaticPerformanceSnapshots() {
mAutomaticPerformanceSnapshots = true;
mPerformanceCollector = new PerformanceCollector();
}
public void startPerformanceSnapshot() {
if (!isProfiling()) {
mPerformanceCollector.beginSnapshot(null);
}
}
public void endPerformanceSnapshot() {
if (!isProfiling()) {
mPerfMetrics = mPerformanceCollector.endSnapshot();
}
}
/**
* Called when the instrumented application is stopping, after all of the
* normal application cleanup has occurred.
*/
public void onDestroy() {
}
/**
* Return the Context of this instrumentation's package. Note that this is
* often different than the Context of the application being
* instrumentated, since the instrumentation code often lives is a
* different package than that of the application it is running against.
* See {@link #getTargetContext} to retrieve a Context for the target
* application.
*
* @return The instrumentation's package context.
*
* @see #getTargetContext
*/
public Context getContext() {
return mInstrContext;
}
/**
* Returns complete component name of this instrumentation.
*
* @return Returns the complete component name for this instrumentation.
*/
public ComponentName getComponentName() {
return mComponent;
}
/**
* Return a Context for the target application being instrumented. Note
* that this is often different than the Context of the instrumentation
* code, since the instrumentation code often lives is a different package
* than that of the application it is running against. See
* {@link #getContext} to retrieve a Context for the instrumentation code.
*
* @return A Context in the target application.
*
* @see #getContext
*/
public Context getTargetContext() {
return mAppContext;
}
/**
* Check whether this instrumentation was started with profiling enabled.
*
* @return Returns true if profiling was enabled when starting, else false.
*/
public boolean isProfiling() {
return mThread.isProfiling();
}
/**
* This method will start profiling if isProfiling() returns true. You should
* only call this method if you set the handleProfiling attribute in the
* manifest file for this Instrumentation to true.
*/
public void startProfiling() {
if (mThread.isProfiling()) {
File file = new File(mThread.getProfileFilePath());
file.getParentFile().mkdirs();
Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
}
}
/**
* Stops profiling if isProfiling() returns true.
*/
public void stopProfiling() {
if (mThread.isProfiling()) {
Debug.stopMethodTracing();
}
}
/**
* Force the global system in or out of touch mode. This can be used if
* your instrumentation relies on the UI being in one more or the other
* when it starts.
*
* @param inTouch Set to true to be in touch mode, false to be in
* focus mode.
*/
public void setInTouchMode(boolean inTouch) {
try {
IWindowManager.Stub.asInterface(
ServiceManager.getService("window")).setInTouchMode(inTouch);
} catch (RemoteException e) {
// Shouldn't happen!
}
}
/**
* Schedule a callback for when the application's main thread goes idle
* (has no more events to process).
*
* @param recipient Called the next time the thread's message queue is
* idle.
*/
public void waitForIdle(Runnable recipient) {
mMessageQueue.addIdleHandler(new Idler(recipient));
mThread.getHandler().post(new EmptyRunnable());
}
/**
* Synchronously wait for the application to be idle. Can not be called
* from the main application thread -- use {@link #start} to execute
* instrumentation in its own thread.
*/
public void waitForIdleSync() {
validateNotAppThread();
Idler idler = new Idler(null);
mMessageQueue.addIdleHandler(idler);
mThread.getHandler().post(new EmptyRunnable());
idler.waitForIdle();
}
/**
* Execute a call on the application's main thread, blocking until it is
* complete. Useful for doing things that are not thread-safe, such as
* looking at or modifying the view hierarchy.
*
* @param runner The code to run on the main thread.
*/
public void runOnMainSync(Runnable runner) {
validateNotAppThread();
SyncRunnable sr = new SyncRunnable(runner);
mThread.getHandler().post(sr);
sr.waitForComplete();
}
/**
* Start a new activity and wait for it to begin running before returning.
* In addition to being synchronous, this method as some semantic
* differences from the standard {@link Context#startActivity} call: the
* activity component is resolved before talking with the activity manager
* (its class name is specified in the Intent that this method ultimately
* starts), and it does not allow you to start activities that run in a
* different process. In addition, if the given Intent resolves to
* multiple activities, instead of displaying a dialog for the user to
* select an activity, an exception will be thrown.
*
*
The function returns as soon as the activity goes idle following the
* call to its {@link Activity#onCreate}. Generally this means it has gone
* through the full initialization including {@link Activity#onResume} and
* drawn and displayed its initial window.
*
* @param intent Description of the activity to start.
*
* @see Context#startActivity
*/
public Activity startActivitySync(Intent intent) {
validateNotAppThread();
synchronized (mSync) {
intent = new Intent(intent);
ActivityInfo ai = intent.resolveActivityInfo(
getTargetContext().getPackageManager(), 0);
if (ai == null) {
throw new RuntimeException("Unable to resolve activity for: " + intent);
}
String myProc = mThread.getProcessName();
if (!ai.processName.equals(myProc)) {
// todo: if this intent is ambiguous, look here to see if
// there is a single match that is in our package.
throw new RuntimeException("Intent in process "
+ myProc + " resolved to different process "
+ ai.processName + ": " + intent);
}
intent.setComponent(new ComponentName(
ai.applicationInfo.packageName, ai.name));
final ActivityWaiter aw = new ActivityWaiter(intent);
if (mWaitingActivities == null) {
mWaitingActivities = new ArrayList();
}
mWaitingActivities.add(aw);
getTargetContext().startActivity(intent);
do {
try {
mSync.wait();
} catch (InterruptedException e) {
}
} while (mWaitingActivities.contains(aw));
return aw.activity;
}
}
/**
* Information about a particular kind of Intent that is being monitored.
* An instance of this class is added to the
* current instrumentation through {@link #addMonitor}; after being added,
* when a new activity is being started the monitor will be checked and, if
* matching, its hit count updated and (optionally) the call stopped and a
* canned result returned.
*
*
An ActivityMonitor can also be used to look for the creation of an
* activity, through the {@link #waitForActivity} method. This will return
* after a matching activity has been created with that activity object.
*/
public static class ActivityMonitor {
private final IntentFilter mWhich;
private final String mClass;
private final ActivityResult mResult;
private final boolean mBlock;
// This is protected by 'Instrumentation.this.mSync'.
/*package*/ int mHits = 0;
// This is protected by 'this'.
/*package*/ Activity mLastActivity = null;
/**
* Create a new ActivityMonitor that looks for a particular kind of
* intent to be started.
*
* @param which The set of intents this monitor is responsible for.
* @param result A canned result to return if the monitor is hit; can
* be null.
* @param block Controls whether the monitor should block the activity
* start (returning its canned result) or let the call
* proceed.
*
* @see Instrumentation#addMonitor
*/
public ActivityMonitor(
IntentFilter which, ActivityResult result, boolean block) {
mWhich = which;
mClass = null;
mResult = result;
mBlock = block;
}
/**
* Create a new ActivityMonitor that looks for a specific activity
* class to be started.
*
* @param cls The activity class this monitor is responsible for.
* @param result A canned result to return if the monitor is hit; can
* be null.
* @param block Controls whether the monitor should block the activity
* start (returning its canned result) or let the call
* proceed.
*
* @see Instrumentation#addMonitor
*/
public ActivityMonitor(
String cls, ActivityResult result, boolean block) {
mWhich = null;
mClass = cls;
mResult = result;
mBlock = block;
}
/**
* Retrieve the filter associated with this ActivityMonitor.
*/
public final IntentFilter getFilter() {
return mWhich;
}
/**
* Retrieve the result associated with this ActivityMonitor, or null if
* none.
*/
public final ActivityResult getResult() {
return mResult;
}
/**
* Check whether this monitor blocks activity starts (not allowing the
* actual activity to run) or allows them to execute normally.
*/
public final boolean isBlocking() {
return mBlock;
}
/**
* Retrieve the number of times the monitor has been hit so far.
*/
public final int getHits() {
return mHits;
}
/**
* Retrieve the most recent activity class that was seen by this
* monitor.
*/
public final Activity getLastActivity() {
return mLastActivity;
}
/**
* Block until an Activity is created that matches this monitor,
* returning the resulting activity.
*
* @return Activity
*/
public final Activity waitForActivity() {
synchronized (this) {
while (mLastActivity == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
Activity res = mLastActivity;
mLastActivity = null;
return res;
}
}
/**
* Block until an Activity is created that matches this monitor,
* returning the resulting activity or till the timeOut period expires.
* If the timeOut expires before the activity is started, return null.
*
* @param timeOut Time to wait before the activity is created.
*
* @return Activity
*/
public final Activity waitForActivityWithTimeout(long timeOut) {
synchronized (this) {
if (mLastActivity == null) {
try {
wait(timeOut);
} catch (InterruptedException e) {
}
}
if (mLastActivity == null) {
return null;
} else {
Activity res = mLastActivity;
mLastActivity = null;
return res;
}
}
}
final boolean match(Context who,
Activity activity,
Intent intent) {
synchronized (this) {
if (mWhich != null
&& mWhich.match(who.getContentResolver(), intent,
true, "Instrumentation") < 0) {
return false;
}
if (mClass != null) {
String cls = null;
if (activity != null) {
cls = activity.getClass().getName();
} else if (intent.getComponent() != null) {
cls = intent.getComponent().getClassName();
}
if (cls == null || !mClass.equals(cls)) {
return false;
}
}
if (activity != null) {
mLastActivity = activity;
notifyAll();
}
return true;
}
}
}
/**
* Add a new {@link ActivityMonitor} that will be checked whenever an
* activity is started. The monitor is added
* after any existing ones; the monitor will be hit only if none of the
* existing monitors can themselves handle the Intent.
*
* @param monitor The new ActivityMonitor to see.
*
* @see #addMonitor(IntentFilter, ActivityResult, boolean)
* @see #checkMonitorHit
*/
public void addMonitor(ActivityMonitor monitor) {
synchronized (mSync) {
if (mActivityMonitors == null) {
mActivityMonitors = new ArrayList();
}
mActivityMonitors.add(monitor);
}
}
/**
* A convenience wrapper for {@link #addMonitor(ActivityMonitor)} that
* creates an intent filter matching {@link ActivityMonitor} for you and
* returns it.
*
* @param filter The set of intents this monitor is responsible for.
* @param result A canned result to return if the monitor is hit; can
* be null.
* @param block Controls whether the monitor should block the activity
* start (returning its canned result) or let the call
* proceed.
*
* @return The newly created and added activity monitor.
*
* @see #addMonitor(ActivityMonitor)
* @see #checkMonitorHit
*/
public ActivityMonitor addMonitor(
IntentFilter filter, ActivityResult result, boolean block) {
ActivityMonitor am = new ActivityMonitor(filter, result, block);
addMonitor(am);
return am;
}
/**
* A convenience wrapper for {@link #addMonitor(ActivityMonitor)} that
* creates a class matching {@link ActivityMonitor} for you and returns it.
*
* @param cls The activity class this monitor is responsible for.
* @param result A canned result to return if the monitor is hit; can
* be null.
* @param block Controls whether the monitor should block the activity
* start (returning its canned result) or let the call
* proceed.
*
* @return The newly created and added activity monitor.
*
* @see #addMonitor(ActivityMonitor)
* @see #checkMonitorHit
*/
public ActivityMonitor addMonitor(
String cls, ActivityResult result, boolean block) {
ActivityMonitor am = new ActivityMonitor(cls, result, block);
addMonitor(am);
return am;
}
/**
* Test whether an existing {@link ActivityMonitor} has been hit. If the
* monitor has been hit at least minHits times, then it will be
* removed from the activity monitor list and true returned. Otherwise it
* is left as-is and false is returned.
*
* @param monitor The ActivityMonitor to check.
* @param minHits The minimum number of hits required.
*
* @return True if the hit count has been reached, else false.
*
* @see #addMonitor
*/
public boolean checkMonitorHit(ActivityMonitor monitor, int minHits) {
waitForIdleSync();
synchronized (mSync) {
if (monitor.getHits() < minHits) {
return false;
}
mActivityMonitors.remove(monitor);
}
return true;
}
/**
* Wait for an existing {@link ActivityMonitor} to be hit. Once the
* monitor has been hit, it is removed from the activity monitor list and
* the first created Activity object that matched it is returned.
*
* @param monitor The ActivityMonitor to wait for.
*
* @return The Activity object that matched the monitor.
*/
public Activity waitForMonitor(ActivityMonitor monitor) {
Activity activity = monitor.waitForActivity();
synchronized (mSync) {
mActivityMonitors.remove(monitor);
}
return activity;
}
/**
* Wait for an existing {@link ActivityMonitor} to be hit till the timeout
* expires. Once the monitor has been hit, it is removed from the activity
* monitor list and the first created Activity object that matched it is
* returned. If the timeout expires, a null object is returned.
*
* @param monitor The ActivityMonitor to wait for.
* @param timeOut The timeout value in secs.
*
* @return The Activity object that matched the monitor.
*/
public Activity waitForMonitorWithTimeout(ActivityMonitor monitor, long timeOut) {
Activity activity = monitor.waitForActivityWithTimeout(timeOut);
synchronized (mSync) {
mActivityMonitors.remove(monitor);
}
return activity;
}
/**
* Remove an {@link ActivityMonitor} that was previously added with
* {@link #addMonitor}.
*
* @param monitor The monitor to remove.
*
* @see #addMonitor
*/
public void removeMonitor(ActivityMonitor monitor) {
synchronized (mSync) {
mActivityMonitors.remove(monitor);
}
}
/**
* Execute a particular menu item.
*
* @param targetActivity The activity in question.
* @param id The identifier associated with the menu item.
* @param flag Additional flags, if any.
* @return Whether the invocation was successful (for example, it could be
* false if item is disabled).
*/
public boolean invokeMenuActionSync(Activity targetActivity,
int id, int flag) {
class MenuRunnable implements Runnable {
private final Activity activity;
private final int identifier;
private final int flags;
boolean returnValue;
public MenuRunnable(Activity _activity, int _identifier,
int _flags) {
activity = _activity;
identifier = _identifier;
flags = _flags;
}
public void run() {
Window win = activity.getWindow();
returnValue = win.performPanelIdentifierAction(
Window.FEATURE_OPTIONS_PANEL,
identifier,
flags);
}
}
MenuRunnable mr = new MenuRunnable(targetActivity, id, flag);
runOnMainSync(mr);
return mr.returnValue;
}
/**
* Show the context menu for the currently focused view and executes a
* particular context menu item.
*
* @param targetActivity The activity in question.
* @param id The identifier associated with the context menu item.
* @param flag Additional flags, if any.
* @return Whether the invocation was successful (for example, it could be
* false if item is disabled).
*/
public boolean invokeContextMenuAction(Activity targetActivity, int id, int flag) {
validateNotAppThread();
// Bring up context menu for current focus.
// It'd be nice to do this through code, but currently ListView depends on
// long press to set metadata for its selected child
final KeyEvent downEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER);
sendKeySync(downEvent);
// Need to wait for long press
waitForIdleSync();
try {
Thread.sleep(ViewConfiguration.getLongPressTimeout());
} catch (InterruptedException e) {
Log.e(TAG, "Could not sleep for long press timeout", e);
return false;
}
final KeyEvent upEvent = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER);
sendKeySync(upEvent);
// Wait for context menu to appear
waitForIdleSync();
class ContextMenuRunnable implements Runnable {
private final Activity activity;
private final int identifier;
private final int flags;
boolean returnValue;
public ContextMenuRunnable(Activity _activity, int _identifier,
int _flags) {
activity = _activity;
identifier = _identifier;
flags = _flags;
}
public void run() {
Window win = activity.getWindow();
returnValue = win.performContextMenuIdentifierAction(
identifier,
flags);
}
}
ContextMenuRunnable cmr = new ContextMenuRunnable(targetActivity, id, flag);
runOnMainSync(cmr);
return cmr.returnValue;
}
/**
* Sends the key events corresponding to the text to the app being
* instrumented.
*
* @param text The text to be sent.
*/
public void sendStringSync(String text) {
if (text == null) {
return;
}
KeyCharacterMap keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
KeyEvent[] events = keyCharacterMap.getEvents(text.toCharArray());
if (events != null) {
for (int i = 0; i < events.length; i++) {
// We have to change the time of an event before injecting it because
// all KeyEvents returned by KeyCharacterMap.getEvents() have the same
// time stamp and the system rejects too old events. Hence, it is
// possible for an event to become stale before it is injected if it
// takes too long to inject the preceding ones.
sendKeySync(KeyEvent.changeTimeRepeat(events[i], SystemClock.uptimeMillis(), 0));
}
}
}
/**
* Send a key event to the currently focused window/view and wait for it to
* be processed. Finished at some point after the recipient has returned
* from its event processing, though it may not have completely
* finished reacting from the event -- for example, if it needs to update
* its display as a result, it may still be in the process of doing that.
*
* @param event The event to send to the current focus.
*/
public void sendKeySync(KeyEvent event) {
validateNotAppThread();
long downTime = event.getDownTime();
long eventTime = event.getEventTime();
int action = event.getAction();
int code = event.getKeyCode();
int repeatCount = event.getRepeatCount();
int metaState = event.getMetaState();
int deviceId = event.getDeviceId();
int scancode = event.getScanCode();
int source = event.getSource();
int flags = event.getFlags();
if (source == InputDevice.SOURCE_UNKNOWN) {
source = InputDevice.SOURCE_KEYBOARD;
}
if (eventTime == 0) {
eventTime = SystemClock.uptimeMillis();
}
if (downTime == 0) {
downTime = eventTime;
}
KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount, metaState,
deviceId, scancode, flags | KeyEvent.FLAG_FROM_SYSTEM, source);
InputManager.getInstance().injectInputEvent(newEvent,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
}
/**
* Sends an up and down key event sync to the currently focused window.
*
* @param key The integer keycode for the event.
*/
public void sendKeyDownUpSync(int key) {
sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, key));
sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, key));
}
/**
* Higher-level method for sending both the down and up key events for a
* particular character key code. Equivalent to creating both KeyEvent
* objects by hand and calling {@link #sendKeySync}. The event appears
* as if it came from keyboard 0, the built in one.
*
* @param keyCode The key code of the character to send.
*/
public void sendCharacterSync(int keyCode) {
sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
}
/**
* Dispatch a pointer event. Finished at some point after the recipient has
* returned from its event processing, though it may not have
* completely finished reacting from the event -- for example, if it needs
* to update its display as a result, it may still be in the process of
* doing that.
*
* @param event A motion event describing the pointer action. (As noted in
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
* {@link SystemClock#uptimeMillis()} as the timebase.
*/
public void sendPointerSync(MotionEvent event) {
validateNotAppThread();
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
}
InputManager.getInstance().injectInputEvent(event,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
}
/**
* Dispatch a trackball event. Finished at some point after the recipient has
* returned from its event processing, though it may not have
* completely finished reacting from the event -- for example, if it needs
* to update its display as a result, it may still be in the process of
* doing that.
*
* @param event A motion event describing the trackball action. (As noted in
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
* {@link SystemClock#uptimeMillis()} as the timebase.
*/
public void sendTrackballEventSync(MotionEvent event) {
validateNotAppThread();
if ((event.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) {
event.setSource(InputDevice.SOURCE_TRACKBALL);
}
InputManager.getInstance().injectInputEvent(event,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
}
/**
* Perform instantiation of the process's {@link Application} object. The
* default implementation provides the normal system behavior.
*
* @param cl The ClassLoader with which to instantiate the object.
* @param className The name of the class implementing the Application
* object.
* @param context The context to initialize the application with
*
* @return The newly instantiated Application object.
*/
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return newApplication(cl.loadClass(className), context);
}
/**
* Perform instantiation of the process's {@link Application} object. The
* default implementation provides the normal system behavior.
*
* @param clazz The class used to create an Application object from.
* @param context The context to initialize the application with
*
* @return The newly instantiated Application object.
*/
static public Application newApplication(Class> clazz, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Application app = (Application)clazz.newInstance();
app.attach(context);
return app;
}
/**
* Perform calling of the application's {@link Application#onCreate}
* method. The default implementation simply calls through to that method.
*
*
Note: This method will be called immediately after {@link #onCreate(Bundle)}.
* Often instrumentation tests start their test thread in onCreate(); you
* need to be careful of races between these. (Well between it and
* everything else, but let's start here.)
*
* @param app The application being created.
*/
public void callApplicationOnCreate(Application app) {
app.onCreate();
}
/**
* Perform instantiation of an {@link Activity} object. This method is intended for use with
* unit tests, such as android.test.ActivityUnitTestCase. The activity will be useable
* locally but will be missing some of the linkages necessary for use within the sytem.
*
* @param clazz The Class of the desired Activity
* @param context The base context for the activity to use
* @param token The token for this activity to communicate with
* @param application The application object (if any)
* @param intent The intent that started this Activity
* @param info ActivityInfo from the manifest
* @param title The title, typically retrieved from the ActivityInfo record
* @param parent The parent Activity (if any)
* @param id The embedded Id (if any)
* @param lastNonConfigurationInstance Arbitrary object that will be
* available via {@link Activity#getLastNonConfigurationInstance()
* Activity.getLastNonConfigurationInstance()}.
* @return Returns the instantiated activity
* @throws InstantiationException
* @throws IllegalAccessException
*/
public Activity newActivity(Class> clazz, Context context,
IBinder token, Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
Object lastNonConfigurationInstance) throws InstantiationException,
IllegalAccessException {
Activity activity = (Activity)clazz.newInstance();
ActivityThread aThread = null;
activity.attach(context, aThread, this, token, application, intent,
info, title, parent, id,
(Activity.NonConfigurationInstances)lastNonConfigurationInstance,
new Configuration());
return activity;
}
/**
* Perform instantiation of the process's {@link Activity} object. The
* default implementation provides the normal system behavior.
*
* @param cl The ClassLoader with which to instantiate the object.
* @param className The name of the class implementing the Activity
* object.
* @param intent The Intent object that specified the activity class being
* instantiated.
*
* @return The newly instantiated Activity object.
*/
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
/**
* Perform calling of an activity's {@link Activity#onCreate}
* method. The default implementation simply calls through to that method.
*
* @param activity The activity being created.
* @param icicle The previously frozen state (or null) to pass through to
* onCreate().
*/
public void callActivityOnCreate(Activity activity, Bundle icicle) {
if (mWaitingActivities != null) {
synchronized (mSync) {
final int N = mWaitingActivities.size();
for (int i=0; i list = results.getIntegerArrayList(key);
if (list != null) {
list.add(value);
}
} else {
ArrayList list = new ArrayList();
list.add(value);
results.putIntegerArrayList(key, list);
}
}
/**
* Returns a bundle with the current results from the allocation counting.
*/
public Bundle getAllocCounts() {
Bundle results = new Bundle();
results.putLong("global_alloc_count", Debug.getGlobalAllocCount());
results.putLong("global_alloc_size", Debug.getGlobalAllocSize());
results.putLong("global_freed_count", Debug.getGlobalFreedCount());
results.putLong("global_freed_size", Debug.getGlobalFreedSize());
results.putLong("gc_invocation_count", Debug.getGlobalGcInvocationCount());
return results;
}
/**
* Returns a bundle with the counts for various binder counts for this process. Currently the only two that are
* reported are the number of send and the number of received transactions.
*/
public Bundle getBinderCounts() {
Bundle results = new Bundle();
results.putLong("sent_transactions", Debug.getBinderSentTransactions());
results.putLong("received_transactions", Debug.getBinderReceivedTransactions());
return results;
}
/**
* Description of a Activity execution result to return to the original
* activity.
*/
public static final class ActivityResult {
/**
* Create a new activity result. See {@link Activity#setResult} for
* more information.
*
* @param resultCode The result code to propagate back to the
* originating activity, often RESULT_CANCELED or RESULT_OK
* @param resultData The data to propagate back to the originating
* activity.
*/
public ActivityResult(int resultCode, Intent resultData) {
mResultCode = resultCode;
mResultData = resultData;
}
/**
* Retrieve the result code contained in this result.
*/
public int getResultCode() {
return mResultCode;
}
/**
* Retrieve the data contained in this result.
*/
public Intent getResultData() {
return mResultData;
}
private final int mResultCode;
private final Intent mResultData;
}
/**
* Execute a startActivity call made by the application. The default
* implementation takes care of updating any active {@link ActivityMonitor}
* objects and dispatches this call to the system activity manager; you can
* override this to watch for the application to start an activity, and
* modify what happens when it does.
*
* This method returns an {@link ActivityResult} object, which you can
* use when intercepting application calls to avoid performing the start
* activity action but still return the result the application is
* expecting. To do this, override this method to catch the call to start
* activity so that it returns a new ActivityResult containing the results
* you would like the application to see, and don't call up to the super
* class. Note that an application is only expecting a result if
* requestCode is >= 0.
*
*
This method throws {@link android.content.ActivityNotFoundException}
* if there was no Activity found to run the given Intent.
*
* @param who The Context from which the activity is being started.
* @param contextThread The main thread of the Context from which the activity
* is being started.
* @param token Internal token identifying to the system who is starting
* the activity; may be null.
* @param target Which activity is performing the start (and thus receiving
* any result); may be null if this call is not being made
* from an activity.
* @param intent The actual Intent to start.
* @param requestCode Identifier for this request's result; less than zero
* if the caller is not expecting a result.
* @param options Addition options.
*
* @return To force the return of a particular result, return an
* ActivityResult object containing the desired data; otherwise
* return null. The default implementation always returns null.
*
* @throws android.content.ActivityNotFoundException
*
* @see Activity#startActivity(Intent)
* @see Activity#startActivityForResult(Intent, int)
* @see Activity#startActivityFromChild
*
* {@hide}
*/
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i= 0 ? am.getResult() : null;
}
break;
}
}
}
}
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess();
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
}
return null;
}
/**
* Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
* but accepts an array of activities to be started. Note that active
* {@link ActivityMonitor} objects only match against the first activity in
* the array.
*
* {@hide}
*/
public void execStartActivities(Context who, IBinder contextThread,
IBinder token, Activity target, Intent[] intents, Bundle options) {
execStartActivitiesAsUser(who, contextThread, token, target, intents, options,
UserHandle.myUserId());
}
/**
* Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
* but accepts an array of activities to be started. Note that active
* {@link ActivityMonitor} objects only match against the first activity in
* the array.
*
* {@hide}
*/
public void execStartActivitiesAsUser(Context who, IBinder contextThread,
IBinder token, Activity target, Intent[] intents, Bundle options,
int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i= 0 ? am.getResult() : null;
}
break;
}
}
}
}
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess();
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mWho : null,
requestCode, 0, null, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
}
return null;
}
/**
* Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
* but for starting as a particular user.
*
* @param who The Context from which the activity is being started.
* @param contextThread The main thread of the Context from which the activity
* is being started.
* @param token Internal token identifying to the system who is starting
* the activity; may be null.
* @param target Which fragment is performing the start (and thus receiving
* any result).
* @param intent The actual Intent to start.
* @param requestCode Identifier for this request's result; less than zero
* if the caller is not expecting a result.
*
* @return To force the return of a particular result, return an
* ActivityResult object containing the desired data; otherwise
* return null. The default implementation always returns null.
*
* @throws android.content.ActivityNotFoundException
*
* @see Activity#startActivity(Intent)
* @see Activity#startActivityForResult(Intent, int)
* @see Activity#startActivityFromChild
*
* {@hide}
*/
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options, UserHandle user) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i= 0 ? am.getResult() : null;
}
break;
}
}
}
}
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess();
int result = ActivityManagerNative.getDefault()
.startActivityAsUser(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, null, options, user.getIdentifier());
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
}
return null;
}
/*package*/ final void init(ActivityThread thread,
Context instrContext, Context appContext, ComponentName component,
IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection) {
mThread = thread;
mMessageQueue = mThread.getLooper().myQueue();
mInstrContext = instrContext;
mAppContext = appContext;
mComponent = component;
mWatcher = watcher;
mUiAutomationConnection = uiAutomationConnection;
}
/*package*/ static void checkStartActivityResult(int res, Object intent) {
if (res >= ActivityManager.START_SUCCESS) {
return;
}
switch (res) {
case ActivityManager.START_INTENT_NOT_RESOLVED:
case ActivityManager.START_CLASS_NOT_FOUND:
if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
throw new ActivityNotFoundException(
"Unable to find explicit activity class "
+ ((Intent)intent).getComponent().toShortString()
+ "; have you declared this activity in your AndroidManifest.xml?");
throw new ActivityNotFoundException(
"No Activity found to handle " + intent);
case ActivityManager.START_PERMISSION_DENIED:
throw new SecurityException("Not allowed to start activity "
+ intent);
case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
throw new AndroidRuntimeException(
"FORWARD_RESULT_FLAG used while also requesting a result");
case ActivityManager.START_NOT_ACTIVITY:
throw new IllegalArgumentException(
"PendingIntent is not an activity");
default:
throw new AndroidRuntimeException("Unknown error code "
+ res + " when starting " + intent);
}
}
private final void validateNotAppThread() {
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new RuntimeException(
"This method can not be called from the main application thread");
}
}
/**
* Gets the {@link UiAutomation} instance.
*
* Note: The APIs exposed via the returned {@link UiAutomation}
* work across application boundaries while the APIs exposed by the instrumentation
* do not. For example, {@link Instrumentation#sendPointerSync(MotionEvent)} will
* not allow you to inject the event in an app different from the instrumentation
* target, while {@link UiAutomation#injectInputEvent(android.view.InputEvent, boolean)}
* will work regardless of the current application.
*
*
* A typical test case should be using either the {@link UiAutomation} or
* {@link Instrumentation} APIs. Using both APIs at the same time is not
* a mistake by itself but a client has to be aware of the APIs limitations.
*
* @return The UI automation instance.
*
* @see UiAutomation
*/
public UiAutomation getUiAutomation() {
if (mUiAutomationConnection != null) {
if (mUiAutomation == null) {
mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(),
mUiAutomationConnection);
mUiAutomation.connect();
}
return mUiAutomation;
}
return null;
}
private final class InstrumentationThread extends Thread {
public InstrumentationThread(String name) {
super(name);
}
public void run() {
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
} catch (RuntimeException e) {
Log.w(TAG, "Exception setting priority of instrumentation thread "
+ Process.myTid(), e);
}
if (mAutomaticPerformanceSnapshots) {
startPerformanceSnapshot();
}
onStart();
}
}
private static final class EmptyRunnable implements Runnable {
public void run() {
}
}
private static final class SyncRunnable implements Runnable {
private final Runnable mTarget;
private boolean mComplete;
public SyncRunnable(Runnable target) {
mTarget = target;
}
public void run() {
mTarget.run();
synchronized (this) {
mComplete = true;
notifyAll();
}
}
public void waitForComplete() {
synchronized (this) {
while (!mComplete) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
}
}
private static final class ActivityWaiter {
public final Intent intent;
public Activity activity;
public ActivityWaiter(Intent _intent) {
intent = _intent;
}
}
private final class ActivityGoing implements MessageQueue.IdleHandler {
private final ActivityWaiter mWaiter;
public ActivityGoing(ActivityWaiter waiter) {
mWaiter = waiter;
}
public final boolean queueIdle() {
synchronized (mSync) {
mWaitingActivities.remove(mWaiter);
mSync.notifyAll();
}
return false;
}
}
private static final class Idler implements MessageQueue.IdleHandler {
private final Runnable mCallback;
private boolean mIdle;
public Idler(Runnable callback) {
mCallback = callback;
mIdle = false;
}
public final boolean queueIdle() {
if (mCallback != null) {
mCallback.run();
}
synchronized (this) {
mIdle = true;
notifyAll();
}
return false;
}
public void waitForIdle() {
synchronized (this) {
while (!mIdle) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
}
}
}