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 
17 package io.appium.droiddriver.runner;
18 
19 import android.app.Activity;
20 import android.os.Build;
21 import android.os.Bundle;
22 import android.test.AndroidTestRunner;
23 import android.test.InstrumentationTestRunner;
24 import android.test.suitebuilder.TestMethod;
25 import android.util.Log;
26 
27 import com.android.internal.util.Predicate;
28 
29 import junit.framework.AssertionFailedError;
30 import junit.framework.Test;
31 import junit.framework.TestListener;
32 
33 import java.lang.annotation.Annotation;
34 import java.util.ArrayList;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Set;
38 
39 import io.appium.droiddriver.helpers.DroidDrivers;
40 import io.appium.droiddriver.util.ActivityUtils;
41 import io.appium.droiddriver.util.ActivityUtils.Supplier;
42 import io.appium.droiddriver.util.InstrumentationUtils;
43 import io.appium.droiddriver.util.Logs;
44 
45 /**
46  * Adds activity watcher to InstrumentationTestRunner.
47  */
48 public 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
getAndroidTestRunner()58   protected AndroidTestRunner getAndroidTestRunner() {
59     return androidTestRunner;
60   }
61 
62   /**
63    * {@inheritDoc}
64    * <p>
65    * Initializes {@link InstrumentationUtils}.
66    */
67   @Override
onCreate(Bundle arguments)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
onStart()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
getBuilderRequirements()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
callActivityOnDestroy(Activity activity)175   public void callActivityOnDestroy(Activity activity) {
176     super.callActivityOnDestroy(activity);
177     synchronized (activities) {
178       activities.remove(activity);
179     }
180   }
181 
182   @Override
callActivityOnCreate(Activity activity, Bundle bundle)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
callActivityOnResume(Activity activity)191   public void callActivityOnResume(Activity activity) {
192     super.callActivityOnResume(activity);
193     runningActivity = activity;
194   }
195 
196   @Override
callActivityOnPause(Activity activity)197   public void callActivityOnPause(Activity activity) {
198     super.callActivityOnPause(activity);
199     if (activity == runningActivity) {
200       runningActivity = null;
201     }
202   }
203 }
204