Skip to content

Commit f58e5b6

Browse files
author
Maxim Siniavine
committed
Added app launch test.
The test will start each app and report the time it takes for the app to start in milliseconds. Change-Id: I974ac36f70f0d982aa01e46824fe138eb641d680
1 parent 89ac38b commit f58e5b6

File tree

3 files changed

+246
-0
lines changed

3 files changed

+246
-0
lines changed

tests/AppLaunch/Android.mk

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
LOCAL_PATH:= $(call my-dir)
2+
include $(CLEAR_VARS)
3+
4+
LOCAL_MODULE_TAGS := tests
5+
6+
# Only compile source java files in this apk.
7+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
8+
9+
LOCAL_PACKAGE_NAME := AppLaunch
10+
11+
LOCAL_CERTIFICATE := platform
12+
LOCAL_JAVA_LIBRARIES := android.test.runner
13+
14+
include $(BUILD_PACKAGE)
15+
16+
# Use the following include to make our test apk.
17+
include $(call all-makefiles-under,$(LOCAL_PATH))
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
4+
package="com.android.tests.applaunch"
5+
android:sharedUserId="android.uid.system" >
6+
<instrumentation android:label="Measure app start up time"
7+
android:name="android.test.InstrumentationTestRunner"
8+
android:targetPackage="com.android.tests.applaunch" />
9+
10+
<application android:label="App Launch Test">
11+
<uses-library android:name="android.test.runner" />
12+
</application>
13+
</manifest>
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
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+
*/
16+
package com.android.tests.applaunch;
17+
18+
import android.app.ActivityManager;
19+
import android.app.ActivityManager.ProcessErrorStateInfo;
20+
import android.app.ActivityManagerNative;
21+
import android.app.IActivityManager;
22+
import android.content.Context;
23+
import android.content.Intent;
24+
import android.content.pm.PackageManager;
25+
import android.content.pm.PackageManager.NameNotFoundException;
26+
import android.content.pm.ResolveInfo;
27+
import android.os.Bundle;
28+
import android.os.RemoteException;
29+
import android.os.UserHandle;
30+
import android.test.InstrumentationTestCase;
31+
import android.test.InstrumentationTestRunner;
32+
import android.util.Log;
33+
34+
import java.util.HashMap;
35+
import java.util.List;
36+
import java.util.Map;
37+
38+
/**
39+
* This test is intended to measure the time it takes for the apps to start.
40+
* Names of the applications are passed in command line, and the
41+
* test starts each application, and reports the start up time in milliseconds.
42+
* The instrumentation expects the following key to be passed on the command line:
43+
* apps - A list of applications to start and their corresponding result keys
44+
* in the following format:
45+
* -e apps <app name>^<result key>|<app name>^<result key>
46+
*/
47+
public class AppLaunch extends InstrumentationTestCase {
48+
49+
private static final int JOIN_TIMEOUT = 10000;
50+
private static final String TAG = "AppLaunch";
51+
private static final String KEY_APPS = "apps";
52+
53+
private Map<String, Intent> mNameToIntent;
54+
private Map<String, String> mNameToProcess;
55+
private Map<String, String> mNameToResultKey;
56+
57+
private IActivityManager mAm;
58+
59+
public void testMeasureStartUpTime() throws RemoteException {
60+
InstrumentationTestRunner instrumentation =
61+
(InstrumentationTestRunner)getInstrumentation();
62+
Bundle args = instrumentation.getBundle();
63+
mAm = ActivityManagerNative.getDefault();
64+
65+
createMappings();
66+
parseArgs(args);
67+
68+
Bundle results = new Bundle();
69+
for (String app : mNameToResultKey.keySet()) {
70+
try {
71+
startApp(app, results);
72+
closeApp();
73+
} catch (NameNotFoundException e) {
74+
Log.i(TAG, "Application " + app + " not found");
75+
}
76+
77+
}
78+
instrumentation.sendStatus(0, results);
79+
}
80+
81+
private void parseArgs(Bundle args) {
82+
mNameToResultKey = new HashMap<String, String>();
83+
String appList = args.getString(KEY_APPS);
84+
85+
if (appList == null)
86+
return;
87+
88+
String appNames[] = appList.split("\\|");
89+
for (String pair : appNames) {
90+
String[] parts = pair.split("\\^");
91+
if (parts.length != 2) {
92+
Log.e(TAG, "The apps key is incorectly formatted");
93+
fail();
94+
}
95+
96+
mNameToResultKey.put(parts[0], parts[1]);
97+
}
98+
}
99+
100+
private void createMappings() {
101+
mNameToIntent = new HashMap<String, Intent>();
102+
mNameToProcess = new HashMap<String, String>();
103+
104+
PackageManager pm = getInstrumentation().getContext()
105+
.getPackageManager();
106+
Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
107+
intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
108+
List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0);
109+
if (ris == null || ris.isEmpty()) {
110+
Log.i(TAG, "Could not find any apps");
111+
} else {
112+
for (ResolveInfo ri : ris) {
113+
Intent startIntent = new Intent(intentToResolve);
114+
startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
115+
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
116+
startIntent.setClassName(ri.activityInfo.packageName,
117+
ri.activityInfo.name);
118+
mNameToIntent.put(ri.loadLabel(pm).toString(), startIntent);
119+
mNameToProcess.put(ri.loadLabel(pm).toString(),
120+
ri.activityInfo.processName);
121+
}
122+
}
123+
}
124+
125+
private void startApp(String appName, Bundle results)
126+
throws NameNotFoundException, RemoteException {
127+
Log.i(TAG, "Starting " + appName);
128+
129+
Intent startIntent = mNameToIntent.get(appName);
130+
AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent);
131+
Thread t = new Thread(runnable);
132+
long startTime = System.currentTimeMillis();
133+
t.start();
134+
try {
135+
t.join(JOIN_TIMEOUT);
136+
} catch (InterruptedException e) {
137+
// ignore
138+
}
139+
if(t.isAlive() || (runnable.getResult() != null &&
140+
runnable.getResult().result != ActivityManager.START_SUCCESS)) {
141+
Log.w(TAG, "Assuming app " + appName + " crashed.");
142+
reportError(appName, mNameToProcess.get(appName), results);
143+
return;
144+
}
145+
long startUpTime = System.currentTimeMillis() - startTime;
146+
results.putString(mNameToResultKey.get(appName), String.valueOf(startUpTime));
147+
sleep(5000);
148+
}
149+
150+
private void closeApp() {
151+
Intent homeIntent = new Intent(Intent.ACTION_MAIN);
152+
homeIntent.addCategory(Intent.CATEGORY_HOME);
153+
homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
154+
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
155+
getInstrumentation().getContext().startActivity(homeIntent);
156+
sleep(3000);
157+
}
158+
159+
private void sleep(int time) {
160+
try {
161+
Thread.sleep(time);
162+
} catch (InterruptedException e) {
163+
// ignore
164+
}
165+
}
166+
167+
private void reportError(String appName, String processName, Bundle results) {
168+
ActivityManager am = (ActivityManager) getInstrumentation()
169+
.getContext().getSystemService(Context.ACTIVITY_SERVICE);
170+
List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState();
171+
if (crashes != null) {
172+
for (ProcessErrorStateInfo crash : crashes) {
173+
if (!crash.processName.equals(processName))
174+
continue;
175+
176+
Log.w(TAG, appName + " crashed: " + crash.shortMsg);
177+
results.putString(mNameToResultKey.get(appName), crash.shortMsg);
178+
return;
179+
}
180+
}
181+
182+
results.putString(mNameToResultKey.get(appName),
183+
"Crashed for unknown reason");
184+
Log.w(TAG, appName
185+
+ " not found in process list, most likely it is crashed");
186+
}
187+
188+
private class AppLaunchRunnable implements Runnable {
189+
private Intent mLaunchIntent;
190+
private IActivityManager.WaitResult mResult;
191+
public AppLaunchRunnable(Intent intent) {
192+
mLaunchIntent = intent;
193+
}
194+
195+
public IActivityManager.WaitResult getResult() {
196+
return mResult;
197+
}
198+
199+
public void run() {
200+
try {
201+
String mimeType = mLaunchIntent.getType();
202+
if (mimeType == null && mLaunchIntent.getData() != null
203+
&& "content".equals(mLaunchIntent.getData().getScheme())) {
204+
mimeType = mAm.getProviderMimeType(mLaunchIntent.getData(),
205+
UserHandle.USER_CURRENT);
206+
}
207+
208+
mResult = mAm.startActivityAndWait(null, mLaunchIntent, mimeType,
209+
null, null, 0, mLaunchIntent.getFlags(), null, null, null,
210+
UserHandle.USER_CURRENT);
211+
} catch (RemoteException e) {
212+
Log.w(TAG, "Error launching app", e);
213+
}
214+
}
215+
}
216+
}

0 commit comments

Comments
 (0)