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