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