/* * 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. *
* A short list of these capabilities: ** Subclasses must call up to onCreate(). This onCreate method does not call start() * it is the subclasses responsibility to call start if it desires. *
*/ @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. ** Subclasses should call super.onStart() before executing any code that touches the application * and it's state. *
*/ @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. ** 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. *
*/ @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(). ** 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). *
** We give the app 2 seconds to stop all its activities, then we proceed. *
*/ 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