• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
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 android.test.appsmoke;
18 
19 import android.app.ActivityManager;
20 import android.app.IActivityController;
21 import android.app.Instrumentation;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.LauncherActivityInfo;
25 import android.content.pm.LauncherApps;
26 import android.content.pm.PackageManager;
27 import android.os.Bundle;
28 import android.os.RemoteException;
29 import android.os.SystemClock;
30 import android.os.UserHandle;
31 import android.os.UserManager;
32 import android.support.test.InstrumentationRegistry;
33 import android.support.test.launcherhelper.ILauncherStrategy;
34 import android.support.test.launcherhelper.LauncherStrategyFactory;
35 import android.support.test.uiautomator.UiDevice;
36 import android.util.Log;
37 
38 import org.junit.After;
39 import org.junit.AfterClass;
40 import org.junit.Assert;
41 import org.junit.Before;
42 import org.junit.BeforeClass;
43 import org.junit.Test;
44 import org.junit.runner.RunWith;
45 import org.junit.runners.Parameterized;
46 import org.junit.runners.Parameterized.Parameter;
47 import org.junit.runners.Parameterized.Parameters;
48 
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Collection;
52 import java.util.Collections;
53 import java.util.HashSet;
54 import java.util.List;
55 import java.util.Set;
56 
57 @RunWith(Parameterized.class)
58 public class AppSmokeTest {
59 
60     private static final String TAG = AppSmokeTest.class.getSimpleName();
61     private static final String EXCLUDE_LIST = "exclude_apps";
62     private static final String DEBUG_LIST = "debug_apps";
63     private static final long WAIT_FOR_ANR = 6000;
64 
65     @Parameter
66     public LaunchParameter mAppInfo;
67 
68     private boolean mAppHasError = false;
69     private boolean mLaunchIntentDetected = false;
70     private boolean mHasLeanback = false;
71     private ILauncherStrategy mLauncherStrategy = null;
72     private static UiDevice sDevice = null;
73 
74     /**
75      * Convenient internal class to hold some launch specific data
76      */
77     private static class LaunchParameter implements Comparable<LaunchParameter>{
78         public String appName;
79         public String packageName;
80         public String activityName;
81 
LaunchParameter(String appName, String packageName, String activityName)82         private LaunchParameter(String appName, String packageName, String activityName) {
83             this.appName = appName;
84             this.packageName = packageName;
85             this.activityName = activityName;
86         }
87 
88         @Override
compareTo(LaunchParameter another)89         public int compareTo(LaunchParameter another) {
90             return appName.compareTo(another.appName);
91         }
92 
93         @Override
toString()94         public String toString() {
95             return appName;
96         }
97 
toLongString()98         public String toLongString() {
99             return String.format("%s [activity: %s/%s]", appName, packageName, activityName);
100         }
101     }
102 
103     /**
104      * an activity controller to detect app launch crashes/ANR etc
105      */
106     private IActivityController mActivityController = new IActivityController.Stub() {
107 
108         @Override
109         public int systemNotResponding(String msg) throws RemoteException {
110             // let system die
111             return -1;
112         }
113 
114         @Override
115         public int appNotResponding(String processName, int pid, String processStats)
116                 throws RemoteException {
117             if (processName.startsWith(mAppInfo.packageName)) {
118                 mAppHasError = true;
119             }
120             // kill app
121             return -1;
122         }
123 
124         @Override
125         public int appEarlyNotResponding(String processName, int pid, String annotation)
126                 throws RemoteException {
127             // do nothing
128             return 0;
129         }
130 
131         @Override
132         public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg,
133                 long timeMillis, String stackTrace) throws RemoteException {
134             if (processName.startsWith(mAppInfo.packageName)) {
135                 mAppHasError = true;
136             }
137             return false;
138         }
139 
140         @Override
141         public boolean activityStarting(Intent intent, String pkg) throws RemoteException {
142             Log.d(TAG, String.format("activityStarting: pkg=%s intent=%s",
143                     pkg, intent.toInsecureString()));
144             // always allow starting
145             if (pkg.equals(mAppInfo.packageName)) {
146                 mLaunchIntentDetected = true;
147             }
148             return true;
149         }
150 
151         @Override
152         public boolean activityResuming(String pkg) throws RemoteException {
153             Log.d(TAG, String.format("activityResuming: pkg=%s", pkg));
154             // always allow resuming
155             return true;
156         }
157     };
158 
159     /**
160      * Generate the list of apps to test for launches by querying package manager
161      * @return
162      */
163     @Parameters(name = "{0}")
generateAppsList()164     public static Collection<LaunchParameter> generateAppsList() {
165         Instrumentation instr = InstrumentationRegistry.getInstrumentation();
166         Bundle args = InstrumentationRegistry.getArguments();
167         Context ctx = instr.getTargetContext();
168         List<LaunchParameter> ret = new ArrayList<>();
169         Set<String> excludedApps = new HashSet<>();
170         Set<String> debugApps = new HashSet<>();
171 
172         // parse list of app names that should be execluded from launch tests
173         if (args.containsKey(EXCLUDE_LIST)) {
174             excludedApps.addAll(Arrays.asList(args.getString(EXCLUDE_LIST).split(",")));
175         }
176         // parse list of app names used for debugging (i.e. essentially a whitelist)
177         if (args.containsKey(DEBUG_LIST)) {
178             debugApps.addAll(Arrays.asList(args.getString(DEBUG_LIST).split(",")));
179         }
180         LauncherApps la = (LauncherApps)ctx.getSystemService(Context.LAUNCHER_APPS_SERVICE);
181         UserManager um = (UserManager)ctx.getSystemService(Context.USER_SERVICE);
182         List<LauncherActivityInfo> activities = new ArrayList<>();
183         for (UserHandle handle : um.getUserProfiles()) {
184             activities.addAll(la.getActivityList(null, handle));
185         }
186         for (LauncherActivityInfo info : activities) {
187             String label = info.getLabel().toString();
188             if (!debugApps.isEmpty()) {
189                 if (!debugApps.contains(label)) {
190                     // if debug apps non-empty, we are essentially in whitelist mode
191                     // bypass any apps not on list
192                     continue;
193                 }
194             } else if (excludedApps.contains(label)) {
195                 // if not debugging apps, bypass any excluded apps
196                 continue;
197             }
198             ret.add(new LaunchParameter(label, info
199                     .getApplicationInfo().packageName, info.getName()));
200         }
201         Collections.sort(ret);
202         return ret;
203     }
204 
205     @Before
before()206     public void before() throws RemoteException {
207         ActivityManager.getService().setActivityController(mActivityController, false);
208         LauncherStrategyFactory factory = LauncherStrategyFactory.getInstance(sDevice);
209         mLauncherStrategy = factory.getLauncherStrategy();
210         // Inject an instance of instrumentation only if leanback. This enables to launch any app
211         // in the Apps and Games row on leanback launcher.
212         Instrumentation instr = InstrumentationRegistry.getInstrumentation();
213         mHasLeanback = hasLeanback(instr.getTargetContext());
214         if (mHasLeanback) {
215             factory.getLeanbackLauncherStrategy().setInstrumentation(instr);
216         }
217         mAppHasError = false;
218         mLaunchIntentDetected = false;
219     }
220 
221     @BeforeClass
beforeClass()222     public static void beforeClass() throws RemoteException {
223         sDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
224         sDevice.setOrientationNatural();
225     }
226 
227     @After
after()228     public void after() throws RemoteException {
229         sDevice.pressHome();
230         ActivityManager.getService().forceStopPackage(
231                 mAppInfo.packageName, UserHandle.USER_ALL);
232         ActivityManager.getService().setActivityController(null, false);
233     }
234 
235     @AfterClass
afterClass()236     public static void afterClass() throws RemoteException {
237         sDevice.unfreezeRotation();
238     }
239 
240     @Test
testAppLaunch()241     public void testAppLaunch() {
242         Log.d(TAG, "Launching: " + mAppInfo.toLongString());
243         long timestamp = mLauncherStrategy.launch(mAppInfo.appName, mAppInfo.packageName);
244         boolean launchResult = (timestamp != ILauncherStrategy.LAUNCH_FAILED_TIMESTAMP);
245         if (launchResult) {
246             // poke app to check if it's responsive
247             pokeApp();
248             SystemClock.sleep(WAIT_FOR_ANR);
249         }
250         if (mAppHasError) {
251             Assert.fail("app crash or ANR detected");
252         }
253         if (!launchResult && !mLaunchIntentDetected) {
254             Assert.fail("no app crash or ANR detected, but failed to launch via UI");
255         }
256         // if launchResult is false but mLaunchIntentDetected is true, we consider it as success
257         // this happens when an app is a trampoline activity to something else
258     }
259 
pokeApp()260     private void pokeApp() {
261         // The swipe action on leanback launcher that doesn't support swipe gesture may
262         // cause unnecessary focus change and test to fail.
263         // Use the dpad key to poke the app instead.
264         if (!mHasLeanback) {
265             int w = sDevice.getDisplayWidth();
266             int h = sDevice.getDisplayHeight();
267             int dY = h / 4;
268             boolean ret = sDevice.swipe(w / 2, h / 2 + dY, w / 2, h / 2 - dY, 40);
269             if (!ret) {
270                 Log.w(TAG, "Failed while attempting to poke front end window with swipe");
271             }
272         } else {
273             sDevice.pressDPadUp();
274         }
275     }
276 
hasLeanback(Context context)277     private boolean hasLeanback(Context context) {
278         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
279     }
280 }
281