Merge "Merge GoogleInstrumentationTestRunner features into android.support.test."
diff --git a/.gitignore b/.gitignore
index aaf6b34..a341061 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,4 @@
 *.cproject
 *.settings
 *.pyc
-
+.DS_Store
diff --git a/support/src/android/support/test/internal/runner/InstrumentationArgumentsRegistry.java b/support/src/android/support/test/internal/runner/InstrumentationArgumentsRegistry.java
new file mode 100644
index 0000000..b8bf8fb
--- /dev/null
+++ b/support/src/android/support/test/internal/runner/InstrumentationArgumentsRegistry.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2014 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.support.test.internal.runner;
+
+import android.os.Bundle;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * An exposed registry instance to make it easy for callers to find the instrumentation arguments.
+ */
+public final class InstrumentationArgumentsRegistry {
+
+    private static final AtomicReference<Bundle> sArguments = new AtomicReference<Bundle>(null);
+
+    /**
+     * Returns a copy of instrumentation arguments Bundle.
+     * <p>
+     * This Bundle is not guaranteed to be present under all instrumentations.
+     * </p>
+     *
+     * @return Bundle the arguments for this instrumentation.
+     * @throws IllegalStateException if no argument Bundle has been registered.
+     */
+    public static Bundle getInstance() {
+        Bundle instance = sArguments.get();
+        if (null == instance) {
+            throw new IllegalStateException("No instrumentation arguments registered! "
+                    + "Are you running under an Instrumentation which registers arguments?");
+        }
+        return new Bundle(instance);
+    }
+
+    /**
+     * Stores a copy of the instrumentation arguments Bundle in the registry.
+     * <p>
+     * This is a global registry - so be aware of the impact of calling this method!
+     * </p>
+     * 
+     * @param arguments the arguments for this application. Null deregisters any existing arguments.
+     */
+    public static void registerInstance(Bundle arguments) {
+        InstrumentationArgumentsRegistry.sArguments.set(new Bundle(arguments));
+    }
+
+    private InstrumentationArgumentsRegistry() { }
+}
diff --git a/support/src/android/support/test/internal/runner/InstrumentationRegistry.java b/support/src/android/support/test/internal/runner/InstrumentationRegistry.java
new file mode 100644
index 0000000..e41338e
--- /dev/null
+++ b/support/src/android/support/test/internal/runner/InstrumentationRegistry.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 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.support.test.internal.runner;
+
+import static android.support.test.internal.util.Checks.checkNotNull;
+
+import android.app.Instrumentation;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Holds a reference to the instrumentation running in the process.
+ */
+public final class InstrumentationRegistry {
+
+    private static final AtomicReference<Instrumentation> sInstrumentationRef =
+            new AtomicReference<Instrumentation>(null);
+
+    /**
+     * Returns the instrumentation currently running.
+     *
+     * @throws IllegalStateException if instrumentation hasn't been registered
+     */
+    public static Instrumentation getInstance() {
+        return checkNotNull(sInstrumentationRef.get(), "No instrumentation registered. " +
+                "Must run under a registering instrumentation.");
+    }
+
+    /**
+     * Records/exposes the instrumentation currently running.
+     * <p>
+     * This is a global registry - so be aware of the impact of calling this method!
+     * </p>
+     */
+    public static void registerInstance(Instrumentation instrumentation) {
+        sInstrumentationRef.set(instrumentation);
+    }
+
+    private InstrumentationRegistry() { }
+}
diff --git a/support/src/android/support/test/internal/runner/lifecycle/ActivityLifecycleMonitorImpl.java b/support/src/android/support/test/internal/runner/lifecycle/ActivityLifecycleMonitorImpl.java
new file mode 100644
index 0000000..7a49d7f
--- /dev/null
+++ b/support/src/android/support/test/internal/runner/lifecycle/ActivityLifecycleMonitorImpl.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2014 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.support.test.internal.runner.lifecycle;
+
+import static android.support.test.internal.util.Checks.checkNotNull;
+
+import android.app.Activity;
+import android.os.Looper;
+import android.support.test.runner.AndroidJUnitRunner;
+import android.support.test.runner.lifecycle.ActivityLifecycleCallback;
+import android.support.test.runner.lifecycle.ActivityLifecycleMonitor;
+import android.support.test.runner.lifecycle.Stage;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * The lifecycle monitor used by {@link AndroidJUnitRunner}.
+ */
+public final class ActivityLifecycleMonitorImpl implements ActivityLifecycleMonitor {
+    private static final String TAG = "LifecycleMonitor";
+    private final boolean mDeclawThreadCheck;
+
+    public ActivityLifecycleMonitorImpl() {
+        this(false);
+    }
+
+    // For Testing
+    public ActivityLifecycleMonitorImpl(boolean declawThreadCheck) {
+        this.mDeclawThreadCheck = declawThreadCheck;
+    }
+
+    // Accessed from any thread.
+    private List<WeakReference<ActivityLifecycleCallback>> mCallbacks =
+            new ArrayList<WeakReference<ActivityLifecycleCallback>>();
+
+    // Only accessed on main thread.
+    private List<ActivityStatus> mActivityStatuses = new ArrayList<ActivityStatus>();
+
+    @Override
+    public void addLifecycleCallback(ActivityLifecycleCallback callback) {
+        // there will never be too many callbacks, so iterating over a list will probably
+        // be faster then the constant time costs of setting up and maintaining a map.
+        checkNotNull(callback);
+
+        synchronized (mCallbacks) {
+            boolean needsAdd = true;
+            Iterator<WeakReference<ActivityLifecycleCallback>> refIter = mCallbacks.iterator();
+            while (refIter.hasNext()) {
+                ActivityLifecycleCallback storedCallback = refIter.next().get();
+                if (null == storedCallback) {
+                    refIter.remove();
+                } else if (storedCallback == callback) {
+                    needsAdd = false;
+                }
+            }
+            if (needsAdd) {
+                mCallbacks.add(new WeakReference<ActivityLifecycleCallback>(callback));
+            }
+        }
+    }
+
+    @Override
+    public void removeLifecycleCallback(ActivityLifecycleCallback callback) {
+        checkNotNull(callback);
+
+        synchronized (mCallbacks) {
+            Iterator<WeakReference<ActivityLifecycleCallback>> refIter = mCallbacks.iterator();
+            while (refIter.hasNext()) {
+                ActivityLifecycleCallback storedCallback = refIter.next().get();
+                if (null == storedCallback) {
+                    refIter.remove();
+                } else if (storedCallback == callback) {
+                    refIter.remove();
+                }
+            }
+        }
+    }
+
+    @Override
+    public Stage getLifecycleStageOf(Activity activity) {
+        checkMainThread();
+        checkNotNull(activity);
+        Iterator<ActivityStatus> statusIterator = mActivityStatuses.iterator();
+        while (statusIterator.hasNext()) {
+            ActivityStatus status = statusIterator.next();
+            Activity statusActivity = status.mActivityRef.get();
+            if (null == statusActivity) {
+                statusIterator.remove();
+            } else if (activity == statusActivity) {
+                return status.mLifecycleStage;
+            }
+        }
+        throw new IllegalArgumentException("Unknown activity: " + activity);
+    }
+
+    @Override
+    public Collection<Activity> getActivitiesInStage(Stage stage) {
+        checkMainThread();
+        checkNotNull(stage);
+
+        List<Activity> activities = new ArrayList<Activity>();
+        Iterator<ActivityStatus> statusIterator = mActivityStatuses.iterator();
+        while (statusIterator.hasNext()) {
+            ActivityStatus status = statusIterator.next();
+            Activity statusActivity = status.mActivityRef.get();
+            if (null == statusActivity) {
+                statusIterator.remove();
+            } else if (stage == status.mLifecycleStage) {
+                activities.add(statusActivity);
+            }
+        }
+
+        return activities;
+    }
+
+    /**
+     * Called by the runner after a particular onXXX lifecycle method has been called on a given
+     * activity.
+     */
+    public void signalLifecycleChange(Stage stage, Activity activity) {
+        // there are never too many activities in existence in an application - so we keep
+        // track of everything in a single list.
+        Log.d(TAG, "Lifecycle status change: " + activity + " in: " + stage);
+
+        boolean needsAdd = true;
+        Iterator<ActivityStatus> statusIterator = mActivityStatuses.iterator();
+        while (statusIterator.hasNext()) {
+            ActivityStatus status = statusIterator.next();
+            Activity statusActivity = status.mActivityRef.get();
+            if (null == statusActivity) {
+                statusIterator.remove();
+            } else if (activity == statusActivity) {
+                needsAdd = false;
+                status.mLifecycleStage = stage;
+            }
+        }
+
+        if (needsAdd) {
+            mActivityStatuses.add(new ActivityStatus(activity, stage));
+        }
+
+        synchronized (mCallbacks) {
+            Iterator<WeakReference<ActivityLifecycleCallback>> refIter = mCallbacks.iterator();
+            while (refIter.hasNext()) {
+                ActivityLifecycleCallback callback = refIter.next().get();
+                if (null == callback) {
+                    refIter.remove();
+                } else {
+                    try {
+                        Log.d(TAG, "running callback: " + callback);
+                        callback.onActivityLifecycleChanged(activity, stage);
+                        Log.d(TAG, "callback completes: " + callback);
+                    } catch (RuntimeException re) {
+                        Log.e(TAG, String.format(
+                                "Callback threw exception! (callback: %s activity: %s stage: %s)",
+                                callback,
+                                activity,
+                                stage),
+                                re);
+                    }
+                }
+            }
+        }
+    }
+
+    private void checkMainThread() {
+        if (mDeclawThreadCheck) {
+            return;
+        }
+
+        if (!Thread.currentThread().equals(Looper.getMainLooper().getThread())) {
+            throw new IllegalStateException(
+                    "Querying activity state off main thread is not allowed.");
+        }
+    }
+
+    private static class ActivityStatus {
+        private final WeakReference<Activity> mActivityRef;
+        private Stage mLifecycleStage;
+
+        ActivityStatus(Activity activity, Stage stage) {
+            this.mActivityRef = new WeakReference<Activity>(checkNotNull(activity));
+            this.mLifecycleStage = checkNotNull(stage);
+        }
+    }
+}
diff --git a/support/src/android/support/test/internal/runner/lifecycle/ActivityLifecycleMonitorRegistry.java b/support/src/android/support/test/internal/runner/lifecycle/ActivityLifecycleMonitorRegistry.java
new file mode 100644
index 0000000..b618354
--- /dev/null
+++ b/support/src/android/support/test/internal/runner/lifecycle/ActivityLifecycleMonitorRegistry.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2014 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.support.test.internal.runner.lifecycle;
+
+import android.support.test.runner.lifecycle.ActivityLifecycleMonitor;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * An exposed registry instance to make it easy for callers to find the lifecycle monitor for their
+ * application.
+ */
+public final class ActivityLifecycleMonitorRegistry {
+
+    private static final AtomicReference<ActivityLifecycleMonitor> sLifecycleMonitor =
+            new AtomicReference<ActivityLifecycleMonitor>(null);
+
+    // singleton - disallow creation
+    private ActivityLifecycleMonitorRegistry() { }
+
+    /**
+     * Returns the ActivityLifecycleMonitor.
+     *
+     * This monitor is not guaranteed to be present under all instrumentations.
+     *
+     * @return ActivityLifecycleMonitor the monitor for this application.
+     * @throws IllegalStateException if no monitor has been registered.
+     */
+    public static ActivityLifecycleMonitor getInstance() {
+        ActivityLifecycleMonitor instance = sLifecycleMonitor.get();
+        if (null == instance) {
+            throw new IllegalStateException("No lifecycle monitor registered! Are you running "
+                    + "under an Instrumentation which registers lifecycle monitors?");
+        }
+        return instance;
+    }
+
+    /**
+     * Stores a lifecycle monitor in the registry.
+     * <p>
+     * This is a global registry - so be aware of the impact of calling this method!
+     * </p>
+     *
+     * @param monitor the monitor for this application. Null deregisters any existing monitor.
+     */
+    public static void registerInstance(ActivityLifecycleMonitor monitor) {
+        sLifecycleMonitor.set(monitor);
+    }
+}
diff --git a/support/src/android/support/test/internal/runner/listener/ActivityFinisherRunListener.java b/support/src/android/support/test/internal/runner/listener/ActivityFinisherRunListener.java
new file mode 100644
index 0000000..ae5cc07
--- /dev/null
+++ b/support/src/android/support/test/internal/runner/listener/ActivityFinisherRunListener.java
@@ -0,0 +1,64 @@
+package android.support.test.internal.runner.listener;
+
+import static android.support.test.internal.util.Checks.checkNotNull;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.support.test.runner.lifecycle.ActivityLifecycleMonitor;
+import android.support.test.runner.lifecycle.Stage;
+import android.util.Log;
+
+import org.junit.runner.Description;
+import org.junit.runner.notification.RunListener;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+
+/**
+ * Ensures that no activities are running when a test method starts and that no activities are still
+ * running when it ends.
+ */
+public class ActivityFinisherRunListener extends RunListener {
+    private static final String TAG = "ActivityFinisher";
+    private final Instrumentation instrumentation;
+    private final ActivityLifecycleMonitor activityLifecycleMonitor;
+    public ActivityFinisherRunListener(Instrumentation instrumentation, ActivityLifecycleMonitor activityLifecycleMonitor) {
+        this.instrumentation = checkNotNull(instrumentation);
+        this.activityLifecycleMonitor = checkNotNull(activityLifecycleMonitor);
+    }
+
+    @Override
+    public void testStarted(Description description) throws Exception {
+        instrumentation.runOnMainSync(makeActivityFinisher());
+    }
+
+    @Override
+    public void testFinished(Description description) throws Exception {
+        instrumentation.runOnMainSync(makeActivityFinisher());
+    }
+
+    private Runnable makeActivityFinisher() {
+        return new Runnable() {
+            @Override
+            public void run() {
+
+                List<Activity> activities = new ArrayList<Activity>();
+                for (Stage s : EnumSet.range(Stage.CREATED, Stage.PAUSED)) {
+                    activities.addAll(activityLifecycleMonitor.getActivitiesInStage(s));
+                }
+                for (Activity activity : activities) {
+                    if (!activity.isFinishing()) {
+                        Log.i(TAG, "Finishing: " + activity);
+                        try {
+                            activity.finish();
+                        } catch (RuntimeException re) {
+                            Log.e(TAG, "Failed to finish: " + activity, re);
+                        }
+                    }
+                }
+            }
+
+        };
+    }
+}
\ No newline at end of file
diff --git a/support/src/android/support/test/internal/util/Checks.java b/support/src/android/support/test/internal/util/Checks.java
new file mode 100644
index 0000000..82fd2f1
--- /dev/null
+++ b/support/src/android/support/test/internal/util/Checks.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2014 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.support.test.internal.util;
+
+/**
+ * Utility method for checking for null references
+ */
+public final class Checks {
+
+    private Checks() {
+    }
+
+    public static <T> T checkNotNull(T reference) {
+        if (reference == null) {
+            throw new NullPointerException();
+        }
+        return reference;
+    }
+
+    public static <T> T checkNotNull(T reference, Object errorMessage) {
+        if (reference == null) {
+            throw new NullPointerException(String.valueOf(errorMessage));
+        }
+        return reference;
+    }
+
+    public static <T> T checkNotNull(T reference,
+            String errorMessageTemplate,
+            Object... errorMessageArgs) {
+        if (reference == null) {
+            // If either of these parameters is null, the right thing happens
+            // anyway
+            throw new NullPointerException(
+                    format(errorMessageTemplate, errorMessageArgs));
+        }
+        return reference;
+    }
+
+    private static String format(String template, Object... args) {
+        template = String.valueOf(template); // null -> "null"
+
+        // start substituting the arguments into the '%s' placeholders
+        StringBuilder builder = new StringBuilder(
+                template.length() + 16 * args.length);
+        int templateStart = 0;
+        int i = 0;
+        while (i < args.length) {
+            int placeholderStart = template.indexOf("%s", templateStart);
+            if (placeholderStart == -1) {
+                break;
+            }
+            builder.append(template.substring(templateStart, placeholderStart));
+            builder.append(args[i++]);
+            templateStart = placeholderStart + 2;
+        }
+        builder.append(template.substring(templateStart));
+
+        // if we run out of placeholders, append the extra args in square braces
+        if (i < args.length) {
+            builder.append(" [");
+            builder.append(args[i++]);
+            while (i < args.length) {
+                builder.append(", ");
+                builder.append(args[i++]);
+            }
+            builder.append(']');
+        }
+        return builder.toString();
+    }
+}
diff --git a/support/src/android/support/test/runner/AndroidJUnitRunner.java b/support/src/android/support/test/runner/AndroidJUnitRunner.java
index 6abf44a..42cdc73 100644
--- a/support/src/android/support/test/runner/AndroidJUnitRunner.java
+++ b/support/src/android/support/test/runner/AndroidJUnitRunner.java
@@ -17,12 +17,18 @@
 package android.support.test.runner;
 
 import android.app.Activity;
