| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.compatibilitytest; |
| |
| import android.app.ActivityManager; |
| import android.app.ActivityManager.ProcessErrorStateInfo; |
| import android.app.ActivityManager.RunningAppProcessInfo; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.os.Bundle; |
| import android.test.InstrumentationTestCase; |
| import android.util.Log; |
| |
| import junit.framework.Assert; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| |
| /** |
| * Application Compatibility Test that launches an application and detects |
| * crashes. |
| */ |
| public class AppCompatibility extends InstrumentationTestCase { |
| |
| private static final String TAG = "AppCompability"; |
| private static final String PACKAGE_TO_LAUNCH = "package_to_launch"; |
| private static final String APP_LAUNCH_TIMEOUT_MSECS = "app_launch_timeout_ms"; |
| private static final String WORKSPACE_LAUNCH_TIMEOUT_MSECS = "workspace_launch_timeout_ms"; |
| |
| private int mAppLaunchTimeout = 7000; |
| private int mWorkspaceLaunchTimeout = 2000; |
| |
| private Context mContext; |
| private ActivityManager mActivityManager; |
| private PackageManager mPackageManager; |
| private AppCompatibilityRunner mRunner; |
| private Bundle mArgs; |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| mRunner = (AppCompatibilityRunner) getInstrumentation(); |
| assertNotNull("Could not fetch InstrumentationTestRunner.", mRunner); |
| |
| mContext = mRunner.getTargetContext(); |
| Assert.assertNotNull("Could not get the Context", mContext); |
| |
| mActivityManager = (ActivityManager) |
| mContext.getSystemService(Context.ACTIVITY_SERVICE); |
| Assert.assertNotNull("Could not get Activity Manager", mActivityManager); |
| |
| mPackageManager = mContext.getPackageManager(); |
| Assert.assertNotNull("Missing Package Manager", mPackageManager); |
| |
| mArgs = mRunner.getBundle(); |
| |
| // Parse optional inputs. |
| String appLaunchTimeoutMsecs = mArgs.getString(APP_LAUNCH_TIMEOUT_MSECS); |
| if (appLaunchTimeoutMsecs != null) { |
| mAppLaunchTimeout = Integer.parseInt(appLaunchTimeoutMsecs); |
| } |
| String workspaceLaunchTimeoutMsecs = mArgs.getString(WORKSPACE_LAUNCH_TIMEOUT_MSECS); |
| if (workspaceLaunchTimeoutMsecs != null) { |
| mWorkspaceLaunchTimeout = Integer.parseInt(workspaceLaunchTimeoutMsecs); |
| } |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| super.tearDown(); |
| } |
| |
| /** |
| * Actual test case that launches the package and throws an exception on the |
| * first error. |
| * |
| * @throws Exception |
| */ |
| public void testAppStability() throws Exception { |
| String packageName = mArgs.getString(PACKAGE_TO_LAUNCH); |
| if (packageName != null) { |
| Log.d(TAG, "Launching app " + packageName); |
| ProcessErrorStateInfo err = launchActivity(packageName); |
| // Make sure there are no errors when launching the application, |
| // otherwise raise an |
| // exception with the first error encountered. |
| assertNull(getStackTrace(err), err); |
| try { |
| assertTrue("App crashed after launch.", processStillUp(packageName)); |
| } finally { |
| returnHome(); |
| } |
| } else { |
| Log.d(TAG, "Missing argument, use " + PACKAGE_TO_LAUNCH + |
| " to specify the package to launch"); |
| } |
| } |
| |
| /** |
| * Gets the stack trace for the error. |
| * |
| * @param in {@link ProcessErrorStateInfo} to parse. |
| * @return {@link String} the long message of the error. |
| */ |
| private String getStackTrace(ProcessErrorStateInfo in) { |
| if (in == null) { |
| return null; |
| } else { |
| return in.stackTrace; |
| } |
| } |
| |
| /** |
| * Returns the process name that the package is going to use. |
| * |
| * @param packageName name of the package |
| * @return process name of the package |
| */ |
| private String getProcessName(String packageName) { |
| try { |
| PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0); |
| return pi.applicationInfo.processName; |
| } catch (NameNotFoundException e) { |
| return packageName; |
| } |
| } |
| |
| private void returnHome() { |
| Intent homeIntent = new Intent(Intent.ACTION_MAIN); |
| homeIntent.addCategory(Intent.CATEGORY_HOME); |
| homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| // Send the "home" intent and wait 2 seconds for us to get there |
| mContext.startActivity(homeIntent); |
| try { |
| Thread.sleep(mWorkspaceLaunchTimeout); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| } |
| |
| /** |
| * Launches and activity and queries for errors. |
| * |
| * @param packageName {@link String} the package name of the application to |
| * launch. |
| * @return {@link Collection} of {@link ProcessErrorStateInfo} detected |
| * during the app launch. |
| */ |
| private ProcessErrorStateInfo launchActivity(String packageName) { |
| // the recommended way to see if this is a tv or not. |
| boolean isleanback = !mPackageManager.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN) |
| && !mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY); |
| Intent intent; |
| if (isleanback) { |
| Log.d(TAG, "Leanback and relax! " + packageName); |
| intent = mPackageManager.getLeanbackLaunchIntentForPackage(packageName); |
| } else { |
| intent = mPackageManager.getLaunchIntentForPackage(packageName); |
| } |
| assertNotNull("Skipping " + packageName + "; missing launch intent", intent); |
| |
| String processName = getProcessName(packageName); |
| |
| // Launch Activity |
| mContext.startActivity(intent); |
| |
| try { |
| Thread.sleep(mAppLaunchTimeout); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| |
| // See if there are any errors. We wait until down here to give ANRs as |
| // much time as |
| // possible to occur. |
| final Collection<ProcessErrorStateInfo> postErr = |
| mActivityManager.getProcessesInErrorState(); |
| |
| if (postErr == null) { |
| return null; |
| } |
| for (ProcessErrorStateInfo error : postErr) { |
| if (error.processName.equals(processName)) { |
| return error; |
| } |
| } |
| return null; |
| } |
| |
| private boolean ensureForegroundActivity(RunningAppProcessInfo info) { |
| Log.d(TAG, String.format("ensureForegroundActivity: proc=%s, pid=%d, state=%d", |
| info.processName, info.pid, info.processState)); |
| return info.processState == ActivityManager.PROCESS_STATE_TOP; |
| } |
| |
| /** |
| * Determine if a given package is still running. |
| * |
| * @param packageName {@link String} package to look for |
| * @return True if package is running, false otherwise. |
| */ |
| private boolean processStillUp(String packageName) { |
| String processName = getProcessName(packageName); |
| List<RunningAppProcessInfo> runningApps = mActivityManager.getRunningAppProcesses(); |
| List<RunningAppProcessInfo> relatedProcs = new ArrayList<>(); |
| for (RunningAppProcessInfo app : runningApps) { |
| if (app.processName.equalsIgnoreCase(processName)) { |
| if (!ensureForegroundActivity(app)) { |
| Log.w(TAG, "Found process but it's not top activity."); |
| return false; |
| } |
| return true; |
| } |
| for (String relatedPackage : app.pkgList) { |
| if (relatedPackage.equalsIgnoreCase(packageName)) { |
| relatedProcs.add(app); |
| } |
| } |
| } |
| // now that we are here, we've found no RAPI's directly matching processName, but |
| // potentially a List of them with one of related packages being processName |
| if (!relatedProcs.isEmpty()) { |
| for (RunningAppProcessInfo app : relatedProcs) { |
| if (ensureForegroundActivity(app)) { |
| return true; |
| } |
| } |
| Log.w(TAG, "Found related processes, but none has top activity."); |
| } |
| Log.w(TAG, "Failed to find process " + processName + " with package name " |
| + packageName); |
| return false; |
| } |
| } |