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