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 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 89 public int compareTo(LaunchParameter another) { 90 return appName.compareTo(another.appName); 91 } 92 93 @Override 94 public String toString() { 95 return appName; 96 } 97 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}") 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 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 222 public static void beforeClass() throws RemoteException { 223 sDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 224 sDevice.setOrientationNatural(); 225 } 226 227 @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 236 public static void afterClass() throws RemoteException { 237 sDevice.unfreezeRotation(); 238 } 239 240 @Test 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 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 277 private boolean hasLeanback(Context context) { 278 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); 279 } 280 } 281