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