AppLaunch.java revision 3a34d17412a5a304e39be1966a16627677d2136f
1/*
2 * Copyright (C) 2012 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.LinkedHashMap;
36import java.util.List;
37import java.util.Map;
38
39/**
40 * This test is intended to measure the time it takes for the apps to start.
41 * Names of the applications are passed in command line, and the
42 * test starts each application, and reports the start up time in milliseconds.
43 * The instrumentation expects the following key to be passed on the command line:
44 * apps - A list of applications to start and their corresponding result keys
45 * in the following format:
46 * -e apps <app name>^<result key>|<app name>^<result key>
47 */
48public class AppLaunch extends InstrumentationTestCase {
49
50    private static final int JOIN_TIMEOUT = 10000;
51    private static final String TAG = "AppLaunch";
52    private static final String KEY_APPS = "apps";
53
54    private Map<String, Intent> mNameToIntent;
55    private Map<String, String> mNameToProcess;
56    private Map<String, String> mNameToResultKey;
57
58    private IActivityManager mAm;
59
60    public void testMeasureStartUpTime() throws RemoteException {
61        InstrumentationTestRunner instrumentation =
62                (InstrumentationTestRunner)getInstrumentation();
63        Bundle args = instrumentation.getArguments();
64        mAm = ActivityManagerNative.getDefault();
65
66        createMappings();
67        parseArgs(args);
68
69        Bundle results = new Bundle();
70        for (String app : mNameToResultKey.keySet()) {
71            try {
72                startApp(app, results);
73                sleep(750);
74                closeApp(app);
75                sleep(2000);
76            } catch (NameNotFoundException e) {
77                Log.i(TAG, "Application " + app + " not found");
78            }
79
80        }
81        instrumentation.sendStatus(0, results);
82    }
83
84    private void parseArgs(Bundle args) {
85        mNameToResultKey = new LinkedHashMap<String, String>();
86        String appList = args.getString(KEY_APPS);
87
88        if (appList == null)
89            return;
90
91        String appNames[] = appList.split("\\|");
92        for (String pair : appNames) {
93            String[] parts = pair.split("\\^");
94            if (parts.length != 2) {
95                Log.e(TAG, "The apps key is incorectly formatted");
96                fail();
97            }
98
99            mNameToResultKey.put(parts[0], parts[1]);
100        }
101    }
102
103    private void createMappings() {
104        mNameToIntent = new LinkedHashMap<String, Intent>();
105        mNameToProcess = new LinkedHashMap<String, String>();
106
107        PackageManager pm = getInstrumentation().getContext()
108                .getPackageManager();
109        Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
110        intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
111        List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0);
112        if (ris == null || ris.isEmpty()) {
113            Log.i(TAG, "Could not find any apps");
114        } else {
115            for (ResolveInfo ri : ris) {
116                Intent startIntent = new Intent(intentToResolve);
117                startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
118                        | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
119                startIntent.setClassName(ri.activityInfo.packageName,
120                        ri.activityInfo.name);
121                mNameToIntent.put(ri.loadLabel(pm).toString(), startIntent);
122                mNameToProcess.put(ri.loadLabel(pm).toString(),
123                        ri.activityInfo.processName);
124            }
125        }
126    }
127
128    private void startApp(String appName, Bundle results)
129            throws NameNotFoundException, RemoteException {
130        Log.i(TAG, "Starting " + appName);
131
132        Intent startIntent = mNameToIntent.get(appName);
133        if (startIntent == null) {
134            Log.w(TAG, "App does not exist: " + appName);
135            return;
136        }
137        AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent);
138        Thread t = new Thread(runnable);
139        t.start();
140        try {
141            t.join(JOIN_TIMEOUT);
142        } catch (InterruptedException e) {
143            // ignore
144        }
145        WaitResult result = runnable.getResult();
146        if(t.isAlive() || (result != null && result.result != ActivityManager.START_SUCCESS)) {
147            Log.w(TAG, "Assuming app " + appName + " crashed.");
148            reportError(appName, mNameToProcess.get(appName), results);
149            return;
150        }
151        results.putString(mNameToResultKey.get(appName), String.valueOf(result.thisTime));
152    }
153
154    private void closeApp(String appName) {
155        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
156        homeIntent.addCategory(Intent.CATEGORY_HOME);
157        homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
158                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
159        getInstrumentation().getContext().startActivity(homeIntent);
160        Intent startIntent = mNameToIntent.get(appName);
161        if (startIntent != null) {
162            String packageName = startIntent.getComponent().getPackageName();
163            try {
164                mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
165            } catch (RemoteException e) {
166                Log.w(TAG, "Error closing app", e);
167            }
168        }
169    }
170
171    private void sleep(int time) {
172        try {
173            Thread.sleep(time);
174        } catch (InterruptedException e) {
175            // ignore
176        }
177    }
178
179    private void reportError(String appName, String processName, Bundle results) {
180        ActivityManager am = (ActivityManager) getInstrumentation()
181                .getContext().getSystemService(Context.ACTIVITY_SERVICE);
182        List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState();
183        if (crashes != null) {
184            for (ProcessErrorStateInfo crash : crashes) {
185                if (!crash.processName.equals(processName))
186                    continue;
187
188                Log.w(TAG, appName + " crashed: " + crash.shortMsg);
189                results.putString(mNameToResultKey.get(appName), crash.shortMsg);
190                return;
191            }
192        }
193
194        results.putString(mNameToResultKey.get(appName),
195                "Crashed for unknown reason");
196        Log.w(TAG, appName
197                + " not found in process list, most likely it is crashed");
198    }
199
200    private class AppLaunchRunnable implements Runnable {
201        private Intent mLaunchIntent;
202        private IActivityManager.WaitResult mResult;
203        public AppLaunchRunnable(Intent intent) {
204            mLaunchIntent = intent;
205        }
206
207        public IActivityManager.WaitResult getResult() {
208            return mResult;
209        }
210
211        public void run() {
212            try {
213                String packageName = mLaunchIntent.getComponent().getPackageName();
214                mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
215                String mimeType = mLaunchIntent.getType();
216                if (mimeType == null && mLaunchIntent.getData() != null
217                        && "content".equals(mLaunchIntent.getData().getScheme())) {
218                    mimeType = mAm.getProviderMimeType(mLaunchIntent.getData(),
219                            UserHandle.USER_CURRENT);
220                }
221
222                mResult = mAm.startActivityAndWait(null, mLaunchIntent, mimeType,
223                        null, null, 0, mLaunchIntent.getFlags(), null, null, null,
224                        UserHandle.USER_CURRENT);
225            } catch (RemoteException e) {
226                Log.w(TAG, "Error launching app", e);
227            }
228        }
229    }
230}
231