+import android.app.Application;
 import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Bundle;
 import android.os.Debug;
+import android.os.IBinder;
 import android.os.Looper;
 import android.support.test.internal.runner.TestRequest;
 import android.support.test.internal.runner.TestRequestBuilder;
@@ -145,7 +151,7 @@
  *    meta-data android:name="listener"
  *              android:value="com.foo.Listener,com.foo.Listener2"
  */
-public class AndroidJUnitRunner extends Instrumentation {
+public class AndroidJUnitRunner extends MonitoringInstrumentation {
 
     // constants for supported instrumentation arguments
     public static final String ARGUMENT_TEST_CLASS = "class";
@@ -172,6 +178,7 @@
     public void onCreate(Bundle arguments) {
         super.onCreate(arguments);
         mArguments = arguments;
+        specifyDexMakerCacheProperty();
 
         start();
     }
@@ -211,8 +218,7 @@
 
     @Override
     public void onStart() {
-        // Wait for target context to finish.
-        waitForIdleSync();
+        super.onStart();
 
         prepareLooper();
 
@@ -220,7 +226,7 @@
             Debug.waitForDebugger();
         }
 
-        setupDexmaker();
+        setupDexmakerClassloader();
 
         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
         PrintStream writer = new PrintStream(byteArrayOutputStream);
@@ -483,12 +489,7 @@
         }
     }
 
