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 17package com.android.compatibilitytest; 18 19import android.app.ActivityManager; 20import android.app.ActivityManager.ProcessErrorStateInfo; 21import android.app.ActivityManager.RunningAppProcessInfo; 22import android.content.Context; 23import android.content.Intent; 24import android.content.pm.PackageInfo; 25import android.content.pm.PackageManager; 26import android.content.pm.PackageManager.NameNotFoundException; 27import android.os.Bundle; 28import android.test.InstrumentationTestCase; 29import android.util.Log; 30 31import junit.framework.Assert; 32 33import java.util.Collection; 34import java.util.List; 35 36/** 37 * Application Compatibility Test that launches an application and detects 38 * crashes. 39 */ 40public class AppCompatibility extends InstrumentationTestCase { 41 42 private static final String TAG = "AppCompability"; 43 private static final String PACKAGE_TO_LAUNCH = "package_to_launch"; 44 private static final String APP_LAUNCH_TIMEOUT_MSECS = "app_launch_timeout_ms"; 45 private static final String WORKSPACE_LAUNCH_TIMEOUT_MSECS = "workspace_launch_timeout_ms"; 46 47 private int mAppLaunchTimeout = 7000; 48 private int mWorkspaceLaunchTimeout = 2000; 49 50 private Context mContext; 51 private ActivityManager mActivityManager; 52 private PackageManager mPackageManager; 53 private AppCompatibilityRunner mRunner; 54 private Bundle mArgs; 55 56 @Override 57 public void setUp() throws Exception { 58 super.setUp(); 59 mRunner = (AppCompatibilityRunner) getInstrumentation(); 60 assertNotNull("Could not fetch InstrumentationTestRunner.", mRunner); 61 62 mContext = mRunner.getTargetContext(); 63 Assert.assertNotNull("Could not get the Context", mContext); 64 65 mActivityManager = (ActivityManager) 66 mContext.getSystemService(Context.ACTIVITY_SERVICE); 67 Assert.assertNotNull("Could not get Activity Manager", mActivityManager); 68 69 mPackageManager = mContext.getPackageManager(); 70 Assert.assertNotNull("Missing Package Manager", mPackageManager); 71 72 mArgs = mRunner.getBundle(); 73 74 // Parse optional inputs. 75 String appLaunchTimeoutMsecs = mArgs.getString(APP_LAUNCH_TIMEOUT_MSECS); 76 if (appLaunchTimeoutMsecs != null) { 77 mAppLaunchTimeout = Integer.parseInt(appLaunchTimeoutMsecs); 78 } 79 String workspaceLaunchTimeoutMsecs = mArgs.getString(WORKSPACE_LAUNCH_TIMEOUT_MSECS); 80 if (workspaceLaunchTimeoutMsecs != null) { 81 mWorkspaceLaunchTimeout = Integer.parseInt(workspaceLaunchTimeoutMsecs); 82 } 83 } 84 85 @Override 86 protected void tearDown() throws Exception { 87 super.tearDown(); 88 } 89 90 /** 91 * Actual test case that launches the package and throws an exception on the 92 * first error. 93 * 94 * @throws Exception 95 */ 96 public void testAppStability() throws Exception { 97 String packageName = mArgs.getString(PACKAGE_TO_LAUNCH); 98 if (packageName != null) { 99 Log.d(TAG, "Launching app " + packageName); 100 Collection<ProcessErrorStateInfo> err = launchActivity(packageName); 101 // Make sure there are no errors when launching the application, 102 // otherwise raise an 103 // exception with the first error encountered. 104 assertNull(getFirstError(err), err); 105 assertTrue("App crashed after launch.", processStillUp(packageName)); 106 } else { 107 Log.d(TAG, "Missing argument, use " + PACKAGE_TO_LAUNCH + 108 " to specify the package to launch"); 109 } 110 } 111 112 /** 113 * Gets the first error in collection and return the long message for it. 114 * 115 * @param in {@link Collection} of {@link ProcessErrorStateInfo} to parse. 116 * @return {@link String} the long message of the error. 117 */ 118 private String getFirstError(Collection<ProcessErrorStateInfo> in) { 119 if (in == null) { 120 return null; 121 } 122 ProcessErrorStateInfo err = in.iterator().next(); 123 if (err != null) { 124 return err.stackTrace; 125 } 126 return null; 127 } 128 129 /** 130 * Launches and activity and queries for errors. 131 * 132 * @param packageName {@link String} the package name of the application to 133 * launch. 134 * @return {@link Collection} of {@link ProcessErrorStateInfo} detected 135 * during the app launch. 136 */ 137 private Collection<ProcessErrorStateInfo> launchActivity(String packageName) { 138 Intent homeIntent = new Intent(Intent.ACTION_MAIN); 139 homeIntent.addCategory(Intent.CATEGORY_HOME); 140 homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 141 142 Intent intent = mPackageManager.getLaunchIntentForPackage(packageName); 143 // Skip if the apk does not have a launch intent. 144 if (intent == null) { 145 Log.d(TAG, "Skipping " + packageName + "; missing launch intent"); 146 return null; 147 } 148 149 // We check for any Crash or ANR dialogs that are already up, and we 150 // ignore them. This is 151 // so that we don't report crashes that were caused by prior apps (which 152 // those particular 153 // tests should have caught and reported already). Otherwise, test 154 // failures would cascade 155 // from the initial broken app to many/all of the tests following that 156 // app's launch. 157 final Collection<ProcessErrorStateInfo> preErr = 158 mActivityManager.getProcessesInErrorState(); 159 160 // Launch Activity 161 mContext.startActivity(intent); 162 163 try { 164 Thread.sleep(mAppLaunchTimeout); 165 } catch (InterruptedException e) { 166 // ignore 167 } 168 169 // Send the "home" intent and wait 2 seconds for us to get there 170 mContext.startActivity(homeIntent); 171 try { 172 Thread.sleep(mWorkspaceLaunchTimeout); 173 } catch (InterruptedException e) { 174 // ignore 175 } 176 177 // See if there are any errors. We wait until down here to give ANRs as 178 // much time as 179 // possible to occur. 180 final Collection<ProcessErrorStateInfo> postErr = 181 mActivityManager.getProcessesInErrorState(); 182 // Take the difference between the error processes we see now, and the 183 // ones that were 184 // present when we started 185 if (preErr != null && postErr != null) { 186 postErr.removeAll(preErr); 187 } 188 return postErr; 189 } 190 191 /** 192 * Determine if a given package is still running. 193 * 194 * @param packageName {@link String} package to look for 195 * @return True if package is running, false otherwise. 196 */ 197 private boolean processStillUp(String packageName) { 198 try { 199 PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, 0); 200 String processName = packageInfo.applicationInfo.processName; 201 List<RunningAppProcessInfo> runningApps = mActivityManager.getRunningAppProcesses(); 202 for (RunningAppProcessInfo app : runningApps) { 203 if (app.processName.equalsIgnoreCase(processName)) { 204 Log.d(TAG, "Found process " + app.processName); 205 return true; 206 } 207 } 208 Log.d(TAG, "Failed to find process " + processName + " with package name " 209 + packageName); 210 } catch (NameNotFoundException e) { 211 Log.w(TAG, "Failed to find package " + packageName); 212 return false; 213 } 214 return false; 215 } 216} 217