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