-    private void setupDexmaker() {
-        // Explicitly set the Dexmaker cache, so tests that use mocking frameworks work
-        String dexCache = getTargetContext().getCacheDir().getPath();
-        Log.i(LOG_TAG, "Setting dexmaker.dexcache to " + dexCache);
-        System.setProperty("dexmaker.dexcache", getTargetContext().getCacheDir().getPath());
-
+    private void setupDexmakerClassloader() {
         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
         // must set the context classloader for apps that use a shared uid, see
         // frameworks/base/core/java/android/app/LoadedApk.java
@@ -497,4 +498,41 @@
                 newClassLoader.toString(), originalClassLoader.toString()));
         Thread.currentThread().setContextClassLoader(newClassLoader);
     }
+
+    // ActivityUnitTestCase defaults to building the ComponentName via
+    // Activity.getClass().getPackage().getName(). This will cause a problem if the Java Package of
+    // the Activity is not the Android Package of the application, specifically
+    // Activity.getPackageName() will return an incorrect value.
+    // @see b/14561718
+    @Override
+    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 {
+        String activityClassPackageName = clazz.getPackage().getName();
+        String contextPackageName = context.getPackageName();
+        ComponentName intentComponentName = intent.getComponent();
+        if (!contextPackageName.equals(intentComponentName.getPackageName())) {
+            if (activityClassPackageName.equals(intentComponentName.getPackageName())) {
+                intent.setComponent(
+                        new ComponentName(contextPackageName, intentComponentName.getClassName()));
+            }
+        }
+        return super.newActivity(clazz,
+                context,
+                token,
+                application,
+                intent,
+                info,
+                title,
+                parent,
+                id,
+                lastNonConfigurationInstance);
+    }
 }
