blob: d683ec746a612b1cbf707a05ce0b50e0ef1cfb9a [file] [log] [blame]
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -07001/*
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
17package com.android.compatibilitytest;
18
19import android.app.ActivityManager;
Guang Zhu3bd71872017-10-25 17:04:25 -070020import android.app.ActivityManager.ProcessErrorStateInfo;
Guang Zhua5fe0de2017-09-28 18:11:19 -070021import android.app.ActivityManager.RunningTaskInfo;
22import android.app.IActivityController;
23import android.app.IActivityManager;
24import android.app.Instrumentation;
Guang Zhu6ac0c8c2015-05-28 13:22:28 -070025import android.app.UiAutomation;
Guang Zhu4f486ee2015-05-26 13:54:34 -070026import android.app.UiModeManager;
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -070027import android.content.Context;
28import android.content.Intent;
29import android.content.pm.PackageManager;
Guang Zhua5fe0de2017-09-28 18:11:19 -070030import android.content.pm.ResolveInfo;
Guang Zhu4f486ee2015-05-26 13:54:34 -070031import android.content.res.Configuration;
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -070032import android.os.Bundle;
Guang Zhua5fe0de2017-09-28 18:11:19 -070033import android.os.DropBoxManager;
34import android.os.RemoteException;
35import android.os.ServiceManager;
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -070036import android.util.Log;
37
Brett Chabot502ec7a2019-03-01 14:43:20 -080038import androidx.test.InstrumentationRegistry;
39import androidx.test.runner.AndroidJUnit4;
40
Guang Zhua5fe0de2017-09-28 18:11:19 -070041import org.junit.After;
42import org.junit.Assert;
43import org.junit.Before;
44import org.junit.Test;
45import org.junit.runner.RunWith;
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -070046
Guang Zhua5fe0de2017-09-28 18:11:19 -070047import java.util.ArrayList;
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -070048import java.util.Collection;
Guang Zhua5fe0de2017-09-28 18:11:19 -070049import java.util.HashMap;
50import java.util.HashSet;
Tsu Chiang Chuang14c716b2013-04-30 15:58:04 -070051import java.util.List;
Guang Zhua5fe0de2017-09-28 18:11:19 -070052import java.util.Map;
53import java.util.Set;
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -070054
55/**
Tsu Chiang Chuang14c716b2013-04-30 15:58:04 -070056 * Application Compatibility Test that launches an application and detects
57 * crashes.
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -070058 */
Guang Zhua5fe0de2017-09-28 18:11:19 -070059@RunWith(AndroidJUnit4.class)
60public class AppCompatibility {
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -070061
Guang Zhu9f521c92015-05-25 12:09:12 -070062 private static final String TAG = AppCompatibility.class.getSimpleName();
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -070063 private static final String PACKAGE_TO_LAUNCH = "package_to_launch";
64 private static final String APP_LAUNCH_TIMEOUT_MSECS = "app_launch_timeout_ms";
65 private static final String WORKSPACE_LAUNCH_TIMEOUT_MSECS = "workspace_launch_timeout_ms";
Guang Zhua5fe0de2017-09-28 18:11:19 -070066 private static final Set<String> DROPBOX_TAGS = new HashSet<>();
67 static {
68 DROPBOX_TAGS.add("SYSTEM_TOMBSTONE");
69 DROPBOX_TAGS.add("system_app_anr");
70 DROPBOX_TAGS.add("system_app_native_crash");
71 DROPBOX_TAGS.add("system_app_crash");
72 DROPBOX_TAGS.add("data_app_anr");
73 DROPBOX_TAGS.add("data_app_native_crash");
74 DROPBOX_TAGS.add("data_app_crash");
75 }
Guang Zhu3bd71872017-10-25 17:04:25 -070076 private static final int MAX_CRASH_SNIPPET_LINES = 20;
77 private static final int MAX_NUM_CRASH_SNIPPET = 3;
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -070078
Guang Zhua5fe0de2017-09-28 18:11:19 -070079 // time waiting for app to launch
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -070080 private int mAppLaunchTimeout = 7000;
Guang Zhua5fe0de2017-09-28 18:11:19 -070081 // time waiting for launcher home screen to show up
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -070082 private int mWorkspaceLaunchTimeout = 2000;
83
84 private Context mContext;
85 private ActivityManager mActivityManager;
86 private PackageManager mPackageManager;
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -070087 private Bundle mArgs;
Guang Zhua5fe0de2017-09-28 18:11:19 -070088 private Instrumentation mInstrumentation;
89 private String mLauncherPackageName;
90 private IActivityController mCrashSupressor = new CrashSuppressor();
91 private Map<String, List<String>> mAppErrors = new HashMap<>();
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -070092
Guang Zhua5fe0de2017-09-28 18:11:19 -070093 @Before
Tsu Chiang Chuang14c716b2013-04-30 15:58:04 -070094 public void setUp() throws Exception {
Guang Zhua5fe0de2017-09-28 18:11:19 -070095 mInstrumentation = InstrumentationRegistry.getInstrumentation();
96 mContext = InstrumentationRegistry.getTargetContext();
97 mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -070098 mPackageManager = mContext.getPackageManager();
Guang Zhua5fe0de2017-09-28 18:11:19 -070099 mArgs = InstrumentationRegistry.getArguments();
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700100
Guang Zhua5fe0de2017-09-28 18:11:19 -0700101 // resolve launcher package name
102 Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME);
103 ResolveInfo resolveInfo = mPackageManager.resolveActivity(
104 intent, PackageManager.MATCH_DEFAULT_ONLY);
105 mLauncherPackageName = resolveInfo.activityInfo.packageName;
106 Assert.assertNotNull("failed to resolve package name for launcher", mLauncherPackageName);
107 Log.v(TAG, "Using launcher package name: " + mLauncherPackageName);
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700108
109 // Parse optional inputs.
110 String appLaunchTimeoutMsecs = mArgs.getString(APP_LAUNCH_TIMEOUT_MSECS);
111 if (appLaunchTimeoutMsecs != null) {
112 mAppLaunchTimeout = Integer.parseInt(appLaunchTimeoutMsecs);
113 }
114 String workspaceLaunchTimeoutMsecs = mArgs.getString(WORKSPACE_LAUNCH_TIMEOUT_MSECS);
115 if (workspaceLaunchTimeoutMsecs != null) {
116 mWorkspaceLaunchTimeout = Integer.parseInt(workspaceLaunchTimeoutMsecs);
117 }
Guang Zhua5fe0de2017-09-28 18:11:19 -0700118 mInstrumentation.getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0);
119
120 // set activity controller to suppress crash dialogs and collects them by process name
121 mAppErrors.clear();
122 IActivityManager.Stub.asInterface(ServiceManager.checkService(Context.ACTIVITY_SERVICE))
123 .setActivityController(mCrashSupressor, false);
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700124 }
125
Guang Zhua5fe0de2017-09-28 18:11:19 -0700126 @After
127 public void tearDown() throws Exception {
128 // unset activity controller
129 IActivityManager.Stub.asInterface(ServiceManager.checkService(Context.ACTIVITY_SERVICE))
130 .setActivityController(null, false);
131 mInstrumentation.getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700132 }
133
134 /**
Tsu Chiang Chuang14c716b2013-04-30 15:58:04 -0700135 * Actual test case that launches the package and throws an exception on the
136 * first error.
137 *
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700138 * @throws Exception
139 */
Guang Zhua5fe0de2017-09-28 18:11:19 -0700140 @Test
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700141 public void testAppStability() throws Exception {
142 String packageName = mArgs.getString(PACKAGE_TO_LAUNCH);
143 if (packageName != null) {
144 Log.d(TAG, "Launching app " + packageName);
Guang Zhu4f486ee2015-05-26 13:54:34 -0700145 Intent intent = getLaunchIntentForPackage(packageName);
146 if (intent == null) {
147 Log.w(TAG, String.format("Skipping %s; no launch intent", packageName));
148 return;
149 }
Guang Zhua5fe0de2017-09-28 18:11:19 -0700150 long startTime = System.currentTimeMillis();
151 launchActivity(packageName, intent);
Guang Zhu34092cf2015-05-07 10:51:29 -0700152 try {
Guang Zhua5fe0de2017-09-28 18:11:19 -0700153 checkDropbox(startTime, packageName);
154 if (mAppErrors.containsKey(packageName)) {
Guang Zhu3bd71872017-10-25 17:04:25 -0700155 StringBuilder message = new StringBuilder("Error(s) detected for package: ")
Guang Zhua5fe0de2017-09-28 18:11:19 -0700156 .append(packageName);
Guang Zhu3bd71872017-10-25 17:04:25 -0700157 List<String> errors = mAppErrors.get(packageName);
158 for (int i = 0; i < MAX_NUM_CRASH_SNIPPET && i < errors.size(); i++) {
159 String err = errors.get(i);
Guang Zhua5fe0de2017-09-28 18:11:19 -0700160 message.append("\n\n");
Guang Zhu3bd71872017-10-25 17:04:25 -0700161 // limit the size of each crash snippet
162 message.append(truncate(err, MAX_CRASH_SNIPPET_LINES));
163 }
164 if (errors.size() > MAX_NUM_CRASH_SNIPPET) {
165 message.append(String.format("\n... %d more errors omitted ...",
166 errors.size() - MAX_NUM_CRASH_SNIPPET));
Guang Zhua5fe0de2017-09-28 18:11:19 -0700167 }
168 Assert.fail(message.toString());
169 }
170 // last check: see if app process is still running
171 Assert.assertTrue("app package \"" + packageName + "\" no longer found in running "
172 + "tasks, but no explicit crashes were detected; check logcat for details",
173 processStillUp(packageName));
Guang Zhu34092cf2015-05-07 10:51:29 -0700174 } finally {
175 returnHome();
176 }
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700177 } else {
178 Log.d(TAG, "Missing argument, use " + PACKAGE_TO_LAUNCH +
179 " to specify the package to launch");
180 }
181 }
182
183 /**
Guang Zhu3bd71872017-10-25 17:04:25 -0700184 * Truncate the text to at most the specified number of lines, and append a marker at the end
185 * when truncated
186 * @param text
187 * @param maxLines
188 * @return
189 */
190 private static String truncate(String text, int maxLines) {
191 String[] lines = text.split("\\r?\\n");
192 StringBuilder ret = new StringBuilder();
193 for (int i = 0; i < maxLines && i < lines.length; i++) {
194 ret.append(lines[i]);
195 ret.append('\n');
196 }
197 if (lines.length > maxLines) {
198 ret.append("... ");
199 ret.append(lines.length - maxLines);
200 ret.append(" more lines truncated ...\n");
201 }
202 return ret.toString();
203 }
204
205 /**
Guang Zhua5fe0de2017-09-28 18:11:19 -0700206 * Check dropbox for entries of interest regarding the specified process
207 * @param startTime if not 0, only check entries with timestamp later than the start time
208 * @param processName the process name to check for
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700209 */
Guang Zhua5fe0de2017-09-28 18:11:19 -0700210 private void checkDropbox(long startTime, String processName) {
211 DropBoxManager dropbox = (DropBoxManager) mContext
212 .getSystemService(Context.DROPBOX_SERVICE);
213 DropBoxManager.Entry entry = null;
214 while (null != (entry = dropbox.getNextEntry(null, startTime))) {
215 try {
216 // only check entries with tag that's of interest
217 String tag = entry.getTag();
218 if (DROPBOX_TAGS.contains(tag)) {
219 String content = entry.getText(4096);
220 if (content != null) {
221 if (content.contains(processName)) {
222 addProcessError(processName, "dropbox:" + tag, content);
223 }
224 }
225 }
226 startTime = entry.getTimeMillis();
227 } finally {
228 entry.close();
229 }
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700230 }
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700231 }
232
Guang Zhu34092cf2015-05-07 10:51:29 -0700233 private void returnHome() {
234 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
235 homeIntent.addCategory(Intent.CATEGORY_HOME);
236 homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
237 // Send the "home" intent and wait 2 seconds for us to get there
238 mContext.startActivity(homeIntent);
239 try {
240 Thread.sleep(mWorkspaceLaunchTimeout);
241 } catch (InterruptedException e) {
242 // ignore
243 }
244 }
245
Guang Zhu4f486ee2015-05-26 13:54:34 -0700246 private Intent getLaunchIntentForPackage(String packageName) {
Guang Zhua5fe0de2017-09-28 18:11:19 -0700247 UiModeManager umm = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
Guang Zhu4f486ee2015-05-26 13:54:34 -0700248 boolean isLeanback = umm.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION;
249 Intent intent = null;
250 if (isLeanback) {
251 intent = mPackageManager.getLeanbackLaunchIntentForPackage(packageName);
252 } else {
253 intent = mPackageManager.getLaunchIntentForPackage(packageName);
254 }
255 return intent;
256 }
257
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700258 /**
259 * Launches and activity and queries for errors.
Tsu Chiang Chuang14c716b2013-04-30 15:58:04 -0700260 *
261 * @param packageName {@link String} the package name of the application to
262 * launch.
263 * @return {@link Collection} of {@link ProcessErrorStateInfo} detected
264 * during the app launch.
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700265 */
Guang Zhua5fe0de2017-09-28 18:11:19 -0700266 private void launchActivity(String packageName, Intent intent) {
Guang Zhu4f486ee2015-05-26 13:54:34 -0700267 Log.d(TAG, String.format("launching package \"%s\" with intent: %s",
268 packageName, intent.toString()));
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700269
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700270 // Launch Activity
271 mContext.startActivity(intent);
272
273 try {
Guang Zhua5fe0de2017-09-28 18:11:19 -0700274 // artificial delay: in case app crashes after doing some work during launch
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700275 Thread.sleep(mAppLaunchTimeout);
276 } catch (InterruptedException e) {
277 // ignore
278 }
Guang Zhua5fe0de2017-09-28 18:11:19 -0700279 }
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700280
Guang Zhua5fe0de2017-09-28 18:11:19 -0700281 private void addProcessError(String processName, String errorType, String errorInfo) {
282 // parse out the package name if necessary, for apps with multiple proceses
283 String pkgName = processName.split(":", 2)[0];
284 List<String> errors;
285 if (mAppErrors.containsKey(pkgName)) {
286 errors = mAppErrors.get(pkgName);
287 } else {
288 errors = new ArrayList<>();
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700289 }
Guang Zhu3bd71872017-10-25 17:04:25 -0700290 errors.add(String.format("### Type: %s, Details:\n%s", errorType, errorInfo));
Guang Zhua5fe0de2017-09-28 18:11:19 -0700291 mAppErrors.put(pkgName, errors);
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700292 }
Tsu Chiang Chuang14c716b2013-04-30 15:58:04 -0700293
294 /**
295 * Determine if a given package is still running.
296 *
297 * @param packageName {@link String} package to look for
298 * @return True if package is running, false otherwise.
299 */
300 private boolean processStillUp(String packageName) {
Guang Zhu9f521c92015-05-25 12:09:12 -0700301 @SuppressWarnings("deprecation")
302 List<RunningTaskInfo> infos = mActivityManager.getRunningTasks(100);
303 for (RunningTaskInfo info : infos) {
304 if (info.baseActivity.getPackageName().equals(packageName)) {
Maxim Siniavinedfe1bdc2014-08-07 18:48:46 -0700305 return true;
Tsu Chiang Chuang14c716b2013-04-30 15:58:04 -0700306 }
Tsu Chiang Chuang14c716b2013-04-30 15:58:04 -0700307 }
308 return false;
309 }
Guang Zhua5fe0de2017-09-28 18:11:19 -0700310
311 /**
312 * An {@link IActivityController} that instructs framework to kill processes hitting crashes
313 * directly without showing crash dialogs
314 *
315 */
316 private class CrashSuppressor extends IActivityController.Stub {
317
318 @Override
319 public boolean activityStarting(Intent intent, String pkg) throws RemoteException {
320 Log.d(TAG, "activity starting: " + intent.getComponent().toShortString());
321 return true;
322 }
323
324 @Override
325 public boolean activityResuming(String pkg) throws RemoteException {
326 Log.d(TAG, "activity resuming: " + pkg);
327 return true;
328 }
329
330 @Override
331 public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg,
332 long timeMillis, String stackTrace) throws RemoteException {
333 Log.d(TAG, "app crash: " + processName);
334 addProcessError(processName, "crash", stackTrace);
335 // don't show dialog
336 return false;
337 }
338
339 @Override
340 public int appEarlyNotResponding(String processName, int pid, String annotation)
341 throws RemoteException {
342 // ignore
343 return 0;
344 }
345
346 @Override
347 public int appNotResponding(String processName, int pid, String processStats)
348 throws RemoteException {
349 Log.d(TAG, "app ANR: " + processName);
350 addProcessError(processName, "ANR", processStats);
351 // don't show dialog
352 return -1;
353 }
354
355 @Override
356 public int systemNotResponding(String msg) throws RemoteException {
357 // ignore
358 return -1;
359 }
360 }
Tsu Chiang Chuangfea39c32012-04-23 16:06:58 -0700361}