2 * Copyright (C) 2013 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.android.tests.applaunch;
18 import android.accounts.Account;
19 import android.accounts.AccountManager;
20 import android.app.ActivityManager;
21 import android.app.ActivityManager.ProcessErrorStateInfo;
22 import android.app.ActivityManagerNative;
23 import android.app.IActivityManager;
24 import android.app.IActivityManager.WaitResult;
25 import android.app.UiAutomation;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.PackageManager;
29 import android.content.pm.PackageManager.NameNotFoundException;
30 import android.content.pm.ResolveInfo;
31 import android.os.Bundle;
32 import android.os.RemoteException;
33 import android.os.UserHandle;
34 import android.test.InstrumentationTestCase;
35 import android.test.InstrumentationTestRunner;
36 import android.util.Log;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.LinkedHashMap;
41 import java.util.List;
46 * This test is intended to measure the time it takes for the apps to start.
47 * Names of the applications are passed in command line, and the
48 * test starts each application, and reports the start up time in milliseconds.
49 * The instrumentation expects the following key to be passed on the command line:
50 * apps - A list of applications to start and their corresponding result keys
51 * in the following format:
52 * -e apps <app name>^<result key>|<app name>^<result key>
54 public class AppLaunch extends InstrumentationTestCase {
56 private static final int JOIN_TIMEOUT = 10000;
57 private static final String TAG = AppLaunch.class.getSimpleName();
58 private static final String KEY_APPS = "apps";
59 private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations";
60 // optional parameter: comma separated list of required account types before proceeding
61 // with the app launch
62 private static final String KEY_REQUIRED_ACCOUNTS = "required_accounts";
63 private static final String WEARABLE_ACTION_GOOGLE =
64 "com.google.android.wearable.action.GOOGLE";
65 private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 7500; //7.5s to allow app to idle
66 private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; //750ms idle for non initial launches
67 private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 2000; //2s between launching apps
69 private Map<String, Intent> mNameToIntent;
70 private Map<String, String> mNameToProcess;
71 private Map<String, String> mNameToResultKey;
72 private Map<String, Long> mNameToLaunchTime;
73 private IActivityManager mAm;
74 private int mLaunchIterations = 10;
75 private Bundle mResult = new Bundle();
76 private Set<String> mRequiredAccounts;
79 protected void setUp() throws Exception {
81 getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0);
85 protected void tearDown() throws Exception {
86 getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
90 public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException {
91 InstrumentationTestRunner instrumentation =
92 (InstrumentationTestRunner)getInstrumentation();
93 Bundle args = instrumentation.getArguments();
94 mAm = ActivityManagerNative.getDefault();
100 // do initial app launch, without force stopping
101 for (String app : mNameToResultKey.keySet()) {
102 long launchTime = startApp(app, false);
103 if (launchTime <= 0) {
104 mNameToLaunchTime.put(app, -1L);
105 // simply pass the app if launch isn't successful
106 // error should have already been logged by startApp
109 mNameToLaunchTime.put(app, launchTime);
111 sleep(INITIAL_LAUNCH_IDLE_TIMEOUT);
112 closeApp(app, false);
113 sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT);
115 // do the real app launch now
116 for (int i = 0; i < mLaunchIterations; i++) {
117 for (String app : mNameToResultKey.keySet()) {
118 long prevLaunchTime = mNameToLaunchTime.get(app);
120 if (prevLaunchTime < 0) {
121 // skip if the app has previous failures
124 launchTime = startApp(app, true);
125 if (launchTime <= 0) {
126 // if it fails once, skip the rest of the launches
127 mNameToLaunchTime.put(app, -1L);
130 // keep the min launch time
131 if (launchTime < prevLaunchTime) {
132 mNameToLaunchTime.put(app, launchTime);
134 sleep(POST_LAUNCH_IDLE_TIMEOUT);
136 sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT);
139 for (String app : mNameToResultKey.keySet()) {
140 long launchTime = mNameToLaunchTime.get(app);
141 if (launchTime != -1) {
142 mResult.putLong(mNameToResultKey.get(app), launchTime);
145 instrumentation.sendStatus(0, mResult);
148 private void parseArgs(Bundle args) {
149 mNameToResultKey = new LinkedHashMap<String, String>();
150 mNameToLaunchTime = new HashMap<String, Long>();
151 String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS);
152 if (launchIterations != null) {
153 mLaunchIterations = Integer.parseInt(launchIterations);
155 String appList = args.getString(KEY_APPS);
159 String appNames[] = appList.split("\\|");
160 for (String pair : appNames) {
161 String[] parts = pair.split("\\^");
162 if (parts.length != 2) {
163 Log.e(TAG, "The apps key is incorrectly formatted");
167 mNameToResultKey.put(parts[0], parts[1]);
168 mNameToLaunchTime.put(parts[0], 0L);
170 String requiredAccounts = args.getString(KEY_REQUIRED_ACCOUNTS);
171 if (requiredAccounts != null) {
172 mRequiredAccounts = new HashSet<String>();
173 for (String accountType : requiredAccounts.split(",")) {
174 mRequiredAccounts.add(accountType);
179 private boolean hasLeanback(Context context) {
180 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
183 private void createMappings() {
184 mNameToIntent = new LinkedHashMap<String, Intent>();
185 mNameToProcess = new LinkedHashMap<String, String>();
187 PackageManager pm = getInstrumentation().getContext()
188 .getPackageManager();
189 Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
190 intentToResolve.addCategory(hasLeanback(getInstrumentation().getContext()) ?
191 Intent.CATEGORY_LEANBACK_LAUNCHER :
192 Intent.CATEGORY_LAUNCHER);
193 List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0);
194 resolveLoop(ris, intentToResolve, pm);
196 intentToResolve = new Intent(WEARABLE_ACTION_GOOGLE);
197 ris = pm.queryIntentActivities(intentToResolve, 0);
198 resolveLoop(ris, intentToResolve, pm);
201 private void resolveLoop(List<ResolveInfo> ris, Intent intentToResolve, PackageManager pm) {
202 if (ris == null || ris.isEmpty()) {
203 Log.i(TAG, "Could not find any apps");
205 for (ResolveInfo ri : ris) {
206 Intent startIntent = new Intent(intentToResolve);
207 startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
208 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
209 startIntent.setClassName(ri.activityInfo.packageName,
210 ri.activityInfo.name);
211 String appName = ri.loadLabel(pm).toString();
212 if (appName != null) {
213 mNameToIntent.put(appName, startIntent);
214 mNameToProcess.put(appName, ri.activityInfo.processName);
220 private long startApp(String appName, boolean forceStopBeforeLaunch)
221 throws NameNotFoundException, RemoteException {
222 Log.i(TAG, "Starting " + appName);
224 Intent startIntent = mNameToIntent.get(appName);
225 if (startIntent == null) {
226 Log.w(TAG, "App does not exist: " + appName);
227 mResult.putString(mNameToResultKey.get(appName), "App does not exist");
230 AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, forceStopBeforeLaunch);
231 Thread t = new Thread(runnable);
234 t.join(JOIN_TIMEOUT);
235 } catch (InterruptedException e) {
238 WaitResult result = runnable.getResult();
239 // report error if any of the following is true:
240 // * launch thread is alive
241 // * result is not null, but:
242 // * result is not START_SUCCESS
243 // * or in case of no force stop, result is not TASK_TO_FRONT either
244 if (t.isAlive() || (result != null
245 && ((result.result != ActivityManager.START_SUCCESS)
246 && (!forceStopBeforeLaunch
247 && result.result != ActivityManager.START_TASK_TO_FRONT)))) {
248 Log.w(TAG, "Assuming app " + appName + " crashed.");
249 reportError(appName, mNameToProcess.get(appName));
252 return result.thisTime;
255 private void checkAccountSignIn() {
256 // ensure that the device has the required account types before starting test
257 // e.g. device must have a valid Google account sign in to measure a meaningful launch time
259 if (mRequiredAccounts == null || mRequiredAccounts.isEmpty()) {
262 final AccountManager am =
263 (AccountManager) getInstrumentation().getTargetContext().getSystemService(
264 Context.ACCOUNT_SERVICE);
265 Account[] accounts = am.getAccounts();
266 // use set here in case device has multiple accounts of the same type
267 Set<String> foundAccounts = new HashSet<String>();
268 for (Account account : accounts) {
269 if (mRequiredAccounts.contains(account.type)) {
270 foundAccounts.add(account.type);
273 // check if account type matches, if not, fail test with message on what account types
275 if (mRequiredAccounts.size() != foundAccounts.size()) {
276 mRequiredAccounts.removeAll(foundAccounts);
277 StringBuilder sb = new StringBuilder("Device missing these accounts:");
278 for (String account : mRequiredAccounts) {
286 private void closeApp(String appName, boolean forceStopApp) {
287 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
288 homeIntent.addCategory(Intent.CATEGORY_HOME);
289 homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
290 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
291 getInstrumentation().getContext().startActivity(homeIntent);
292 sleep(POST_LAUNCH_IDLE_TIMEOUT);
294 Intent startIntent = mNameToIntent.get(appName);
295 if (startIntent != null) {
296 String packageName = startIntent.getComponent().getPackageName();
298 mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
299 } catch (RemoteException e) {
300 Log.w(TAG, "Error closing app", e);
306 private void sleep(int time) {
309 } catch (InterruptedException e) {
314 private void reportError(String appName, String processName) {
315 ActivityManager am = (ActivityManager) getInstrumentation()
316 .getContext().getSystemService(Context.ACTIVITY_SERVICE);
317 List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState();
318 if (crashes != null) {
319 for (ProcessErrorStateInfo crash : crashes) {
320 if (!crash.processName.equals(processName))
323 Log.w(TAG, appName + " crashed: " + crash.shortMsg);
324 mResult.putString(mNameToResultKey.get(appName), crash.shortMsg);
329 mResult.putString(mNameToResultKey.get(appName),
330 "Crashed for unknown reason");
332 + " not found in process list, most likely it is crashed");
335 private class AppLaunchRunnable implements Runnable {
336 private Intent mLaunchIntent;
337 private IActivityManager.WaitResult mResult;
338 private boolean mForceStopBeforeLaunch;
340 public AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch) {
341 mLaunchIntent = intent;
342 mForceStopBeforeLaunch = forceStopBeforeLaunch;
345 public IActivityManager.WaitResult getResult() {
351 String packageName = mLaunchIntent.getComponent().getPackageName();
352 if (mForceStopBeforeLaunch) {
353 mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
355 String mimeType = mLaunchIntent.getType();
356 if (mimeType == null && mLaunchIntent.getData() != null
357 && "content".equals(mLaunchIntent.getData().getScheme())) {
358 mimeType = mAm.getProviderMimeType(mLaunchIntent.getData(),
359 UserHandle.USER_CURRENT);
362 mResult = mAm.startActivityAndWait(null, null, mLaunchIntent, mimeType,
363 null, null, 0, mLaunchIntent.getFlags(), null, null,
364 UserHandle.USER_CURRENT);
365 } catch (RemoteException e) {
366 Log.w(TAG, "Error launching app", e);