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 
17 package com.android.compatibilitytest;
18 
19 import android.app.ActivityManager;
20 import android.app.UiAutomation;
21 import android.app.UiModeManager;
22 import android.app.ActivityManager.ProcessErrorStateInfo;
23 import android.app.ActivityManager.RunningTaskInfo;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.pm.PackageInfo;
27 import android.content.pm.PackageManager;
28 import android.content.pm.PackageManager.NameNotFoundException;
29 import android.content.res.Configuration;
30 import android.os.Bundle;
31 import android.test.InstrumentationTestCase;
32 import android.util.Log;
33 
34 import junit.framework.Assert;
35 
36 import java.util.Collection;
37 import java.util.List;
38 
39 /**
40  * Application Compatibility Test that launches an application and detects
41  * crashes.
42  */
43 public class AppCompatibility extends InstrumentationTestCase {
44 
45     private static final String TAG = AppCompatibility.class.getSimpleName();
46     private static final String PACKAGE_TO_LAUNCH = "package_to_launch";
47     private static final String APP_LAUNCH_TIMEOUT_MSECS = "app_launch_timeout_ms";
48     private static final String WORKSPACE_LAUNCH_TIMEOUT_MSECS = "workspace_launch_timeout_ms";
49 
50     private int mAppLaunchTimeout = 7000;
51     private int mWorkspaceLaunchTimeout = 2000;
52 
53     private Context mContext;
54     private ActivityManager mActivityManager;
55     private PackageManager mPackageManager;
56     private AppCompatibilityRunner mRunner;
57     private Bundle mArgs;
58 
59     @Override
setUp()60     public void setUp() throws Exception {
61         super.setUp();
62         mRunner = (AppCompatibilityRunner) getInstrumentation();
63         assertNotNull("Could not fetch InstrumentationTestRunner.", mRunner);
64 
65         mContext = mRunner.getTargetContext();
66         Assert.assertNotNull("Could not get the Context", mContext);
67 
68         mActivityManager = (ActivityManager)
69                 mContext.getSystemService(Context.ACTIVITY_SERVICE);
70         Assert.assertNotNull("Could not get Activity Manager", mActivityManager);
71 
72         mPackageManager = mContext.getPackageManager();
73         Assert.assertNotNull("Missing Package Manager", mPackageManager);
74 
75         mArgs = mRunner.getBundle();
76 
77         // Parse optional inputs.
78         String appLaunchTimeoutMsecs = mArgs.getString(APP_LAUNCH_TIMEOUT_MSECS);
79         if (appLaunchTimeoutMsecs != null) {
80             mAppLaunchTimeout = Integer.parseInt(appLaunchTimeoutMsecs);
81         }
82         String workspaceLaunchTimeoutMsecs = mArgs.getString(WORKSPACE_LAUNCH_TIMEOUT_MSECS);
83         if (workspaceLaunchTimeoutMsecs != null) {
84             mWorkspaceLaunchTimeout = Integer.parseInt(workspaceLaunchTimeoutMsecs);
85         }
86         getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0);
87     }
88 
89     @Override
tearDown()90     protected void tearDown() throws Exception {
91         getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
92         super.tearDown();
93     }
94 
95     /**
96      * Actual test case that launches the package and throws an exception on the
97      * first error.
98      *
99      * @throws Exception
100      */
testAppStability()101     public void testAppStability() throws Exception {
102         String packageName = mArgs.getString(PACKAGE_TO_LAUNCH);
103         if (packageName != null) {
104             Log.d(TAG, "Launching app " + packageName);
105             Intent intent = getLaunchIntentForPackage(packageName);
106             if (intent == null) {
107                 Log.w(TAG, String.format("Skipping %s; no launch intent", packageName));
108                 return;
109             }
110             ProcessErrorStateInfo err = launchActivity(packageName, intent);
111             // Make sure there are no errors when launching the application,
112             // otherwise raise an
113             // exception with the first error encountered.
114             assertNull(getStackTrace(err), err);
115             try {
116                 assertTrue("App crashed after launch.", processStillUp(packageName));
117             } finally {
118                 returnHome();
119             }
120         } else {
121             Log.d(TAG, "Missing argument, use " + PACKAGE_TO_LAUNCH +
122                     " to specify the package to launch");
123         }
124     }
125 
126     /**
127      * Gets the stack trace for the error.
128      *
129      * @param in {@link ProcessErrorStateInfo} to parse.
130      * @return {@link String} the long message of the error.
131      */
getStackTrace(ProcessErrorStateInfo in)132     private String getStackTrace(ProcessErrorStateInfo in) {
133         if (in == null) {
134             return null;
135         } else {
136             return in.stackTrace;
137         }
138     }
139 
140     /**
141      * Returns the process name that the package is going to use.
142      *
143      * @param packageName name of the package
144      * @return process name of the package
145      */
getProcessName(String packageName)146     private String getProcessName(String packageName) {
147         try {
148             PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0);
149             return pi.applicationInfo.processName;
150         } catch (NameNotFoundException e) {
151             return packageName;
152         }
153     }
154 
returnHome()155     private void returnHome() {
156         Intent homeIntent = new Intent(Intent.ACTION_MAIN);
157         homeIntent.addCategory(Intent.CATEGORY_HOME);
158         homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
159         // Send the "home" intent and wait 2 seconds for us to get there
160         mContext.startActivity(homeIntent);
161         try {
162             Thread.sleep(mWorkspaceLaunchTimeout);
163         } catch (InterruptedException e) {
164             // ignore
165         }
166     }
167 
getLaunchIntentForPackage(String packageName)168     private Intent getLaunchIntentForPackage(String packageName) {
169         UiModeManager umm = (UiModeManager)
170                 getInstrumentation().getContext().getSystemService(Context.UI_MODE_SERVICE);
171         boolean isLeanback = umm.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION;
172         Intent intent = null;
173         if (isLeanback) {
174             intent = mPackageManager.getLeanbackLaunchIntentForPackage(packageName);
175         } else {
176             intent = mPackageManager.getLaunchIntentForPackage(packageName);
177         }
178         return intent;
179     }
180 
181     /**
182      * Launches and activity and queries for errors.
183      *
184      * @param packageName {@link String} the package name of the application to
185      *            launch.
186      * @return {@link Collection} of {@link ProcessErrorStateInfo} detected
187      *         during the app launch.
188      */
launchActivity(String packageName, Intent intent)189     private ProcessErrorStateInfo launchActivity(String packageName, Intent intent) {
190         Log.d(TAG, String.format("launching package \"%s\" with intent: %s",
191                 packageName, intent.toString()));
192 
193         String processName = getProcessName(packageName);
194 
195         // Launch Activity
196         mContext.startActivity(intent);
197 
198         try {
199             Thread.sleep(mAppLaunchTimeout);
200         } catch (InterruptedException e) {
201             // ignore
202         }
203 
204         // See if there are any errors. We wait until down here to give ANRs as much time as
205         // possible to occur.
206         final Collection<ProcessErrorStateInfo> postErr =
207                 mActivityManager.getProcessesInErrorState();
208 
209         if (postErr == null) {
210             return null;
211         }
212         for (ProcessErrorStateInfo error : postErr) {
213             if (error.processName.equals(processName)) {
214                 return error;
215             }
216         }
217         return null;
218     }
219 
220     /**
221      * Determine if a given package is still running.
222      *
223      * @param packageName {@link String} package to look for
224      * @return True if package is running, false otherwise.
225      */
processStillUp(String packageName)226     private boolean processStillUp(String packageName) {
227         @SuppressWarnings("deprecation")
228         List<RunningTaskInfo> infos = mActivityManager.getRunningTasks(100);
229         for (RunningTaskInfo info : infos) {
230             if (info.baseActivity.getPackageName().equals(packageName)) {
231                 return true;
232             }
233         }
234         return false;
235     }
236 }
237