diff --git a/support/src/android/support/test/runner/MonitoringInstrumentation.java b/support/src/android/support/test/runner/MonitoringInstrumentation.java
new file mode 100644
index 0000000..f6ed97a
--- /dev/null
+++ b/support/src/android/support/test/runner/MonitoringInstrumentation.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2014 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.support.test.runner;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.MessageQueue.IdleHandler;
+import android.support.test.internal.runner.InstrumentationArgumentsRegistry;
+import android.support.test.internal.runner.InstrumentationRegistry;
+import android.support.test.internal.runner.lifecycle.ActivityLifecycleMonitorImpl;
+import android.support.test.internal.runner.lifecycle.ActivityLifecycleMonitorRegistry;
+import android.support.test.runner.lifecycle.Stage;
+import android.util.Log;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * An instrumentation that enables several advanced features and makes some hard guarantees about
+ * the state of the application under instrumentation.
+ * <p/>
+ * A short list of these capabilities:
+ * <ul>
+ * <li>Forces Application.onCreate() to happen before Instrumentation.onStart() runs (ensuring your
+ * code always runs in a sane state).</li>
+ * <li>Logs application death due to exceptions.</li>
+ * <li>Allows tracking of activity lifecycle states.</li>
+ * <li>Registers instrumentation arguments in an easy to access place.</li>
+ * <li>Ensures your activities are creating themselves in reasonable amounts of time.</li>
+ * <li>Provides facilities to dump current app threads to test outputs.</li>
+ * <li>Ensures all activities finish before instrumentation exits.</li>
+ * </ul>
+ *
+ * This Instrumentation is *NOT* a test instrumentation (some of its subclasses are). It makes no
+ * assumptions about what the subclass wants to do.
+ */
+public class MonitoringInstrumentation extends Instrumentation {
+
+    private static final long MILLIS_TO_WAIT_FOR_ACTIVITY_TO_STOP = TimeUnit.SECONDS.toMillis(2);
+    private static final long MILLIS_TO_POLL_FOR_ACTIVITY_STOP =
+            MILLIS_TO_WAIT_FOR_ACTIVITY_TO_STOP / 40;
+
+    private static final String LOG_TAG = "MonitoringInstrumentation";
+
+    private static final int START_ACTIVITY_TIMEOUT_SECONDS = 45;
+    private ActivityLifecycleMonitorImpl mLifecycleMonitor = new ActivityLifecycleMonitorImpl();
+    private ExecutorService mExecutorService;
+    private Handler mHandlerForMainLooper;
+    private AtomicBoolean mAnActivityHasBeenLaunched = new AtomicBoolean(false);
+    private Thread mMainThread;
+    private AtomicLong mLastIdleTime = new AtomicLong(0);
+    private AtomicInteger mStartedActivityCounter = new AtomicInteger(0);
+
+    private IdleHandler mIdleHandler = new IdleHandler() {
+        @Override
+        public boolean queueIdle() {
+            mLastIdleTime.set(System.currentTimeMillis());
+            return true;
+        }
+    };
+
+    private volatile boolean mFinished = false;
+
+    /**
+     * Sets up lifecycle monitoring, and argument registry.
+     * <p>
+     * Subclasses must call up to onCreate(). This onCreate method does not call start()
+     * it is the subclasses responsibility to call start if it desires.
+     * </p>
+     */
+    @Override
+    public void onCreate(Bundle arguments) {
+        Log.i(LOG_TAG, "Instrumentation Started!");
+        logUncaughtExceptions();
+
+        InstrumentationRegistry.registerInstance(this);
+        ActivityLifecycleMonitorRegistry.registerInstance(mLifecycleMonitor);
+
+        InstrumentationArgumentsRegistry.registerInstance(arguments);
+
+        mHandlerForMainLooper = new Handler(Looper.getMainLooper());
+        mMainThread = Thread.currentThread();
+        mExecutorService = Executors.newCachedThreadPool();
+        Looper.myQueue().addIdleHandler(mIdleHandler);
+        super.onCreate(arguments);
+    }
+
+    protected final void specifyDexMakerCacheProperty() {
+        // DexMaker uses heuristics to figure out where to store its temporary dex files
+        // these heuristics may break (eg - they no longer work on JB MR2). So we create
+        // our own cache dir to be used if the app doesnt specify a cache dir, rather then
+        // relying on heuristics.
+        //
+        File dexCache = getTargetContext().getDir("dxmaker_cache", Context.MODE_PRIVATE);
+        System.getProperties().put("dexmaker.dexcache", dexCache.getAbsolutePath());
+    }
+
+    private void logUncaughtExceptions() {
+        final Thread.UncaughtExceptionHandler standardHandler =
+                Thread.currentThread().getUncaughtExceptionHandler();
+        Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+            @Override
+            public void uncaughtException(Thread t, Throwable e) {
+                onException(t, e);
+                if (null != standardHandler) {
+                    standardHandler.uncaughtException(t, e);
+                }
+            }
+        });
+    }
+
+    /**
+     * This implementation of onStart() will guarantee that the Application's onCreate method
+     * has completed when it returns.
+     * <p>
+     * Subclasses should call super.onStart() before executing any code that touches the application
+     * and it's state.
+     * </p>
+     */
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        // Due to the way Android initializes instrumentation - all instrumentations have the
+        // possibility of seeing the Application and its classes in an inconsistent state.
+        // Specifically ActivityThread creates Instrumentation first, initializes it, and calls
+        // instrumentation.onCreate(). After it does that, it calls
+        // instrumentation.callApplicationOnCreate() which ends up calling the application's
+        // onCreateMethod.
+        //
+        // So, Android's InstrumentationTestRunner's onCreate method() spawns a separate thread to
+        // execute tests. This causes tests to start accessing the application and its classes while
+        // the ActivityThread is calling callApplicationOnCreate() in its own thread.
+        //
+        // This makes it possible for tests to see the application in a state that is normally never
+        // visible: pre-application.onCreate() and during application.onCreate()).
+        //
+        // *phew* that sucks! Here we waitForOnIdleSync() to ensure onCreate has completed before we
+        // start executing tests.
+        waitForIdleSync();
+    }
+
+    /**
+     * Ensures all activities launched in this instrumentation are finished before the
+     * instrumentation exits.
+     * <p>
+     * Subclasses who override this method should do their finish processing and then call
+     * super.finish to invoke this logic. Not waiting for all activities to finish() before exiting
+     * can cause device wide instability.
+     * </p>
+     */
+    @Override
+    public void finish(int resultCode, Bundle results) {
+        if (mFinished) {
+            Log.w(LOG_TAG, "finish called 2x!");
+            return;
+        } else {
+            mFinished = true;
+        }
+
+        mHandlerForMainLooper.post(new ActivityFinisher());
+
+        long startTime = System.currentTimeMillis();
+        waitForActivitiesToComplete();
+        long endTime = System.currentTimeMillis();
+        Log.i(LOG_TAG, String.format("waitForActivitiesToComplete() took: %sms", endTime - startTime));
+        ActivityLifecycleMonitorRegistry.registerInstance(null);
+        super.finish(resultCode, results);
+    }
+
+    /**
+     * Ensures we've onStopped() all activities which were onStarted().
+     * <p>
+     * According to Activity's contract, the process is not killable between onStart and onStop.
+     * Breaking this contract (which finish() will if you let it) can cause bad behaviour (including
+     * a full restart of system_server).
+     * </p>
+     * <p>
+     * We give the app 2 seconds to stop all its activities, then we proceed.
+     * </p>
+     */
+    protected void waitForActivitiesToComplete() {
+        long endTime = System.currentTimeMillis() + MILLIS_TO_WAIT_FOR_ACTIVITY_TO_STOP;
+        int currentActivityCount = mStartedActivityCounter.get();
+
+        while (currentActivityCount > 0 && System.currentTimeMillis() < endTime) {
+            try {
+                Log.i(LOG_TAG, "Unstopped activity count: " + currentActivityCount);
+                Thread.sleep(MILLIS_TO_POLL_FOR_ACTIVITY_STOP);
+                currentActivityCount = mStartedActivityCounter.get();
+            } catch (InterruptedException ie) {
+                Log.i(LOG_TAG, "Abandoning activity wait due to interruption.", ie);
+                break;
+            }
+        }
+
+        if (currentActivityCount > 0) {
+            dumpThreadStateToOutputs("ThreadState-unstopped.txt");
+            Log.w(LOG_TAG, String.format("Still %s activities active after waiting %s ms.",
+                    currentActivityCount, MILLIS_TO_WAIT_FOR_ACTIVITY_TO_STOP));
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.i(LOG_TAG, "Instrumentation Finished!");
+        Looper.myQueue().removeIdleHandler(mIdleHandler);
+        super.onDestroy();
+    }
+
+    @Override
+    public Activity startActivitySync(final Intent intent) {
+        validateNotAppThread();
+        long lastIdleTimeBeforeLaunch = mLastIdleTime.get();
+
+        if (mAnActivityHasBeenLaunched.compareAndSet(false, true)) {
+            // All activities launched from InstrumentationTestCase.launchActivityWithIntent get
+            // started with FLAG_ACTIVITY_NEW_TASK. This includes calls to
+            // ActivityInstrumentationTestcase2.getActivity().
+            //
+            // This gives us a pristine environment - MOST OF THE TIME.
+            //
+            // However IF we've run a test method previously and that has launched an activity
+            // outside of our process our old task is still lingering around. By launching a new
+            // activity android will place our activity at the bottom of the stack and bring the
+            // previous external activity to the front of the screen.
+            //
+            // To wipe out the old task and execute within a pristine environment for each test
+            // we tell android to CLEAR_TOP the very first activity we see, no matter what.
+            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        }
+        Future<Activity> startedActivity = mExecutorService.submit(new Callable<Activity>() {
+            @Override
+            public Activity call() {
+                return MonitoringInstrumentation.super.startActivitySync(intent);
+            }
+        });
+
+        try {
+            return startedActivity.get(START_ACTIVITY_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        } catch (TimeoutException te) {
+            startedActivity.cancel(true);
+            dumpThreadStateToOutputs("ThreadState-startActivityTimeout.txt");
+            throw new RuntimeException(String.format("Could not launch intent %s within %s seconds."
+                    + " Perhaps the main thread has not gone idle within a reasonable amount of "
+                    + "time? There could be an animation or something constantly repainting the "
+                    + "screen. Or the activity is doing network calls on creation? See the "
+                    + "threaddump logs. For your reference the last time the event queue was idle "
+                    + "before your activity launch request was %s and now the last time the queue "
+                    + "went idle was: %s. If these numbers are the same your activity might be "
+                    +"hogging the event queue.",
+                    intent, START_ACTIVITY_TIMEOUT_SECONDS, lastIdleTimeBeforeLaunch,
+                    mLastIdleTime.get()));
+        } catch (ExecutionException ee) {
+            throw new RuntimeException("Could not launch activity", ee.getCause());
+        } catch (InterruptedException ie) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("interrupted", ie);
+        }
+    }
+
+    private void validateNotAppThread() {
+        if (mMainThread.equals(Thread.currentThread())) {
+            throw new RuntimeException(
+                    "this method cannot be called from the main application thread");
+        }
+    }
+
+    @Override
+    public boolean onException(Object obj, Throwable e) {
+        String error = String.format("Exception encountered by: %s. Dumping thread state to "
+                + "outputs and pining for the fjords.", obj);
+        Log.e(LOG_TAG, error, e);
+        dumpThreadStateToOutputs("ThreadState-onException.txt");
+        Log.e(LOG_TAG, "Dying now...");
+        return super.onException(obj, e);
+    }
+
+    protected final void dumpThreadStateToOutputs(String outputFileName) {
+        String threadState = getThreadState();
+        Log.e("THREAD_STATE", threadState);
+    }
+
+    private static String getThreadState() {
+        Set<Map.Entry<Thread, StackTraceElement[]>> threads = Thread.getAllStackTraces().entrySet();
+        StringBuilder threadState = new StringBuilder();
+        for (Map.Entry<Thread, StackTraceElement[]> threadAndStack : threads) {
+            StringBuilder threadMessage = new StringBuilder("  ").append(threadAndStack.getKey());
+            threadMessage.append("\n");
+            for (StackTraceElement ste : threadAndStack.getValue()) {
+                threadMessage.append("    ");
+                threadMessage.append(ste.toString());
+                threadMessage.append("\n");
+            }
+            threadMessage.append("\n");
+            threadState.append(threadMessage.toString());
+        }
+        return threadState.toString();
+    }
+
+    @Override
+    public void callActivityOnDestroy(Activity activity) {
+        super.callActivityOnDestroy(activity);
+        mLifecycleMonitor.signalLifecycleChange(Stage.DESTROYED, activity);
+    }
+
+    @Override
+    public void callActivityOnRestart(Activity activity) {
+        super.callActivityOnRestart(activity);
+        mLifecycleMonitor.signalLifecycleChange(Stage.RESTARTED, activity);
+    }
+
+    @Override
+    public void callActivityOnCreate(Activity activity, Bundle bundle) {
+        mLifecycleMonitor.signalLifecycleChange(Stage.PRE_ON_CREATE, activity);
+        super.callActivityOnCreate(activity, bundle);
+        mLifecycleMonitor.signalLifecycleChange(Stage.CREATED, activity);
+    }
+
+    // NOTE: we need to keep a count of activities between the start
+    // and stop lifecycle internal to our instrumentation. Exiting the test
+    // process with activities in this state can cause crashes/flakiness
+    // that would impact a subsequent test run.
+    @Override
+    public void callActivityOnStart(Activity activity) {
+        mStartedActivityCounter.incrementAndGet();
+        try {
+            super.callActivityOnStart(activity);
+            mLifecycleMonitor.signalLifecycleChange(Stage.STARTED, activity);
+        } catch (RuntimeException re) {
+            mStartedActivityCounter.decrementAndGet();
+            throw re;
+        }
+    }
+
+    @Override
+    public void callActivityOnStop(Activity activity) {
+        try {
+            super.callActivityOnStop(activity);
+            mLifecycleMonitor.signalLifecycleChange(Stage.STOPPED, activity);
+        } finally {
+            mStartedActivityCounter.decrementAndGet();
+        }
+    }
+
+    @Override
+    public void callActivityOnResume(Activity activity) {
+        super.callActivityOnResume(activity);
+        mLifecycleMonitor.signalLifecycleChange(Stage.RESUMED, activity);
+    }
+
+    @Override
+    public void callActivityOnPause(Activity activity) {
+        super.callActivityOnPause(activity);
+        mLifecycleMonitor.signalLifecycleChange(Stage.PAUSED, activity);
+    }
+
+    /**
+     * Loops through all the activities that have not yet finished and explicitly calls finish
+     * on them.
+     */
+    public class ActivityFinisher implements Runnable {
+        @Override
+        public void run() {
+            List<Activity> activities = new ArrayList<Activity>();
+
+            for (Stage s : EnumSet.range(Stage.CREATED, Stage.PAUSED)) {
+                activities.addAll(mLifecycleMonitor.getActivitiesInStage(s));
+            }
+
+            Log.i(LOG_TAG, "Activities that are still in CREATED to PAUSED: " + activities.size());
+
+            for (Activity activity : activities) {
+                if (!activity.isFinishing()) {
+                    try {
+                        Log.i(LOG_TAG, "Stopping activity: " + activity);
+                        activity.finish();
+                    } catch (RuntimeException e) {
+                        Log.e(LOG_TAG, "Failed to stop activity.", e);
+                    }
+                }
+            }
+        }
+    };
+}
diff --git a/support/src/android/support/test/runner/lifecycle/ActivityLifecycleCallback.java b/support/src/android/support/test/runner/lifecycle/ActivityLifecycleCallback.java
new file mode 100644
index 0000000..0ffe5c9
--- /dev/null
+++ b/support/src/android/support/test/runner/lifecycle/ActivityLifecycleCallback.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2014 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.support.test.runner.lifecycle;
+
+import android.app.Activity;
+
+/**
+ * Callback for monitoring activity lifecycle events. These callbacks are invoked on the main
+ * thread, so any long operations or violating the strict mode policies should be avoided.
+ */
+public interface ActivityLifecycleCallback {
+
+    /**
+     * Called on the main thread after an activity has processed its lifecycle change event
+     * (for example onResume or onStart)
+     *
+     * @param activity The activity
+     * @param stage its current stage.
+     */
+    public void onActivityLifecycleChanged(Activity activity, Stage stage);
+}
+
diff --git a/support/src/android/support/test/runner/lifecycle/ActivityLifecycleMonitor.java b/support/src/android/support/test/runner/lifecycle/ActivityLifecycleMonitor.java
new file mode 100644
index 0000000..f44bd59
--- /dev/null
+++ b/support/src/android/support/test/runner/lifecycle/ActivityLifecycleMonitor.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2014 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.support.test.runner.lifecycle;
+
+import android.app.Activity;
+
+import java.util.Collection;
+
+/**
+ * Interface for tests to use when they need to query the activity lifecycle state.
+ * <p>
+ * Activity lifecycle changes occur only on the UI thread - therefore listeners registered with
+ * an ActivityLifecycleMonitor should expect to be invoked on the UI thread. The direct query
+ * methods can only be called on the UI thread because otherwise they would not be able to return
+ * consistent responses.
+ * </p>
+ * <p>
+ * Retrieve instances of the monitor thru ActivityLifecycleMonitorRegistry.
+ * </p>
+ * <p>
+ * Detecting these lifecycle states requires support from Instrumentation, therefore do not expect
+ * an instance to be present under any arbitrary instrumentation.
+ * </p>
+ */
+public interface ActivityLifecycleMonitor {
+
+    /**
+     * Adds a new callback that will be notified when lifecycle changes occur.
+     * <p>
+     * Implementors will not hold a strong ref to the callback, the code which registers callbacks
+     * is responsible for this. Code which registers callbacks should responsibly
+     * remove their callback when it is no longer needed.
+     * </p>
+     * <p>
+     * Callbacks are executed on the main thread of the application, and should take care not to
+     * block or otherwise perform expensive operations as it will directly impact the application.
+     * </p>
+     *
+     * @param callback an ActivityLifecycleCallback
+     */
+    void addLifecycleCallback(ActivityLifecycleCallback callback);
+
+    /**
+     * Removes a previously registered lifecycle callback.
+     */
+    void removeLifecycleCallback(ActivityLifecycleCallback callback);
+
+    /**
+     * Returns the current lifecycle stage of a given activity.
+     * <p>
+     * This method can only return a consistant and correct answer
+     * from the main thread, therefore callers should always invoke
+     * it from the main thread and implementors are free to throw an
+     * exception if the call is not made on the main thread.
+     * </p>
+     * <p>
+     * Implementors should ensure this method returns a consistant response if called from a
+     * lifecycle callback also registered with this monitor (eg: it would be horriblely wrong if a
+     * callback sees PAUSED and calls this method with the same activity and gets RESUMED.
+     * </p>
+     *
+     * @param activity an activity in this application.
+     * @return the lifecycle stage this activity is in.
+     * @throws IllegalArgumentException if activity is unknown to the monitor.
+     * @throws NullPointerException if activity is null.
+     * @throws IllegalStateException if called off the main thread.
+     */
+    Stage getLifecycleStageOf(Activity activity);
+
+    /**
+     * Returns all activities in a given stage of their lifecycle.
+     * <p>
+     * This method can only return a consistant and correct answer from the main thread, therefore
+     * callers should always invoke it from the main thread and implementors are free to throw an
+     * exception if the call is not made on the main thread.
+     * </p>
+     * <p>
+     * Implementors should ensure this method returns a consistant response if called from a
+     * lifecycle callback also registered with this monitor (eg: it would be horriblely wrong if a
+     * callback sees PAUSED and calls this method with the PAUSED and does not see its activity in
+     * the response.
+     * </p>
+     * <p>
+     * Callers should be aware that the monitor implementation may not hold strong references to the
+     * Activities in the application. Therefore stages which are considered end stages or eligible
+     * for garbage collection on low memory situations may not return an instance of a particular
+     * activity if it has been garbage collected.
+     *
+     * @param stage the stage to query for.
+     * @return a snapshot Collection of activities in the given stage. This collection may be empty.
+     * @throws IllegalStateException if called from outside the main thread.
+     */
+    Collection<Activity> getActivitiesInStage(Stage stage);
+}
diff --git a/support/src/android/support/test/runner/lifecycle/Stage.java b/support/src/android/support/test/runner/lifecycle/Stage.java
new file mode 100644
index 0000000..49f212e
--- /dev/null
+++ b/support/src/android/support/test/runner/lifecycle/Stage.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 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.support.test.runner.lifecycle;
+
+/**
+ * An enumeration of the lifecycle stages an activity undergoes.
+ * <p>
+ * See the {@link android.app.Activity} javadoc for detailed documentation.
+ * </p>
+ */
+public enum Stage {
+    /** Indicates that onCreate is being called before any onCreate code executes.*/
+    PRE_ON_CREATE,
+    /** Indicates that onCreate has been called. */
+    CREATED,
+    /** Indicates that onStart has been called. */
+    STARTED,
+    /** Indicates that onResume has been called - activity is now visible to user. */
+    RESUMED,
+    /** Indicates that onPause has been called - activity is no longer in the foreground. */
+    PAUSED,
+    /** Indicates that onStop has been called - activity is no longer visible to the user. */
+    STOPPED,
+    /** Indicates that onResume has been called - we have navigated back to the activity. */
+    RESTARTED,
+    /** Indicates that onDestroy has been called - system is shutting down the activity. */
+    DESTROYED
+}
diff --git a/support/tests/.classpath b/support/tests/.classpath
index 487c349..730d799 100644
--- a/support/tests/.classpath
+++ b/support/tests/.classpath
@@ -8,6 +8,8 @@
 	<classpathentry kind="src" path="mockito-src"/>
 	<classpathentry kind="src" path="dexmaker-mockito"/>
 	<classpathentry kind="src" path="android-support-test-src"/>
