1/*
2 * Copyright (C) 2013 DroidDriver committers
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package io.appium.droiddriver.runner;
18
19import android.app.Activity;
20import android.os.Build;
21import android.os.Bundle;
22import android.test.AndroidTestRunner;
23import android.test.InstrumentationTestRunner;
24import android.test.suitebuilder.TestMethod;
25import android.util.Log;
26
27import com.android.internal.util.Predicate;
28
29import junit.framework.AssertionFailedError;
30import junit.framework.Test;
31import junit.framework.TestListener;
32
33import java.lang.annotation.Annotation;
34import java.util.ArrayList;
35import java.util.HashSet;
36import java.util.List;
37import java.util.Set;
38
39import io.appium.droiddriver.helpers.DroidDrivers;
40import io.appium.droiddriver.util.ActivityUtils;
41import io.appium.droiddriver.util.ActivityUtils.Supplier;
42import io.appium.droiddriver.util.InstrumentationUtils;
43import io.appium.droiddriver.util.Logs;
44
45/**
46 * Adds activity watcher to InstrumentationTestRunner.
47 */
48public class TestRunner extends InstrumentationTestRunner {
49  private final Set<Activity> activities = new HashSet<Activity>();
50  private final AndroidTestRunner androidTestRunner = new AndroidTestRunner();
51  private volatile Activity runningActivity;
52
53  /**
54   * Returns an {@link AndroidTestRunner} that is shared by this and super, such
55   * that we can add custom {@link TestListener}s.
56   */
57  @Override
58  protected AndroidTestRunner getAndroidTestRunner() {
59    return androidTestRunner;
60  }
61
62  /**
63   * {@inheritDoc}
64   * <p>
65   * Initializes {@link InstrumentationUtils}.
66   */
67  @Override
68  public void onCreate(Bundle arguments) {
69    InstrumentationUtils.init(this, arguments);
70    super.onCreate(arguments);
71  }
72
73  /**
74   * {@inheritDoc}
75   * <p>
76   * Adds a {@link TestListener} that finishes all created activities.
77   */
78  @Override
79  public void onStart() {
80    getAndroidTestRunner().addTestListener(new TestListener() {
81      @Override
82      public void endTest(Test test) {
83        // Try to finish activity on best-effort basis - TestListener should
84        // not throw.
85        final Activity[] activitiesCopy;
86        synchronized (activities) {
87          if (activities.isEmpty()) {
88            return;
89          }
90          activitiesCopy = activities.toArray(new Activity[activities.size()]);
91        }
92
93        try {
94          InstrumentationUtils.runOnMainSyncWithTimeout(new Runnable() {
95            @Override
96            public void run() {
97              for (Activity activity : activitiesCopy) {
98                if (!activity.isFinishing()) {
99                  try {
100                    Logs.log(Log.INFO, "Stopping activity: " + activity);
101                    activity.finish();
102                  } catch (Throwable e) {
103                    Logs.log(Log.ERROR, e, "Failed to stop activity");
104                  }
105                }
106              }
107            }
108          });
109        } catch (Throwable e) {
110          Logs.log(Log.ERROR, e);
111        }
112
113        // We've done what we can. Clear activities if any are left.
114        synchronized (activities) {
115          activities.clear();
116          runningActivity = null;
117        }
118      }
119
120      @Override
121      public void addError(Test arg0, Throwable arg1) {}
122
123      @Override
124      public void addFailure(Test arg0, AssertionFailedError arg1) {}
125
126      @Override
127      public void startTest(Test arg0) {}
128    });
129
130    ActivityUtils.setRunningActivitySupplier(new Supplier<Activity>() {
131      @Override
132      public Activity get() {
133        return runningActivity;
134      }
135    });
136
137    super.onStart();
138  }
139
140  // Overrides InstrumentationTestRunner
141  List<Predicate<TestMethod>> getBuilderRequirements() {
142    List<Predicate<TestMethod>> requirements = new ArrayList<Predicate<TestMethod>>();
143    requirements.add(new Predicate<TestMethod>() {
144      @Override
145      public boolean apply(TestMethod arg0) {
146        MinSdkVersion minSdkVersion = getAnnotation(arg0, MinSdkVersion.class);
147        if (minSdkVersion != null && minSdkVersion.value() > Build.VERSION.SDK_INT) {
148          Logs.logfmt(Log.INFO, "filtered %s#%s: MinSdkVersion=%d", arg0.getEnclosingClassname(),
149              arg0.getName(), minSdkVersion.value());
150          return false;
151        }
152
153        UseUiAutomation useUiAutomation = getAnnotation(arg0, UseUiAutomation.class);
154        if (useUiAutomation != null && !DroidDrivers.hasUiAutomation()) {
155          Logs.logfmt(Log.INFO,
156              "filtered %s#%s: Has @UseUiAutomation, but ro.build.version.sdk=%d",
157              arg0.getEnclosingClassname(), arg0.getName(), Build.VERSION.SDK_INT);
158          return false;
159        }
160        return true;
161      }
162
163      private <T extends Annotation> T getAnnotation(TestMethod testMethod, Class<T> clazz) {
164        T annotation = testMethod.getAnnotation(clazz);
165        if (annotation == null) {
166          annotation = testMethod.getEnclosingClass().getAnnotation(clazz);
167        }
168        return annotation;
169      }
170    });
171    return requirements;
172  }
173
174  @Override
175  public void callActivityOnDestroy(Activity activity) {
176    super.callActivityOnDestroy(activity);
177    synchronized (activities) {
178      activities.remove(activity);
179    }
180  }
181
182  @Override
183  public void callActivityOnCreate(Activity activity, Bundle bundle) {
184    super.callActivityOnCreate(activity, bundle);
185    synchronized (activities) {
186      activities.add(activity);
187    }
188  }
189
190  @Override
191  public void callActivityOnResume(Activity activity) {
192    super.callActivityOnResume(activity);
193    runningActivity = activity;
194  }
195
196  @Override
197  public void callActivityOnPause(Activity activity) {
198    super.callActivityOnPause(activity);
199    if (activity == runningActivity) {
200      runningActivity = null;
201    }
202  }
203}
204