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 java.io.OutputStreamWriter; 19 20import android.accounts.Account; 21import android.accounts.AccountManager; 22import android.app.ActivityManagerNative; 23import android.app.ActivityManager; 24import android.app.ActivityManager.ProcessErrorStateInfo; 25import android.content.Context; 26import android.content.Intent; 27import android.content.pm.PackageManager; 28import android.content.pm.PackageManager.NameNotFoundException; 29import android.content.pm.ResolveInfo; 30import android.os.Bundle; 31import android.os.RemoteException; 32import android.os.UserHandle; 33import android.app.UiAutomation; 34import android.app.IActivityManager; 35import android.app.IActivityManager.WaitResult; 36import android.support.test.rule.logging.AtraceLogger; 37import android.test.InstrumentationTestCase; 38import android.test.InstrumentationTestRunner; 39import android.util.Log; 40import java.io.File; 41import java.io.IOException; 42import java.util.HashMap; 43import java.util.HashSet; 44import java.util.LinkedHashMap; 45import java.util.List; 46import java.util.ArrayList; 47import java.util.Map; 48import java.util.Set; 49import android.os.ParcelFileDescriptor; 50import java.io.FileInputStream; 51import java.io.FileOutputStream; 52import java.io.InputStream; 53import java.io.BufferedReader; 54import java.io.BufferedWriter; 55import java.io.InputStreamReader; 56 57/** 58 * This test is intended to measure the time it takes for the apps to start. 59 * Names of the applications are passed in command line, and the 60 * test starts each application, and reports the start up time in milliseconds. 61 * The instrumentation expects the following key to be passed on the command line: 62 * apps - A list of applications to start and their corresponding result keys 63 * in the following format: 64 * -e apps <app name>^<result key>|<app name>^<result key> 65 */ 66public class AppLaunch extends InstrumentationTestCase { 67 68 private static final int JOIN_TIMEOUT = 10000; 69 private static final String TAG = AppLaunch.class.getSimpleName(); 70 // optional parameter: comma separated list of required account types before proceeding 71 // with the app launch 72 private static final String KEY_REQUIRED_ACCOUNTS = "required_accounts"; 73 private static final String KEY_APPS = "apps"; 74 private static final String KEY_TRIAL_LAUNCH = "trial_launch"; 75 private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations"; 76 private static final String KEY_LAUNCH_ORDER = "launch_order"; 77 private static final String KEY_DROP_CACHE = "drop_cache"; 78 private static final String KEY_SIMPLEPPERF_CMD = "simpleperf_cmd"; 79 private static final String KEY_TRACE_ITERATIONS = "trace_iterations"; 80 private static final String KEY_LAUNCH_DIRECTORY = "launch_directory"; 81 private static final String KEY_TRACE_DIRECTORY = "trace_directory"; 82 private static final String KEY_TRACE_CATEGORY = "trace_categories"; 83 private static final String KEY_TRACE_BUFFERSIZE = "trace_bufferSize"; 84 private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval"; 85 private static final String WEARABLE_ACTION_GOOGLE = 86 "com.google.android.wearable.action.GOOGLE"; 87 private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 60000; //60s to allow app to idle 88 private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; //750ms idle for non initial launches 89 private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 5000; //5s between launching apps 90 private static final String LAUNCH_SUB_DIRECTORY = "launch_logs"; 91 private static final String LAUNCH_FILE = "applaunch.txt"; 92 private static final String TRACE_SUB_DIRECTORY = "atrace_logs"; 93 private static final String DEFAULT_TRACE_CATEGORIES = "sched,freq,gfx,view,dalvik,webview," 94 + "input,wm,disk,am,wm"; 95 private static final String DEFAULT_TRACE_BUFFER_SIZE = "20000"; 96 private static final String DEFAULT_TRACE_DUMP_INTERVAL = "10"; 97 private static final String TRIAL_LAUNCH = "TRAIL_LAUNCH"; 98 private static final String DELIMITER = ","; 99 private static final String DROP_CACHE_SCRIPT = "/data/local/tmp/dropCache.sh"; 100 private static final String APP_LAUNCH_CMD = "am start -W -n"; 101 private static final String SUCCESS_MESSAGE = "Status: ok"; 102 private static final String THIS_TIME = "ThisTime:"; 103 private static final String LAUNCH_ITERATION = "LAUNCH_ITERATION - %d"; 104 private static final String TRACE_ITERATION = "TRACE_ITERATION - %d"; 105 private static final String LAUNCH_ITERATION_PREFIX = "LAUNCH_ITERATION"; 106 private static final String TRACE_ITERATION_PREFIX = "TRACE_ITERATION"; 107 private static final String LAUNCH_ORDER_CYCLIC = "cyclic"; 108 private static final String LAUNCH_ORDER_SEQUENTIAL = "sequential"; 109 110 111 private Map<String, Intent> mNameToIntent; 112 private Map<String, String> mNameToProcess; 113 private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>(); 114 private Map<String, String> mNameToResultKey; 115 private Map<String, List<Long>> mNameToLaunchTime; 116 private IActivityManager mAm; 117 private String mSimplePerfCmd = null; 118 private String mLaunchOrder = null; 119 private boolean mDropCache = false; 120 private int mLaunchIterations = 10; 121 private int mTraceLaunchCount = 0; 122 private String mTraceDirectoryStr = null; 123 private Bundle mResult = new Bundle(); 124 private Set<String> mRequiredAccounts; 125 private boolean mTrailLaunch = true; 126 private File mFile = null; 127 private FileOutputStream mOutputStream = null; 128 private BufferedWriter mBufferedWriter = null; 129 130 131 @Override 132 protected void setUp() throws Exception { 133 super.setUp(); 134 getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0); 135 } 136 137 @Override 138 protected void tearDown() throws Exception { 139 getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE); 140 super.tearDown(); 141 } 142 143 public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException, 144 IOException, InterruptedException { 145 InstrumentationTestRunner instrumentation = 146 (InstrumentationTestRunner)getInstrumentation(); 147 Bundle args = instrumentation.getArguments(); 148 mAm = ActivityManagerNative.getDefault(); 149 String launchDirectory = args.getString(KEY_LAUNCH_DIRECTORY); 150 mTraceDirectoryStr = args.getString(KEY_TRACE_DIRECTORY); 151 mDropCache = Boolean.parseBoolean(args.getString(KEY_DROP_CACHE)); 152 mSimplePerfCmd = args.getString(KEY_SIMPLEPPERF_CMD); 153 mLaunchOrder = args.getString(KEY_LAUNCH_ORDER, LAUNCH_ORDER_CYCLIC); 154 createMappings(); 155 parseArgs(args); 156 checkAccountSignIn(); 157 158 // Root directory for applaunch file to log the app launch output 159 // Will be useful in case of simpleperf command is used 160 File launchRootDir = null; 161 if (null != launchDirectory && !launchDirectory.isEmpty()) { 162 launchRootDir = new File(launchDirectory); 163 if (!launchRootDir.exists() && !launchRootDir.mkdirs()) { 164 throw new IOException("Unable to create the destination directory"); 165 } 166 } 167 168 try { 169 File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); 170 if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { 171 throw new IOException("Unable to create the lauch file sub directory"); 172 } 173 mFile = new File(launchSubDir, LAUNCH_FILE); 174 mOutputStream = new FileOutputStream(mFile); 175 mBufferedWriter = new BufferedWriter(new OutputStreamWriter( 176 mOutputStream)); 177 178 // Root directory for trace file during the launches 179 File rootTrace = null; 180 File rootTraceSubDir = null; 181 int traceBufferSize = 0; 182 int traceDumpInterval = 0; 183 Set<String> traceCategoriesSet = null; 184 if (null != mTraceDirectoryStr && !mTraceDirectoryStr.isEmpty()) { 185 rootTrace = new File(mTraceDirectoryStr); 186 if (!rootTrace.exists() && !rootTrace.mkdirs()) { 187 throw new IOException("Unable to create the trace directory"); 188 } 189 rootTraceSubDir = new File(rootTrace, TRACE_SUB_DIRECTORY); 190 if (!rootTraceSubDir.exists() && !rootTraceSubDir.mkdirs()) { 191 throw new IOException("Unable to create the trace sub directory"); 192 } 193 assertNotNull("Trace iteration parameter is mandatory", 194 args.getString(KEY_TRACE_ITERATIONS)); 195 mTraceLaunchCount = Integer.parseInt(args.getString(KEY_TRACE_ITERATIONS)); 196 String traceCategoriesStr = args 197 .getString(KEY_TRACE_CATEGORY, DEFAULT_TRACE_CATEGORIES); 198 traceBufferSize = Integer.parseInt(args.getString(KEY_TRACE_BUFFERSIZE, 199 DEFAULT_TRACE_BUFFER_SIZE)); 200 traceDumpInterval = Integer.parseInt(args.getString(KEY_TRACE_DUMPINTERVAL, 201 DEFAULT_TRACE_DUMP_INTERVAL)); 202 traceCategoriesSet = new HashSet<String>(); 203 if (!traceCategoriesStr.isEmpty()) { 204 String[] traceCategoriesSplit = traceCategoriesStr.split(DELIMITER); 205 for (int i = 0; i < traceCategoriesSplit.length; i++) { 206 traceCategoriesSet.add(traceCategoriesSplit[i]); 207 } 208 } 209 } 210 211 // Get the app launch order based on launch order, trial launch, 212 // launch iterations and trace iterations 213 setLaunchOrder(); 214 215 for (LaunchOrder launch : mLaunchOrderList) { 216 217 // App launch times for trial launch will not be used for final 218 // launch time calculations. 219 if (launch.getLaunchReason().equals(TRIAL_LAUNCH)) { 220 // In the "applaunch.txt" file, trail launches is referenced using 221 // "TRIAL_LAUNCH" 222 long launchTime = startApp(launch.getApp(), true, launch.getLaunchReason()); 223 if (launchTime < 0) { 224 List<Long> appLaunchList = new ArrayList<Long>(); 225 appLaunchList.add(-1L); 226 mNameToLaunchTime.put(launch.getApp(), appLaunchList); 227 // simply pass the app if launch isn't successful 228 // error should have already been logged by startApp 229 continue; 230 } 231 sleep(INITIAL_LAUNCH_IDLE_TIMEOUT); 232 closeApp(launch.getApp(), true); 233 dropCache(); 234 sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); 235 } 236 237 // App launch times used for final calculation 238 if (launch.getLaunchReason().contains(LAUNCH_ITERATION_PREFIX)) { 239 long launchTime = -1; 240 if (null != mNameToLaunchTime.get(launch.getApp())) { 241 long firstLaunchTime = mNameToLaunchTime.get(launch.getApp()).get(0); 242 if (firstLaunchTime < 0) { 243 // skip if the app has failures while launched first 244 continue; 245 } 246 } 247 // In the "applaunch.txt" file app launches are referenced using 248 // "LAUNCH_ITERATION - ITERATION NUM" 249 launchTime = startApp(launch.getApp(), true, launch.getLaunchReason()); 250 if (launchTime < 0) { 251 // if it fails once, skip the rest of the launches 252 List<Long> appLaunchList = new ArrayList<Long>(); 253 appLaunchList.add(-1L); 254 mNameToLaunchTime.put(launch.getApp(), appLaunchList); 255 continue; 256 } else { 257 if (null != mNameToLaunchTime.get(launch.getApp())) { 258 mNameToLaunchTime.get(launch.getApp()).add(launchTime); 259 } else { 260 List<Long> appLaunchList = new ArrayList<Long>(); 261 appLaunchList.add(launchTime); 262 mNameToLaunchTime.put(launch.getApp(), appLaunchList); 263 } 264 } 265 sleep(POST_LAUNCH_IDLE_TIMEOUT); 266 closeApp(launch.getApp(), true); 267 dropCache(); 268 sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); 269 } 270 271 // App launch times for trace launch will not be used for final 272 // launch time calculations. 273 if (launch.getLaunchReason().contains(TRACE_ITERATION_PREFIX)) { 274 AtraceLogger atraceLogger = AtraceLogger 275 .getAtraceLoggerInstance(getInstrumentation()); 276 // Start the trace 277 try { 278 atraceLogger.atraceStart(traceCategoriesSet, traceBufferSize, 279 traceDumpInterval, rootTraceSubDir, 280 String.format("%s-%s", launch.getApp(), launch.getLaunchReason())); 281 startApp(launch.getApp(), true, launch.getLaunchReason()); 282 sleep(POST_LAUNCH_IDLE_TIMEOUT); 283 } finally { 284 // Stop the trace 285 atraceLogger.atraceStop(); 286 closeApp(launch.getApp(), true); 287 dropCache(); 288 sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); 289 } 290 } 291 } 292 } finally { 293 if (null != mBufferedWriter) { 294 mBufferedWriter.close(); 295 } 296 } 297 298 for (String app : mNameToResultKey.keySet()) { 299 StringBuilder launchTimes = new StringBuilder(); 300 for (Long launch : mNameToLaunchTime.get(app)) { 301 launchTimes.append(launch); 302 launchTimes.append(","); 303 } 304 mResult.putString(mNameToResultKey.get(app), launchTimes.toString()); 305 } 306 instrumentation.sendStatus(0, mResult); 307 } 308 309 /** 310 * If launch order is "cyclic" then apps will be launched one after the 311 * other for each iteration count. 312 * If launch order is "sequential" then each app will be launched for given number 313 * iterations at once before launching the other apps. 314 */ 315 private void setLaunchOrder() { 316 if (LAUNCH_ORDER_CYCLIC.equalsIgnoreCase(mLaunchOrder)) { 317 if (mTrailLaunch) { 318 for (String app : mNameToResultKey.keySet()) { 319 mLaunchOrderList.add(new LaunchOrder(app, TRIAL_LAUNCH)); 320 } 321 } 322 for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) { 323 for (String app : mNameToResultKey.keySet()) { 324 mLaunchOrderList.add(new LaunchOrder(app, 325 String.format(LAUNCH_ITERATION, launchCount))); 326 } 327 } 328 if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) { 329 for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) { 330 for (String app : mNameToResultKey.keySet()) { 331 mLaunchOrderList.add(new LaunchOrder(app, 332 String.format(TRACE_ITERATION, traceCount))); 333 } 334 } 335 } 336 } else if (LAUNCH_ORDER_SEQUENTIAL.equalsIgnoreCase(mLaunchOrder)) { 337 for (String app : mNameToResultKey.keySet()) { 338 if (mTrailLaunch) { 339 mLaunchOrderList.add(new LaunchOrder(app, TRIAL_LAUNCH)); 340 } 341 for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) { 342 mLaunchOrderList.add(new LaunchOrder(app, 343 String.format(LAUNCH_ITERATION, launchCount))); 344 } 345 if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) { 346 for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) { 347 mLaunchOrderList.add(new LaunchOrder(app, 348 String.format(TRACE_ITERATION, traceCount))); 349 } 350 } 351 } 352 } else { 353 assertTrue("Launch order is not valid parameter", false); 354 } 355 } 356 357 private void dropCache() { 358 if (true == mDropCache) { 359 assertNotNull("Issue in dropping the cache", 360 getInstrumentation().getUiAutomation() 361 .executeShellCommand(DROP_CACHE_SCRIPT)); 362 } 363 } 364 365 private void parseArgs(Bundle args) { 366 mNameToResultKey = new LinkedHashMap<String, String>(); 367 mNameToLaunchTime = new HashMap<String, List<Long>>(); 368 String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS); 369 if (launchIterations != null) { 370 mLaunchIterations = Integer.parseInt(launchIterations); 371 } 372 String appList = args.getString(KEY_APPS); 373 if (appList == null) 374 return; 375 376 String appNames[] = appList.split("\\|"); 377 for (String pair : appNames) { 378 String[] parts = pair.split("\\^"); 379 if (parts.length != 2) { 380 Log.e(TAG, "The apps key is incorrectly formatted"); 381 fail(); 382 } 383 384 mNameToResultKey.put(parts[0], parts[1]); 385 mNameToLaunchTime.put(parts[0], null); 386 } 387 String requiredAccounts = args.getString(KEY_REQUIRED_ACCOUNTS); 388 if (requiredAccounts != null) { 389 mRequiredAccounts = new HashSet<String>(); 390 for (String accountType : requiredAccounts.split(",")) { 391 mRequiredAccounts.add(accountType); 392 } 393 } 394 mTrailLaunch = "true".equals(args.getString(KEY_TRIAL_LAUNCH)); 395 } 396 397 private boolean hasLeanback(Context context) { 398 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); 399 } 400 401 private void createMappings() { 402 mNameToIntent = new LinkedHashMap<String, Intent>(); 403 mNameToProcess = new LinkedHashMap<String, String>(); 404 405 PackageManager pm = getInstrumentation().getContext() 406 .getPackageManager(); 407 Intent intentToResolve = new Intent(Intent.ACTION_MAIN); 408 intentToResolve.addCategory(hasLeanback(getInstrumentation().getContext()) ? 409 Intent.CATEGORY_LEANBACK_LAUNCHER : 410 Intent.CATEGORY_LAUNCHER); 411 List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0); 412 resolveLoop(ris, intentToResolve, pm); 413 // For Wear 414 intentToResolve = new Intent(WEARABLE_ACTION_GOOGLE); 415 ris = pm.queryIntentActivities(intentToResolve, 0); 416 resolveLoop(ris, intentToResolve, pm); 417 } 418 419 private void resolveLoop(List<ResolveInfo> ris, Intent intentToResolve, PackageManager pm) { 420 if (ris == null || ris.isEmpty()) { 421 Log.i(TAG, "Could not find any apps"); 422 } else { 423 for (ResolveInfo ri : ris) { 424 Intent startIntent = new Intent(intentToResolve); 425 startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 426 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 427 startIntent.setClassName(ri.activityInfo.packageName, 428 ri.activityInfo.name); 429 String appName = ri.loadLabel(pm).toString(); 430 if (appName != null) { 431 mNameToIntent.put(appName, startIntent); 432 mNameToProcess.put(appName, ri.activityInfo.processName); 433 } 434 } 435 } 436 } 437 438 private long startApp(String appName, boolean forceStopBeforeLaunch, String launchReason) 439 throws NameNotFoundException, RemoteException { 440 Log.i(TAG, "Starting " + appName); 441 442 Intent startIntent = mNameToIntent.get(appName); 443 if (startIntent == null) { 444 Log.w(TAG, "App does not exist: " + appName); 445 mResult.putString(mNameToResultKey.get(appName), "App does not exist"); 446 return -1L; 447 } 448 AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, forceStopBeforeLaunch , 449 launchReason); 450 Thread t = new Thread(runnable); 451 t.start(); 452 try { 453 t.join(JOIN_TIMEOUT); 454 } catch (InterruptedException e) { 455 // ignore 456 } 457 return runnable.getResult(); 458 } 459 460 private void checkAccountSignIn() { 461 // ensure that the device has the required account types before starting test 462 // e.g. device must have a valid Google account sign in to measure a meaningful launch time 463 // for Gmail 464 if (mRequiredAccounts == null || mRequiredAccounts.isEmpty()) { 465 return; 466 } 467 final AccountManager am = 468 (AccountManager) getInstrumentation().getTargetContext().getSystemService( 469 Context.ACCOUNT_SERVICE); 470 Account[] accounts = am.getAccounts(); 471 // use set here in case device has multiple accounts of the same type 472 Set<String> foundAccounts = new HashSet<String>(); 473 for (Account account : accounts) { 474 if (mRequiredAccounts.contains(account.type)) { 475 foundAccounts.add(account.type); 476 } 477 } 478 // check if account type matches, if not, fail test with message on what account types 479 // are missing 480 if (mRequiredAccounts.size() != foundAccounts.size()) { 481 mRequiredAccounts.removeAll(foundAccounts); 482 StringBuilder sb = new StringBuilder("Device missing these accounts:"); 483 for (String account : mRequiredAccounts) { 484 sb.append(' '); 485 sb.append(account); 486 } 487 fail(sb.toString()); 488 } 489 } 490 491 private void closeApp(String appName, boolean forceStopApp) { 492 Intent homeIntent = new Intent(Intent.ACTION_MAIN); 493 homeIntent.addCategory(Intent.CATEGORY_HOME); 494 homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 495 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 496 getInstrumentation().getContext().startActivity(homeIntent); 497 sleep(POST_LAUNCH_IDLE_TIMEOUT); 498 if (forceStopApp) { 499 Intent startIntent = mNameToIntent.get(appName); 500 if (startIntent != null) { 501 String packageName = startIntent.getComponent().getPackageName(); 502 try { 503 mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); 504 } catch (RemoteException e) { 505 Log.w(TAG, "Error closing app", e); 506 } 507 } 508 } 509 } 510 511 private void sleep(int time) { 512 try { 513 Thread.sleep(time); 514 } catch (InterruptedException e) { 515 // ignore 516 } 517 } 518 519 private void reportError(String appName, String processName) { 520 ActivityManager am = (ActivityManager) getInstrumentation() 521 .getContext().getSystemService(Context.ACTIVITY_SERVICE); 522 List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState(); 523 if (crashes != null) { 524 for (ProcessErrorStateInfo crash : crashes) { 525 if (!crash.processName.equals(processName)) 526 continue; 527 528 Log.w(TAG, appName + " crashed: " + crash.shortMsg); 529 mResult.putString(mNameToResultKey.get(appName), crash.shortMsg); 530 return; 531 } 532 } 533 534 mResult.putString(mNameToResultKey.get(appName), 535 "Crashed for unknown reason"); 536 Log.w(TAG, appName 537 + " not found in process list, most likely it is crashed"); 538 } 539 540 private class LaunchOrder { 541 private String mApp; 542 private String mLaunchReason; 543 544 LaunchOrder(String app,String launchReason){ 545 mApp = app; 546 mLaunchReason = launchReason; 547 } 548 549 public String getApp() { 550 return mApp; 551 } 552 553 public void setApp(String app) { 554 mApp = app; 555 } 556 557 public String getLaunchReason() { 558 return mLaunchReason; 559 } 560 561 public void setLaunchReason(String launchReason) { 562 mLaunchReason = launchReason; 563 } 564 } 565 566 private class AppLaunchRunnable implements Runnable { 567 private Intent mLaunchIntent; 568 private Long mResult; 569 private boolean mForceStopBeforeLaunch; 570 private String mLaunchReason; 571 572 public AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch, 573 String launchReason) { 574 mLaunchIntent = intent; 575 mForceStopBeforeLaunch = forceStopBeforeLaunch; 576 mLaunchReason = launchReason; 577 mResult = -1L; 578 } 579 580 public Long getResult() { 581 return mResult; 582 } 583 584 public void run() { 585 try { 586 String packageName = mLaunchIntent.getComponent().getPackageName(); 587 String componentName = mLaunchIntent.getComponent().flattenToShortString(); 588 if (mForceStopBeforeLaunch) { 589 mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); 590 } 591 String launchCmd = String.format("%s %s", APP_LAUNCH_CMD, componentName); 592 if (null != mSimplePerfCmd) { 593 launchCmd = String.format("%s %s", mSimplePerfCmd, launchCmd); 594 } 595 Log.v(TAG, "Final launch cmd:" + launchCmd); 596 ParcelFileDescriptor parcelDesc = getInstrumentation().getUiAutomation() 597 .executeShellCommand(launchCmd); 598 mResult = Long.parseLong(parseLaunchTimeAndWrite(parcelDesc, String.format 599 ("App Launch :%s %s", 600 componentName, mLaunchReason)), 10); 601 } catch (RemoteException e) { 602 Log.w(TAG, "Error launching app", e); 603 } 604 } 605 606 /** 607 * Method to parse the launch time info and write the result to file 608 * 609 * @param parcelDesc 610 * @return 611 */ 612 private String parseLaunchTimeAndWrite(ParcelFileDescriptor parcelDesc, String headerInfo) { 613 String launchTime = "-1"; 614 boolean launchSuccess = false; 615 try { 616 InputStream inputStream = new FileInputStream(parcelDesc.getFileDescriptor()); 617 StringBuilder appLaunchOuput = new StringBuilder(); 618 /* SAMPLE OUTPUT : 619 Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator } 620 Status: ok 621 Activity: com.google.android.calculator/com.android.calculator2.Calculator 622 ThisTime: 357 623 TotalTime: 357 624 WaitTime: 377 625 Complete*/ 626 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( 627 inputStream)); 628 String line = null; 629 int lineCount = 1; 630 mBufferedWriter.newLine(); 631 mBufferedWriter.write(headerInfo); 632 mBufferedWriter.newLine(); 633 while ((line = bufferedReader.readLine()) != null) { 634 if (lineCount == 2 && line.contains(SUCCESS_MESSAGE)) { 635 launchSuccess = true; 636 } 637 if (launchSuccess && lineCount == 4) { 638 String launchSplit[] = line.split(":"); 639 launchTime = launchSplit[1].trim(); 640 } 641 mBufferedWriter.write(line); 642 mBufferedWriter.newLine(); 643 lineCount++; 644 } 645 mBufferedWriter.flush(); 646 inputStream.close(); 647 } catch (IOException e) { 648 Log.w(TAG, "Error writing the launch file", e); 649 } 650 return launchTime; 651 } 652 653 } 654} 655