AppLaunch.java revision 32abd66ebd6d63cfc631ce5f4425bb5dc4a4beac
1/* 2 * Copyright (C) 2013 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 */ 16package com.android.tests.applaunch; 17 18import android.app.ActivityManager; 19import android.app.ActivityManager.ProcessErrorStateInfo; 20import android.app.ActivityManagerNative; 21import android.app.IActivityManager; 22import android.app.IActivityManager.WaitResult; 23import android.content.Context; 24import android.content.Intent; 25import android.content.pm.PackageManager; 26import android.content.pm.PackageManager.NameNotFoundException; 27import android.content.pm.ResolveInfo; 28import android.os.Bundle; 29import android.os.RemoteException; 30import android.os.UserHandle; 31import android.test.InstrumentationTestCase; 32import android.test.InstrumentationTestRunner; 33import android.util.Log; 34 35import java.util.HashMap; 36import java.util.LinkedHashMap; 37import java.util.List; 38import java.util.Map; 39 40/** 41 * This test is intended to measure the time it takes for the apps to start. 42 * Names of the applications are passed in command line, and the 43 * test starts each application, and reports the start up time in milliseconds. 44 * The instrumentation expects the following key to be passed on the command line: 45 * apps - A list of applications to start and their corresponding result keys 46 * in the following format: 47 * -e apps <app name>^<result key>|<app name>^<result key> 48 */ 49public class AppLaunch extends InstrumentationTestCase { 50 51 private static final int JOIN_TIMEOUT = 10000; 52 private static final String TAG = AppLaunch.class.getSimpleName(); 53 private static final String KEY_APPS = "apps"; 54 private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations"; 55 private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 7500; //7.5s to allow app to idle 56 private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; //750ms idle for non initial launches 57 private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 2000; //2s between launching apps 58 59 private Map<String, Intent> mNameToIntent; 60 private Map<String, String> mNameToProcess; 61 private Map<String, String> mNameToResultKey; 62 private Map<String, Long> mNameToLaunchTime; 63 private IActivityManager mAm; 64 private int mLaunchIterations = 10; 65 private Bundle mResult = new Bundle(); 66 67 public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException { 68 InstrumentationTestRunner instrumentation = 69 (InstrumentationTestRunner)getInstrumentation(); 70 Bundle args = instrumentation.getArguments(); 71 mAm = ActivityManagerNative.getDefault(); 72 73 createMappings(); 74 parseArgs(args); 75 76 // do initial app launch, without force stopping 77 for (String app : mNameToResultKey.keySet()) { 78 long launchTime = startApp(app, false); 79 if (launchTime <=0 ) { 80 mNameToLaunchTime.put(app, -1L); 81 // simply pass the app if launch isn't successful 82 // error should have already been logged by startApp 83 continue; 84 } 85 sleep(INITIAL_LAUNCH_IDLE_TIMEOUT); 86 closeApp(app, false); 87 sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); 88 } 89 // do the real app launch now 90 for (int i = 0; i < mLaunchIterations; i++) { 91 for (String app : mNameToResultKey.keySet()) { 92 long totalLaunchTime = mNameToLaunchTime.get(app); 93 long launchTime = 0; 94 if (totalLaunchTime < 0) { 95 // skip if the app has previous failures 96 continue; 97 } 98 launchTime = startApp(app, true); 99 if (launchTime <= 0) { 100 // if it fails once, skip the rest of the launches 101 mNameToLaunchTime.put(app, -1L); 102 continue; 103 } 104 totalLaunchTime += launchTime; 105 mNameToLaunchTime.put(app, totalLaunchTime); 106 sleep(POST_LAUNCH_IDLE_TIMEOUT); 107 closeApp(app, true); 108 sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); 109 } 110 } 111 for (String app : mNameToResultKey.keySet()) { 112 long totalLaunchTime = mNameToLaunchTime.get(app); 113 if (totalLaunchTime != -1) { 114 mResult.putDouble(mNameToResultKey.get(app), 115 ((double) totalLaunchTime) / mLaunchIterations); 116 } 117 } 118 instrumentation.sendStatus(0, mResult); 119 } 120 121 private void parseArgs(Bundle args) { 122 mNameToResultKey = new LinkedHashMap<String, String>(); 123 mNameToLaunchTime = new HashMap<String, Long>(); 124 String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS); 125 if (launchIterations != null) { 126 mLaunchIterations = Integer.parseInt(launchIterations); 127 } 128 String appList = args.getString(KEY_APPS); 129 if (appList == null) 130 return; 131 132 String appNames[] = appList.split("\\|"); 133 for (String pair : appNames) { 134 String[] parts = pair.split("\\^"); 135 if (parts.length != 2) { 136 Log.e(TAG, "The apps key is incorectly formatted"); 137 fail(); 138 } 139 140 mNameToResultKey.put(parts[0], parts[1]); 141 mNameToLaunchTime.put(parts[0], 0L); 142 } 143 } 144 145 private void createMappings() { 146 mNameToIntent = new LinkedHashMap<String, Intent>(); 147 mNameToProcess = new LinkedHashMap<String, String>(); 148 149 PackageManager pm = getInstrumentation().getContext() 150 .getPackageManager(); 151 Intent intentToResolve = new Intent(Intent.ACTION_MAIN); 152 intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER); 153 List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0); 154 if (ris == null || ris.isEmpty()) { 155 Log.i(TAG, "Could not find any apps"); 156 } else { 157 for (ResolveInfo ri : ris) { 158 Intent startIntent = new Intent(intentToResolve); 159 startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 160 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 161 startIntent.setClassName(ri.activityInfo.packageName, 162 ri.activityInfo.name); 163 String appName = ri.loadLabel(pm).toString(); 164 if (appName != null) { 165 mNameToIntent.put(appName, startIntent); 166 mNameToProcess.put(appName, ri.activityInfo.processName); 167 } 168 } 169 } 170 } 171 172 private long startApp(String appName, boolean forceStopBeforeLaunch) 173 throws NameNotFoundException, RemoteException { 174 Log.i(TAG, "Starting " + appName); 175 176 Intent startIntent = mNameToIntent.get(appName); 177 if (startIntent == null) { 178 Log.w(TAG, "App does not exist: " + appName); 179 mResult.putString(mNameToResultKey.get(appName), "App does not exist"); 180 return -1; 181 } 182 AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, forceStopBeforeLaunch); 183 Thread t = new Thread(runnable); 184 t.start(); 185 try { 186 t.join(JOIN_TIMEOUT); 187 } catch (InterruptedException e) { 188 // ignore 189 } 190 WaitResult result = runnable.getResult(); 191 // report error if any of the following is true: 192 // * launch thread is alive 193 // * result is not null, but: 194 // * result is not START_SUCESS 195 // * or in case of no force stop, result is not TASK_TO_FRONT either 196 if (t.isAlive() || (result != null 197 && ((result.result != ActivityManager.START_SUCCESS) 198 && (!forceStopBeforeLaunch 199 && result.result != ActivityManager.START_TASK_TO_FRONT)))) { 200 Log.w(TAG, "Assuming app " + appName + " crashed."); 201 reportError(appName, mNameToProcess.get(appName)); 202 return -1; 203 } 204 return result.thisTime; 205 } 206 207 private void closeApp(String appName, boolean forceStopApp) { 208 Intent homeIntent = new Intent(Intent.ACTION_MAIN); 209 homeIntent.addCategory(Intent.CATEGORY_HOME); 210 homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 211 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 212 getInstrumentation().getContext().startActivity(homeIntent); 213 sleep(POST_LAUNCH_IDLE_TIMEOUT); 214 if (forceStopApp) { 215 Intent startIntent = mNameToIntent.get(appName); 216 if (startIntent != null) { 217 String packageName = startIntent.getComponent().getPackageName(); 218 try { 219 mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); 220 } catch (RemoteException e) { 221 Log.w(TAG, "Error closing app", e); 222 } 223 } 224 } 225 } 226 227 private void sleep(int time) { 228 try { 229 Thread.sleep(time); 230 } catch (InterruptedException e) { 231 // ignore 232 } 233 } 234 235 private void reportError(String appName, String processName) { 236 ActivityManager am = (ActivityManager) getInstrumentation() 237 .getContext().getSystemService(Context.ACTIVITY_SERVICE); 238 List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState(); 239 if (crashes != null) { 240 for (ProcessErrorStateInfo crash : crashes) { 241 if (!crash.processName.equals(processName)) 242 continue; 243 244 Log.w(TAG, appName + " crashed: " + crash.shortMsg); 245 mResult.putString(mNameToResultKey.get(appName), crash.shortMsg); 246 return; 247 } 248 } 249 250 mResult.putString(mNameToResultKey.get(appName), 251 "Crashed for unknown reason"); 252 Log.w(TAG, appName 253 + " not found in process list, most likely it is crashed"); 254 } 255 256 private class AppLaunchRunnable implements Runnable { 257 private Intent mLaunchIntent; 258 private IActivityManager.WaitResult mResult; 259 private boolean mForceStopBeforeLaunch; 260 261 public AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch) { 262 mLaunchIntent = intent; 263 mForceStopBeforeLaunch = forceStopBeforeLaunch; 264 } 265 266 public IActivityManager.WaitResult getResult() { 267 return mResult; 268 } 269 270 public void run() { 271 try { 272 String packageName = mLaunchIntent.getComponent().getPackageName(); 273 if (mForceStopBeforeLaunch) { 274 mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); 275 } 276 String mimeType = mLaunchIntent.getType(); 277 if (mimeType == null && mLaunchIntent.getData() != null 278 && "content".equals(mLaunchIntent.getData().getScheme())) { 279 mimeType = mAm.getProviderMimeType(mLaunchIntent.getData(), 280 UserHandle.USER_CURRENT); 281 } 282 283 mResult = mAm.startActivityAndWait(null, null, mLaunchIntent, mimeType, 284 null, null, 0, mLaunchIntent.getFlags(), null, null, null, 285 UserHandle.USER_CURRENT); 286 } catch (RemoteException e) { 287 Log.w(TAG, "Error launching app", e); 288 } 289 } 290 } 291} 292