+	<classpathentry kind="src" path="hamcrest-library-src"/>
+	<classpathentry kind="src" path="hamcrest-integration-src"/>
 	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
 	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
 	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
diff --git a/support/tests/.project b/support/tests/.project
index 8070e85..6608212 100644
--- a/support/tests/.project
+++ b/support/tests/.project
@@ -52,6 +52,16 @@
 			<locationURI>ANDROID_TOP/external/dexmaker/src/mockito/java</locationURI>
 		</link>
 		<link>
+			<name>hamcrest-integration-src</name>
+			<type>2</type>
+			<locationURI>ANDROID_TOP/external/hamcrest/integration/src</locationURI>
+		</link>
+		<link>
+			<name>hamcrest-library-src</name>
+			<type>2</type>
+			<locationURI>ANDROID_TOP/external/hamcrest/library/src</locationURI>
+		</link>
+		<link>
 			<name>hamcrest-src</name>
 			<type>2</type>
 			<locationURI>ANDROID_TOP/external/hamcrest/src</locationURI>
diff --git a/support/tests/Android.mk b/support/tests/Android.mk
index 02cbe1a..6cf0c54 100644
--- a/support/tests/Android.mk
+++ b/support/tests/Android.mk
@@ -28,7 +28,7 @@
 # SDK 10 needed for mockito/objnesis. Otherwise 8 would work
 LOCAL_SDK_VERSION := 10
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test mockito-target dexmaker
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test mockito-target dexmaker hamcrest-library hamcrest-integration
 
 LOCAL_PROGUARD_ENABLED := disabled
 
