• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
16 package com.android.tests.applaunch;
17 
18 import java.io.OutputStreamWriter;
19 
20 import android.accounts.Account;
21 import android.accounts.AccountManager;
22 import android.app.ActivityManagerNative;
23 import android.app.ActivityManager;
24 import android.app.ActivityManager.ProcessErrorStateInfo;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.PackageManager;
28 import android.content.pm.PackageManager.NameNotFoundException;
29 import android.content.pm.ResolveInfo;
30 import android.os.Bundle;
31 import android.os.RemoteException;
32 import android.os.UserHandle;
33 import android.app.UiAutomation;
34 import android.app.IActivityManager;
35 import android.app.IActivityManager.WaitResult;
36 import android.support.test.rule.logging.AtraceLogger;
37 import android.test.InstrumentationTestCase;
38 import android.test.InstrumentationTestRunner;
39 import android.util.Log;
40 import java.io.File;
41 import java.io.IOException;
42 import java.util.HashMap;
43 import java.util.HashSet;
44 import java.util.LinkedHashMap;
45 import java.util.List;
46 import java.util.ArrayList;
47 import java.util.Map;
48 import java.util.Set;
49 import android.os.ParcelFileDescriptor;
50 import java.io.FileInputStream;
51 import java.io.FileOutputStream;
52 import java.io.InputStream;
53 import java.io.BufferedReader;
54 import java.io.BufferedWriter;
55 import 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  */
66 public 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
setUp()132     protected void setUp() throws Exception {
133         super.setUp();
134         getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0);
135     }
136 
137     @Override
tearDown()138     protected void tearDown() throws Exception {
139         getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
140         super.tearDown();
141     }
142 
testMeasureStartUpTime()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      */
setLaunchOrder()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 
dropCache()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 
parseArgs(Bundle args)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 
hasLeanback(Context context)397     private boolean hasLeanback(Context context) {
398         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
399     }
400 
createMappings()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 
resolveLoop(List<ResolveInfo> ris, Intent intentToResolve, PackageManager pm)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 
startApp(String appName, boolean forceStopBeforeLaunch, String launchReason)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 
checkAccountSignIn()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 
closeApp(String appName, boolean forceStopApp)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 
sleep(int time)511     private void sleep(int time) {
512         try {
513             Thread.sleep(time);
514         } catch (InterruptedException e) {
515             // ignore
516         }
517     }
518 
reportError(String appName, String processName)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 
LaunchOrder(String app,String launchReason)544         LaunchOrder(String app,String launchReason){
545             mApp = app;
546             mLaunchReason = launchReason;
547         }
548 
getApp()549         public String getApp() {
550             return mApp;
551         }
552 
setApp(String app)553         public void setApp(String app) {
554             mApp = app;
555         }
556 
getLaunchReason()557         public String getLaunchReason() {
558             return mLaunchReason;
559         }
560 
setLaunchReason(String launchReason)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 
AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch, String launchReason)572         public AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch,
573                 String launchReason) {
574             mLaunchIntent = intent;
575             mForceStopBeforeLaunch = forceStopBeforeLaunch;
576             mLaunchReason = launchReason;
577             mResult = -1L;
578         }
579 
getResult()580         public Long getResult() {
581             return mResult;
582         }
583 
run()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          */
parseLaunchTimeAndWrite(ParcelFileDescriptor parcelDesc, String headerInfo)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