/* * Copyright (C) 2013 DroidDriver committers * * 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 io.appium.droiddriver.runner; import android.app.Activity; import android.os.Build; import android.os.Bundle; import android.test.AndroidTestRunner; import android.test.InstrumentationTestRunner; import android.test.suitebuilder.TestMethod; import android.util.Log; import com.android.internal.util.Predicate; import junit.framework.AssertionFailedError; import junit.framework.Test; import junit.framework.TestListener; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import io.appium.droiddriver.helpers.DroidDrivers; import io.appium.droiddriver.util.ActivityUtils; import io.appium.droiddriver.util.ActivityUtils.Supplier; import io.appium.droiddriver.util.InstrumentationUtils; import io.appium.droiddriver.util.Logs; /** * Adds activity watcher to InstrumentationTestRunner. */ public class TestRunner extends InstrumentationTestRunner { private final Set<Activity> activities = new HashSet<Activity>(); private final AndroidTestRunner androidTestRunner = new AndroidTestRunner(); private volatile Activity runningActivity; /** * Returns an {@link AndroidTestRunner} that is shared by this and super, such * that we can add custom {@link TestListener}s. */ @Override protected AndroidTestRunner getAndroidTestRunner() { return androidTestRunner; } /** * {@inheritDoc} * <p> * Initializes {@link InstrumentationUtils}. */ @Override public void onCreate(Bundle arguments) { InstrumentationUtils.init(this, arguments); super.onCreate(arguments); } /** * {@inheritDoc} * <p> * Adds a {@link TestListener} that finishes all created activities. */ @Override public void onStart() { getAndroidTestRunner().addTestListener(new TestListener() { @Override public void endTest(Test test) { // Try to finish activity on best-effort basis - TestListener should // not throw. final Activity[] activitiesCopy; synchronized (activities) { if (activities.isEmpty()) { return; } activitiesCopy = activities.toArray(new Activity[activities.size()]); } try { InstrumentationUtils.runOnMainSyncWithTimeout(new Runnable() { @Override public void run() { for (Activity activity : activitiesCopy) { if (!activity.isFinishing()) { try { Logs.log(Log.INFO, "Stopping activity: " + activity); activity.finish(); } catch (Throwable e) { Logs.log(Log.ERROR, e, "Failed to stop activity"); } } } } }); } catch (Throwable e) { Logs.log(Log.ERROR, e); } // We've done what we can. Clear activities if any are left. synchronized (activities) { activities.clear(); runningActivity = null; } } @Override public void addError(Test arg0, Throwable arg1) {} @Override public void addFailure(Test arg0, AssertionFailedError arg1) {} @Override public void startTest(Test arg0) {} }); ActivityUtils.setRunningActivitySupplier(new Supplier<Activity>() { @Override public Activity get() { return runningActivity; } }); super.onStart(); } // Overrides InstrumentationTestRunner List<Predicate<TestMethod>> getBuilderRequirements() { List<Predicate<TestMethod>> requirements = new ArrayList<Predicate<TestMethod>>(); requirements.add(new Predicate<TestMethod>() { @Override public boolean apply(TestMethod arg0) { MinSdkVersion minSdkVersion = getAnnotation(arg0, MinSdkVersion.class); if (minSdkVersion != null && minSdkVersion.value() > Build.VERSION.SDK_INT) { Logs.logfmt(Log.INFO, "filtered %s#%s: MinSdkVersion=%d", arg0.getEnclosingClassname(), arg0.getName(), minSdkVersion.value()); return false; } UseUiAutomation useUiAutomation = getAnnotation(arg0, UseUiAutomation.class); if (useUiAutomation != null && !DroidDrivers.hasUiAutomation()) { Logs.logfmt(Log.INFO, "filtered %s#%s: Has @UseUiAutomation, but ro.build.version.sdk=%d", arg0.getEnclosingClassname(), arg0.getName(), Build.VERSION.SDK_INT); return false; } return true; } private <T extends Annotation> T getAnnotation(TestMethod testMethod, Class<T> clazz) { T annotation = testMethod.getAnnotation(clazz); if (annotation == null) { annotation = testMethod.getEnclosingClass().getAnnotation(clazz); } return annotation; } }); return requirements; } @Override public void callActivityOnDestroy(Activity activity) { super.callActivityOnDestroy(activity); synchronized (activities) { activities.remove(activity); } } @Override public void callActivityOnCreate(Activity activity, Bundle bundle) { super.callActivityOnCreate(activity, bundle); synchronized (activities) { activities.add(activity); } } @Override public void callActivityOnResume(Activity activity) { super.callActivityOnResume(activity); runningActivity = activity; } @Override public void callActivityOnPause(Activity activity) { super.callActivityOnPause(activity); if (activity == runningActivity) { runningActivity = null; } } }