diff --git a/support/tests/AndroidManifest.xml b/support/tests/AndroidManifest.xml
index 9585a9e..5cf3430 100644
--- a/support/tests/AndroidManifest.xml
+++ b/support/tests/AndroidManifest.xml
@@ -18,6 +18,7 @@
    package="android.support.test.tests">
 
     <application>
+        <uses-library android:name="android.test.runner" />
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/support/tests/src/android/support/test/internal/runner/InstrumentationArgumentsRegistryTest.java b/support/tests/src/android/support/test/internal/runner/InstrumentationArgumentsRegistryTest.java
new file mode 100644
index 0000000..864d32e
--- /dev/null
+++ b/support/tests/src/android/support/test/internal/runner/InstrumentationArgumentsRegistryTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2014 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.support.test.internal.runner;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.lessThan;
+
+import android.os.Bundle;
+import android.support.test.internal.runner.InstrumentationArgumentsRegistry;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+/**
+ * InstrumentationArgumentsRegistry tests.
+ */
+@SmallTest
+public class InstrumentationArgumentsRegistryTest extends TestCase {
+
+    public void testArgumentsArePopulated() {
+        assertNotNull(InstrumentationArgumentsRegistry.getInstance());
+    }
+
+    public void testModifyingReadBundleShouldNotAffectFutureReads() {
+        Bundle readArguments = InstrumentationArgumentsRegistry.getInstance();
+        int originalSize = readArguments.size();
+
+        readArguments.putString("mykey", "myvalue");
+
+        assertThat(originalSize, lessThan(readArguments.size()));
+        // Subsequent reads should not be affected by the local modifications.
+        assertEquals(originalSize, InstrumentationArgumentsRegistry.getInstance().size());
+    }
+
+    public void testModifyingSetBundleShouldNotAffectFutureReads() {
+        Bundle setArguments = new Bundle();
+        int originalSize = setArguments.size();
+        InstrumentationArgumentsRegistry.registerInstance(setArguments);
+        Bundle readArguments = InstrumentationArgumentsRegistry.getInstance();
+        assertEquals(originalSize, readArguments.size());
+
+        readArguments.putString("mykey", "myvalue");
+
+        // Subsequent reads should not be affected by the local modifications.
+        assertEquals(originalSize, InstrumentationArgumentsRegistry.getInstance().size());
+    }
+}
\ No newline at end of file
diff --git a/support/tests/src/android/support/test/internal/runner/lifecycle/ActivityLifecycleMonitorImplTest.java b/support/tests/src/android/support/test/internal/runner/lifecycle/ActivityLifecycleMonitorImplTest.java
new file mode 100644
index 0000000..aa5de5a
--- /dev/null
+++ b/support/tests/src/android/support/test/internal/runner/lifecycle/ActivityLifecycleMonitorImplTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2014 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.support.test.internal.runner.lifecycle;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.isIn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.Activity;
+import android.support.test.internal.runner.lifecycle.ActivityLifecycleMonitorImpl;
+import android.support.test.runner.lifecycle.ActivityLifecycleCallback;
+import android.support.test.runner.lifecycle.Stage;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * ActivityLifecycleMonitorImpl tests.
+ */
+@SmallTest
+public class ActivityLifecycleMonitorImplTest extends TestCase {
+
+    private final Activity mMockActivity = mock(Activity.class);
+
+    private final ActivityLifecycleMonitorImpl mMonitor = new ActivityLifecycleMonitorImpl(true);
+
+    public void testAddRemoveListener() {
+        ActivityLifecycleCallback callback = mock(ActivityLifecycleCallback.class);
+
+        // multiple adds should only register once.
+        mMonitor.addLifecycleCallback(callback);
+        mMonitor.addLifecycleCallback(callback);
+        mMonitor.addLifecycleCallback(callback);
+
+        mMonitor.signalLifecycleChange(Stage.CREATED, mMockActivity);
+        mMonitor.signalLifecycleChange(Stage.STARTED, mMockActivity);
+
+        // multiple removes should no-op.
+        mMonitor.removeLifecycleCallback(callback);
+        mMonitor.removeLifecycleCallback(callback);
+
+        mMonitor.signalLifecycleChange(Stage.DESTROYED, mMockActivity);
+
+
+        verify(callback).onActivityLifecycleChanged(mMockActivity, Stage.CREATED);
+        verify(callback).onActivityLifecycleChanged(mMockActivity, Stage.STARTED);
+        verify(callback, never()).onActivityLifecycleChanged(mMockActivity, Stage.DESTROYED);
+    }
+
+    public void testCallbackConsistancy() {
+        ConsistancyCheckingCallback callback = new ConsistancyCheckingCallback();
+        mMonitor.addLifecycleCallback(callback);
+
+        for (Stage stage : Stage.values()) {
+            mMonitor.signalLifecycleChange(stage, mMockActivity);
+            if (null != callback.mError) {
+                throw callback.mError;
+            }
+        }
+    }
+
+    public void testDirectQueries() {
+        Activity mock1 = mock(Activity.class);
+        Activity mock2 = mock(Activity.class);
+        Activity mock3 = mock(Activity.class);
+
+        mMonitor.signalLifecycleChange(Stage.CREATED, mock1);
+        mMonitor.signalLifecycleChange(Stage.CREATED, mock2);
+        mMonitor.signalLifecycleChange(Stage.CREATED, mock3);
+
+        assertThat(mMonitor.getLifecycleStageOf(mock1), is(Stage.CREATED));
+        assertThat(mMonitor.getLifecycleStageOf(mock2), is(Stage.CREATED));
+        assertThat(mMonitor.getLifecycleStageOf(mock3), is(Stage.CREATED));
+
+        List<Activity> expectedActivities = new ArrayList<Activity>();
+        expectedActivities.add(mock1);
+        expectedActivities.add(mock2);
+        expectedActivities.add(mock3);
+
+        assertTrue(expectedActivities.containsAll(mMonitor.getActivitiesInStage(Stage.CREATED)));
+
+        mMonitor.signalLifecycleChange(Stage.DESTROYED, mock1);
+        mMonitor.signalLifecycleChange(Stage.PAUSED, mock2);
+        mMonitor.signalLifecycleChange(Stage.PAUSED, mock3);
+        assertThat(mMonitor.getLifecycleStageOf(mock1), is(Stage.DESTROYED));
+        assertThat(mMonitor.getLifecycleStageOf(mock2), is(Stage.PAUSED));
+        assertThat(mMonitor.getLifecycleStageOf(mock3), is(Stage.PAUSED));
+
+        assertThat(mMonitor.getActivitiesInStage(Stage.CREATED).isEmpty(), is(true));
+        assertThat(mock1, isIn(mMonitor.getActivitiesInStage(Stage.DESTROYED)));
+        assertThat(mock2, isIn(mMonitor.getActivitiesInStage(Stage.PAUSED)));
+        assertThat(mock3, isIn(mMonitor.getActivitiesInStage(Stage.PAUSED)));
+    }
+
+    private class ConsistancyCheckingCallback implements ActivityLifecycleCallback {
+        private RuntimeException mError = null;
+
+        @Override
+        public void onActivityLifecycleChanged(Activity activity, Stage stage) {
+            try {
+                assertThat(activity, isIn(mMonitor.getActivitiesInStage(stage)));
+                assertThat(mMonitor.getLifecycleStageOf(activity), is(stage));
+            } catch (RuntimeException re) {
+                mError = re;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/support/tests/src/android/support/test/runner/AndroidJUnitRunnerLifeCycleTest.java b/support/tests/src/android/support/test/runner/AndroidJUnitRunnerLifeCycleTest.java
new file mode 100644
index 0000000..8b25a20
--- /dev/null
+++ b/support/tests/src/android/support/test/runner/AndroidJUnitRunnerLifeCycleTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2014 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.support.test.runner;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.isIn;
+import static org.mockito.Mockito.inOrder;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.test.internal.runner.InstrumentationArgumentsRegistry;
+import android.support.test.internal.runner.lifecycle.ActivityLifecycleMonitorRegistry;
+import android.support.test.runner.lifecycle.ActivityLifecycleCallback;
+import android.support.test.runner.lifecycle.ActivityLifecycleMonitor;
+import android.support.test.runner.lifecycle.Stage;
+import android.test.ActivityUnitTestCase;
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.Suppress;
+
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+
+/**
+ * Integration tests between lifecycle management methods in runner and the LifecycleMonitor.
+ */
+@MediumTest
+public class AndroidJUnitRunnerLifeCycleTest
+extends ActivityUnitTestCase<AndroidJUnitRunnerLifeCycleTest.PublicLifecycleMethodActivity> {
+
+    private final ActivityLifecycleCallback mCallback = Mockito.mock(
+            ActivityLifecycleCallback.class);
+
+    private PublicLifecycleMethodActivity mSpiedActivity;
+    private ActivityLifecycleMonitor mMonitor;
+
+    public AndroidJUnitRunnerLifeCycleTest() {
+        super(PublicLifecycleMethodActivity.class);
+    }
+
+    @Override
+    public void setActivity(Activity activity) {
+        if (null != activity) {
+            mSpiedActivity = Mockito.spy((PublicLifecycleMethodActivity) activity);
+        }
+    }
+
+    @Override
+    public PublicLifecycleMethodActivity getActivity() {
+        // otherwise ActivityUnitTestCase will call onCreate which will have side
+        // effects in lifecycle tracking which we are specifically testing for.
+        return null;
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mMonitor = ActivityLifecycleMonitorRegistry.getInstance();
+        mMonitor.addLifecycleCallback(mCallback);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mMonitor.removeLifecycleCallback(mCallback);
+        super.tearDown();
+    }
+
+    public void testInstrumentationArgumentsRegistryGetsPopulated() {
+        assertNotNull(InstrumentationArgumentsRegistry.getInstance());
+    }
+
+    @UiThreadTest
+    public void testActivityPreOnCreateCalled() {
+        startActivity(new Intent(), null, null);
+        mSpiedActivity.setRunnableForOnCreate(new Runnable() {
+            @Override
+            public void run() {
+                assertThat(mSpiedActivity, isIn(mMonitor.getActivitiesInStage(Stage.PRE_ON_CREATE)));
+                assertThat(mMonitor.getLifecycleStageOf(mSpiedActivity), is(Stage.PRE_ON_CREATE));
+            }
+        });
+        getInstrumentation().callActivityOnCreate(mSpiedActivity, new Bundle());
+    }
+
+    // temporarily suppress - fails due to some sort of mockito issue
+    @Suppress
+    @UiThreadTest
+    public void testOnStartStopCalled() {
+        startActivity(new Intent(), null, null);
+
+        // if we dont pair start/stop together the test runner will block until a timeout
+        // occurs waiting for the activity to stop.
+        getInstrumentation().callActivityOnStart(mSpiedActivity);
+        assertThat(mSpiedActivity, isIn(mMonitor.getActivitiesInStage(Stage.STARTED)));
+        assertThat(mMonitor.getLifecycleStageOf(mSpiedActivity), is(Stage.STARTED));
+
+        getInstrumentation().callActivityOnStop(mSpiedActivity);
+        assertThat(mSpiedActivity, isIn(mMonitor.getActivitiesInStage(Stage.STOPPED)));
+        assertThat(mMonitor.getLifecycleStageOf(mSpiedActivity), is(Stage.STOPPED));
+
+        InOrder order = inOrder(mSpiedActivity, mCallback);
+        order.verify(mSpiedActivity).onStart();
+        order.verify(mCallback).onActivityLifecycleChanged(mSpiedActivity, Stage.STARTED);
+        order.verify(mSpiedActivity).onStop();
+        order.verify(mCallback).onActivityLifecycleChanged(mSpiedActivity, Stage.STOPPED);
+    }
+
+    @UiThreadTest
+    public void testOnCreateCalled() {
+        startActivity(new Intent(), null, null);
+        Bundle b = new Bundle();
+        getInstrumentation().callActivityOnCreate(mSpiedActivity, b);
+
+        assertThat(mSpiedActivity, isIn(mMonitor.getActivitiesInStage(Stage.CREATED)));
+        assertThat(mMonitor.getLifecycleStageOf(mSpiedActivity), is(Stage.CREATED));
+        InOrder order = inOrder(mSpiedActivity, mCallback);
+        order.verify(mSpiedActivity).onCreate(b);
+        order.verify(mCallback).onActivityLifecycleChanged(mSpiedActivity, Stage.CREATED);
+    }
+
+    @UiThreadTest
+    public void testOnResumeCalled() {
+        startActivity(new Intent(), null, null);
+        getInstrumentation().callActivityOnResume(mSpiedActivity);
+
+        assertThat(mSpiedActivity, isIn(mMonitor.getActivitiesInStage(Stage.RESUMED)));
+        assertThat(mMonitor.getLifecycleStageOf(mSpiedActivity), is(Stage.RESUMED));
+        InOrder order = inOrder(mSpiedActivity, mCallback);
+        order.verify(mSpiedActivity).onResume();
+        order.verify(mCallback).onActivityLifecycleChanged(mSpiedActivity, Stage.RESUMED);
+    }
+
+    @UiThreadTest
+    public void testOnPauseCalled() {
+        startActivity(new Intent(), null, null);
+        getInstrumentation().callActivityOnPause(mSpiedActivity);
+
+        assertThat(mSpiedActivity, isIn(mMonitor.getActivitiesInStage(Stage.PAUSED)));
+        assertThat(mMonitor.getLifecycleStageOf(mSpiedActivity), is(Stage.PAUSED));
+        InOrder order = inOrder(mSpiedActivity, mCallback);
+        order.verify(mSpiedActivity).onPause();
+        order.verify(mCallback).onActivityLifecycleChanged(mSpiedActivity, Stage.PAUSED);
+    }
+
+    @UiThreadTest
+    public void testOnRestartCalled() {
+        startActivity(new Intent(), null, null);
+        getInstrumentation().callActivityOnRestart(mSpiedActivity);
+
+        assertThat(mSpiedActivity, isIn(mMonitor.getActivitiesInStage(Stage.RESTARTED)));
+        assertThat(mMonitor.getLifecycleStageOf(mSpiedActivity), is(Stage.RESTARTED));
+        InOrder order = inOrder(mSpiedActivity, mCallback);
+        order.verify(mSpiedActivity).onRestart();
+        order.verify(mCallback).onActivityLifecycleChanged(mSpiedActivity, Stage.RESTARTED);
+    }
+
+    @UiThreadTest
+    public void testOnDestroyCalled() {
+        startActivity(new Intent(), null, null);
+        getInstrumentation().callActivityOnDestroy(mSpiedActivity);
+
+        assertThat(mSpiedActivity, isIn(mMonitor.getActivitiesInStage(Stage.DESTROYED)));
+        assertThat(mMonitor.getLifecycleStageOf(mSpiedActivity), is(Stage.DESTROYED));
+        InOrder order = inOrder(mSpiedActivity, mCallback);
+        order.verify(mSpiedActivity).onDestroy();
+        order.verify(mCallback).onActivityLifecycleChanged(mSpiedActivity, Stage.DESTROYED);
+    }
+
+    /**
+     * Makes lifecycle methods public so we can verify on them.
+     *
+     */
+    public static class PublicLifecycleMethodActivity extends Activity {
+        private Runnable runnableForOnCreate;
+
+        /**
+         * Invokes the runnable in onCreate of this activity.
+         *
+         * @param runnable runnable to invoke in onCreate or {@code null} for no-op runnable.
+         */
+        public void setRunnableForOnCreate(Runnable runnable) {
+            runnableForOnCreate = runnable;
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+        }
+
+        @Override
+        public void onStop() {
+            super.onStop();
+        }
+
+        @Override
+        public void onCreate(Bundle b) {
+            super.onCreate(b);
+            if (runnableForOnCreate != null) {
+                runnableForOnCreate.run();
+            }
+        }
+
+        @Override
+        public void onRestart() {
+            super.onRestart();
+        }
+
+        @Override
+        public void onDestroy() {
+            super.onDestroy();
+        }
+
+        @Override
+        public void onPause() {
+            super.onPause();
+        }
+
+        @Override
+        public void onResume() {
+            super.onResume();
+        }
+    }
+}
\ No newline at end of file