diff options
author | Kevin Neas <kneas@google.com> | 2018-02-15 17:53:48 -0800 |
---|---|---|
committer | Kevin Neas <kneas@google.com> | 2018-02-16 04:46:49 +0000 |
commit | aff528421ffda3087495bea2a9fb4b4f99dfba4c (patch) | |
tree | 5adf5c3beeb5f5035916de3182278aa5c75453b9 | |
parent | 519802d7675d89c733221dc43b2ccf22310d111b (diff) |
AppLaunch tests for Wear
Wear needs specific changes that the platform team is unable to support.
Most of this code is an exact copy of the AppLaunch file, with a specific
check to not kill the app is Wear Home.
Change-Id: I2332416d61676b52518d6988849271e0b0777009
-rw-r--r-- | tests/AppLaunchWear/Android.mk | 21 | ||||
-rw-r--r-- | tests/AppLaunchWear/AndroidManifest.xml | 21 | ||||
-rw-r--r-- | tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java | 821 |
3 files changed, 863 insertions, 0 deletions
diff --git a/tests/AppLaunchWear/Android.mk b/tests/AppLaunchWear/Android.mk new file mode 100644 index 000000000000..ac123e79bcd9 --- /dev/null +++ b/tests/AppLaunchWear/Android.mk @@ -0,0 +1,21 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +# Only compile source java files in this apk. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := AppLaunchWear + +LOCAL_CERTIFICATE := platform +LOCAL_JAVA_LIBRARIES := android.test.base android.test.runner + +LOCAL_STATIC_JAVA_LIBRARIES := android-support-test + +LOCAL_COMPATIBILITY_SUITE := device-tests + +include $(BUILD_PACKAGE) + +# Use the following include to make our test apk. +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tests/AppLaunchWear/AndroidManifest.xml b/tests/AppLaunchWear/AndroidManifest.xml new file mode 100644 index 000000000000..7dfd7bafbaaa --- /dev/null +++ b/tests/AppLaunchWear/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.tests.applaunch" + android:sharedUserId="android.uid.system" > + + <uses-permission android:name="android.permission.REAL_GET_TASKS" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + + <uses-sdk + android:minSdkVersion="22" + android:targetSdkVersion="24" /> + + <instrumentation android:label="Measure app start up time" + android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.tests.applaunch" /> + + <application android:label="App Launch Test"> + <uses-library android:name="android.test.runner" /> + </application> +</manifest> diff --git a/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java new file mode 100644 index 000000000000..f32464bf84c2 --- /dev/null +++ b/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java @@ -0,0 +1,821 @@ +/* + * Copyright (C) 2013 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.tests.applaunch; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.ActivityManager; +import android.app.ActivityManager.ProcessErrorStateInfo; +import android.app.IActivityManager; +import android.app.UiAutomation; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.UserHandle; +import android.support.test.rule.logging.AtraceLogger; +import android.test.InstrumentationTestCase; +import android.test.InstrumentationTestRunner; +import android.util.Log; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +/** + * This test is intended to measure the time it takes for the apps to start. + * Names of the applications are passed in command line, and the + * test starts each application, and reports the start up time in milliseconds. + * The instrumentation expects the following key to be passed on the command line: + * apps - A list of applications to start and their corresponding result keys + * in the following format: + * -e apps <app name>^<result key>|<app name>^<result key> + */ +public class AppLaunch extends InstrumentationTestCase { + + private static final int JOIN_TIMEOUT = 10000; + private static final String TAG = AppLaunch.class.getSimpleName(); + + // optional parameter: comma separated list of required account types before proceeding + // with the app launch + private static final String KEY_REQUIRED_ACCOUNTS = "required_accounts"; + private static final String KEY_APPS = "apps"; + private static final String KEY_TRIAL_LAUNCH = "trial_launch"; + private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations"; + private static final String KEY_LAUNCH_ORDER = "launch_order"; + private static final String KEY_DROP_CACHE = "drop_cache"; + private static final String KEY_SIMPLEPERF_CMD = "simpleperf_cmd"; + private static final String KEY_SIMPLEPERF_APP = "simpleperf_app"; + private static final String KEY_TRACE_ITERATIONS = "trace_iterations"; + private static final String KEY_LAUNCH_DIRECTORY = "launch_directory"; + private static final String KEY_TRACE_DIRECTORY = "trace_directory"; + private static final String KEY_TRACE_CATEGORY = "trace_categories"; + private static final String KEY_TRACE_BUFFERSIZE = "trace_bufferSize"; + private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval"; + private static final String KEY_COMPILER_FILTERS = "compiler_filters"; + + private static final String SIMPLEPERF_APP_CMD = + "simpleperf --log fatal stat --csv -e cpu-cycles,major-faults --app %s & %s"; + private static final String WEARABLE_ACTION_GOOGLE = + "com.google.android.wearable.action.GOOGLE"; + private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 5000; // 5s to allow app to idle + private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; // 750ms idle for non initial launches + private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 5000; // 5s between launching apps + private static final String LAUNCH_SUB_DIRECTORY = "launch_logs"; + private static final String LAUNCH_FILE = "applaunch.txt"; + private static final String TRACE_SUB_DIRECTORY = "atrace_logs"; + private static final String DEFAULT_TRACE_CATEGORIES = + "sched,freq,gfx,view,dalvik,webview,input,wm,disk,am,wm"; + private static final String DEFAULT_TRACE_BUFFER_SIZE = "20000"; + private static final String DEFAULT_TRACE_DUMP_INTERVAL = "10"; + private static final String TRIAL_LAUNCH = "TRIAL_LAUNCH"; + private static final String DELIMITER = ","; + private static final String DROP_CACHE_SCRIPT = "/data/local/tmp/dropCache.sh"; + private static final String APP_LAUNCH_CMD = "am start -W -n"; + private static final String SUCCESS_MESSAGE = "Status: ok"; + private static final String COMPILE_SUCCESS = "Success"; + private static final String THIS_TIME = "ThisTime:"; + private static final String LAUNCH_ITERATION = "LAUNCH_ITERATION - %d"; + private static final String TRACE_ITERATION = "TRACE_ITERATION-%d"; + private static final String LAUNCH_ITERATION_PREFIX = "LAUNCH_ITERATION"; + private static final String TRACE_ITERATION_PREFIX = "TRACE_ITERATION"; + private static final String LAUNCH_ORDER_CYCLIC = "cyclic"; + private static final String LAUNCH_ORDER_SEQUENTIAL = "sequential"; + private static final String COMPILE_CMD = "cmd package compile -f -m %s %s"; + private static final String SPEED_PROFILE_FILTER = "speed-profile"; + private static final String VERIFY_FILTER = "verify"; + private static final String LAUNCH_SCRIPT_NAME = "appLaunch"; + private static final String WEARABLE_HOME_PACKAGE = "com.google.android.wearable.app"; + + private Map<String, Intent> mNameToIntent; + private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>(); + private Map<String, String> mNameToResultKey; + private Map<String, Map<String, List<AppLaunchResult>>> mNameToLaunchTime; + private IActivityManager mAm; + private String mSimplePerfCmd = null; + private String mLaunchOrder = null; + private boolean mDropCache = false; + private int mLaunchIterations = 10; + private int mTraceLaunchCount = 0; + private String mTraceDirectoryStr = null; + private Bundle mResult = new Bundle(); + private Set<String> mRequiredAccounts; + private boolean mTrialLaunch = false; + private BufferedWriter mBufferedWriter = null; + private boolean mSimplePerfAppOnly = false; + private String[] mCompilerFilters = null; + + @Override + protected void setUp() throws Exception { + super.setUp(); + getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0); + } + + @Override + protected void tearDown() throws Exception { + getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE); + super.tearDown(); + } + + private void addLaunchResult(LaunchOrder launch, AppLaunchResult result) { + mNameToLaunchTime.get(launch.getApp()).get(launch.getCompilerFilter()).add(result); + } + + private boolean hasFailureOnFirstLaunch(LaunchOrder launch) { + List<AppLaunchResult> results = + mNameToLaunchTime.get(launch.getApp()).get(launch.getCompilerFilter()); + return (results.size() > 0) && (results.get(0).mLaunchTime < 0); + } + + public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException, + IOException, InterruptedException { + InstrumentationTestRunner instrumentation = + (InstrumentationTestRunner)getInstrumentation(); + Bundle args = instrumentation.getArguments(); + mAm = ActivityManager.getService(); + String launchDirectory = args.getString(KEY_LAUNCH_DIRECTORY); + + createMappings(); + parseArgs(args); + checkAccountSignIn(); + + // Root directory for applaunch file to log the app launch output + // Will be useful in case of simpleperf command is used + File launchRootDir = null; + if (null != launchDirectory && !launchDirectory.isEmpty()) { + launchRootDir = new File(launchDirectory); + if (!launchRootDir.exists() && !launchRootDir.mkdirs()) { + throw new IOException("Unable to create the destination directory"); + } + } + + try { + File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); + + if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { + throw new IOException("Unable to create the lauch file sub directory"); + } + File file = new File(launchSubDir, LAUNCH_FILE); + FileOutputStream outputStream = new FileOutputStream(file); + mBufferedWriter = new BufferedWriter(new OutputStreamWriter( + outputStream)); + + // Root directory for trace file during the launches + File rootTrace = null; + File rootTraceSubDir = null; + int traceBufferSize = 0; + int traceDumpInterval = 0; + Set<String> traceCategoriesSet = null; + if (null != mTraceDirectoryStr && !mTraceDirectoryStr.isEmpty()) { + rootTrace = new File(mTraceDirectoryStr); + if (!rootTrace.exists() && !rootTrace.mkdirs()) { + throw new IOException("Unable to create the trace directory"); + } + rootTraceSubDir = new File(rootTrace, TRACE_SUB_DIRECTORY); + if (!rootTraceSubDir.exists() && !rootTraceSubDir.mkdirs()) { + throw new IOException("Unable to create the trace sub directory"); + } + assertNotNull("Trace iteration parameter is mandatory", + args.getString(KEY_TRACE_ITERATIONS)); + mTraceLaunchCount = Integer.parseInt(args.getString(KEY_TRACE_ITERATIONS)); + String traceCategoriesStr = args + .getString(KEY_TRACE_CATEGORY, DEFAULT_TRACE_CATEGORIES); + traceBufferSize = Integer.parseInt(args.getString(KEY_TRACE_BUFFERSIZE, + DEFAULT_TRACE_BUFFER_SIZE)); + traceDumpInterval = Integer.parseInt(args.getString(KEY_TRACE_DUMPINTERVAL, + DEFAULT_TRACE_DUMP_INTERVAL)); + traceCategoriesSet = new HashSet<String>(); + if (!traceCategoriesStr.isEmpty()) { + String[] traceCategoriesSplit = traceCategoriesStr.split(DELIMITER); + for (int i = 0; i < traceCategoriesSplit.length; i++) { + traceCategoriesSet.add(traceCategoriesSplit[i]); + } + } + } + + // Get the app launch order based on launch order, trial launch, + // launch iterations and trace iterations + setLaunchOrder(); + + for (LaunchOrder launch : mLaunchOrderList) { + dropCache(); + String appPkgName = mNameToIntent.get(launch.getApp()) + .getComponent().getPackageName(); + + // App launch times for trial launch will not be used for final + // launch time calculations. + if (launch.getLaunchReason().equals(TRIAL_LAUNCH)) { + // In the "applaunch.txt" file, trail launches is referenced using + // "TRIAL_LAUNCH" + if (SPEED_PROFILE_FILTER.equals(launch.getCompilerFilter())) { + assertTrue(String.format("Not able to compile the app : %s", appPkgName), + compileApp(VERIFY_FILTER, appPkgName)); + } else if (launch.getCompilerFilter() != null) { + assertTrue(String.format("Not able to compile the app : %s", appPkgName), + compileApp(launch.getCompilerFilter(), appPkgName)); + } + // We only need to run a trial for the speed-profile filter, but we always + // run one for "applaunch.txt" consistency. + AppLaunchResult launchResult = + startApp(launch.getApp(), true, launch.getLaunchReason()); + if (launchResult.mLaunchTime < 0) { + addLaunchResult(launch, new AppLaunchResult()); + // simply pass the app if launch isn't successful + // error should have already been logged by startApp + continue; + } + sleep(INITIAL_LAUNCH_IDLE_TIMEOUT); + if (SPEED_PROFILE_FILTER.equals(launch.getCompilerFilter())) { + // Send SIGUSR1 to force dumping a profile. + String sendSignalCommand = + String.format("killall -s SIGUSR1 %s", appPkgName); + getInstrumentation().getUiAutomation().executeShellCommand( + sendSignalCommand); + assertTrue(String.format("Not able to compile the app : %s", appPkgName), + compileApp(launch.getCompilerFilter(), appPkgName)); + } + } + + // App launch times used for final calculation + else if (launch.getLaunchReason().contains(LAUNCH_ITERATION_PREFIX)) { + AppLaunchResult launchResults = null; + if (hasFailureOnFirstLaunch(launch)) { + // skip if the app has failures while launched first + continue; + } + // In the "applaunch.txt" file app launches are referenced using + // "LAUNCH_ITERATION - ITERATION NUM" + if (appPkgName.contains(WEARABLE_HOME_PACKAGE)) { + launchResults = startApp(launch.getApp(), false, launch.getLaunchReason()); + } else { + launchResults = startApp(launch.getApp(), true, launch.getLaunchReason()); + } + if (launchResults.mLaunchTime < 0) { + addLaunchResult(launch, new AppLaunchResult()); + // if it fails once, skip the rest of the launches + continue; + } else { + addLaunchResult(launch, launchResults); + } + sleep(POST_LAUNCH_IDLE_TIMEOUT); + } + + // App launch times for trace launch will not be used for final + // launch time calculations. + else if (launch.getLaunchReason().contains(TRACE_ITERATION_PREFIX)) { + AtraceLogger atraceLogger = AtraceLogger + .getAtraceLoggerInstance(getInstrumentation()); + // Start the trace + try { + atraceLogger.atraceStart(traceCategoriesSet, traceBufferSize, + traceDumpInterval, rootTraceSubDir, + String.format("%s-%s", launch.getApp(), launch.getLaunchReason())); + startApp(launch.getApp(), true, launch.getLaunchReason()); + sleep(POST_LAUNCH_IDLE_TIMEOUT); + } finally { + // Stop the trace + atraceLogger.atraceStop(); + } + } + closeApp(launch.getApp(), true); + sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); + } + } finally { + if (null != mBufferedWriter) { + mBufferedWriter.close(); + } + } + + for (String app : mNameToResultKey.keySet()) { + for (String compilerFilter : mCompilerFilters) { + StringBuilder launchTimes = new StringBuilder(); + StringBuilder cpuCycles = new StringBuilder(); + StringBuilder majorFaults = new StringBuilder(); + for (AppLaunchResult result : mNameToLaunchTime.get(app).get(compilerFilter)) { + launchTimes.append(result.mLaunchTime); + launchTimes.append(","); + if (mSimplePerfAppOnly) { + cpuCycles.append(result.mCpuCycles); + cpuCycles.append(","); + majorFaults.append(result.mMajorFaults); + majorFaults.append(","); + } + } + String filterName = (compilerFilter == null) ? "" : ("-" + compilerFilter); + mResult.putString(mNameToResultKey.get(app) + filterName, launchTimes.toString()); + if (mSimplePerfAppOnly) { + mResult.putString(mNameToResultKey.get(app) + filterName + "-cpuCycles", + cpuCycles.toString()); + mResult.putString(mNameToResultKey.get(app) + filterName + "-majorFaults", + majorFaults.toString()); + } + } + } + instrumentation.sendStatus(0, mResult); + } + + /** + * Compile the app package using compilerFilter and return true or false + * based on status of the compilation command. + */ + private boolean compileApp(String compilerFilter, String appPkgName) throws IOException { + try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). + executeShellCommand(String.format(COMPILE_CMD, compilerFilter, appPkgName)); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( + new FileInputStream(result.getFileDescriptor())))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + if (line.contains(COMPILE_SUCCESS)) { + return true; + } + } + return false; + } + } + + /** + * If launch order is "cyclic" then apps will be launched one after the + * other for each iteration count. + * If launch order is "sequential" then each app will be launched for given number + * iterations at once before launching the other apps. + */ + private void setLaunchOrder() { + if (LAUNCH_ORDER_CYCLIC.equalsIgnoreCase(mLaunchOrder)) { + for (String compilerFilter : mCompilerFilters) { + if (mTrialLaunch) { + for (String app : mNameToResultKey.keySet()) { + mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH)); + } + } + for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) { + for (String app : mNameToResultKey.keySet()) { + mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, + String.format(LAUNCH_ITERATION, launchCount))); + } + } + if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) { + for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) { + for (String app : mNameToResultKey.keySet()) { + mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, + String.format(TRACE_ITERATION, traceCount))); + } + } + } + } + } else if (LAUNCH_ORDER_SEQUENTIAL.equalsIgnoreCase(mLaunchOrder)) { + for (String compilerFilter : mCompilerFilters) { + for (String app : mNameToResultKey.keySet()) { + if (mTrialLaunch) { + mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH)); + } + for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) { + mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, + String.format(LAUNCH_ITERATION, launchCount))); + } + if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) { + for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) { + mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, + String.format(TRACE_ITERATION, traceCount))); + } + } + } + } + } else { + assertTrue("Launch order is not valid parameter", false); + } + } + + private void dropCache() { + if (mDropCache) { + assertNotNull("Issue in dropping the cache", + getInstrumentation().getUiAutomation() + .executeShellCommand(DROP_CACHE_SCRIPT)); + } + } + + private void parseArgs(Bundle args) { + mNameToResultKey = new LinkedHashMap<String, String>(); + mNameToLaunchTime = new HashMap<>(); + String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS); + if (launchIterations != null) { + mLaunchIterations = Integer.parseInt(launchIterations); + } + String appList = args.getString(KEY_APPS); + if (appList == null) + return; + + String appNames[] = appList.split("\\|"); + for (String pair : appNames) { + String[] parts = pair.split("\\^"); + if (parts.length != 2) { + Log.e(TAG, "The apps key is incorrectly formatted"); + fail(); + } + + mNameToResultKey.put(parts[0], parts[1]); + mNameToLaunchTime.put(parts[0], null); + } + String requiredAccounts = args.getString(KEY_REQUIRED_ACCOUNTS); + if (requiredAccounts != null) { + mRequiredAccounts = new HashSet<String>(); + for (String accountType : requiredAccounts.split(",")) { + mRequiredAccounts.add(accountType); + } + } + + String compilerFilterList = args.getString(KEY_COMPILER_FILTERS); + if (compilerFilterList != null) { + // If a compiler filter is passed, we make a trial launch to force compilation + // of the apps. + mTrialLaunch = true; + mCompilerFilters = compilerFilterList.split("\\|"); + } else { + // Just pass a null compiler filter to use the current state of the app. + mCompilerFilters = new String[1]; + } + + // Pre-populate the results map to avoid null checks. + for (String app : mNameToLaunchTime.keySet()) { + HashMap<String, List<AppLaunchResult>> map = new HashMap<>(); + mNameToLaunchTime.put(app, map); + for (String compilerFilter : mCompilerFilters) { + map.put(compilerFilter, new ArrayList<>()); + } + } + + mTraceDirectoryStr = args.getString(KEY_TRACE_DIRECTORY); + mDropCache = Boolean.parseBoolean(args.getString(KEY_DROP_CACHE)); + mSimplePerfCmd = args.getString(KEY_SIMPLEPERF_CMD); + mLaunchOrder = args.getString(KEY_LAUNCH_ORDER, LAUNCH_ORDER_CYCLIC); + mSimplePerfAppOnly = Boolean.parseBoolean(args.getString(KEY_SIMPLEPERF_APP)); + mTrialLaunch = mTrialLaunch || Boolean.parseBoolean(args.getString(KEY_TRIAL_LAUNCH)); + + if (mSimplePerfCmd != null && mSimplePerfAppOnly) { + Log.w(TAG, String.format("Passing both %s and %s is not supported, ignoring %s", + KEY_SIMPLEPERF_CMD, KEY_SIMPLEPERF_APP)); + } + } + + private boolean hasLeanback(Context context) { + return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); + } + + private void createMappings() { + mNameToIntent = new LinkedHashMap<String, Intent>(); + + PackageManager pm = getInstrumentation().getContext() + .getPackageManager(); + Intent intentToResolve = new Intent(Intent.ACTION_MAIN); + intentToResolve.addCategory(hasLeanback(getInstrumentation().getContext()) ? + Intent.CATEGORY_LEANBACK_LAUNCHER : + Intent.CATEGORY_LAUNCHER); + List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0); + resolveLoop(ris, intentToResolve, pm); + // For Wear + intentToResolve = new Intent(WEARABLE_ACTION_GOOGLE); + ris = pm.queryIntentActivities(intentToResolve, 0); + resolveLoop(ris, intentToResolve, pm); + } + + private void resolveLoop(List<ResolveInfo> ris, Intent intentToResolve, PackageManager pm) { + if (ris == null || ris.isEmpty()) { + Log.i(TAG, "Could not find any apps"); + } else { + for (ResolveInfo ri : ris) { + Intent startIntent = new Intent(intentToResolve); + startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + startIntent.setClassName(ri.activityInfo.packageName, + ri.activityInfo.name); + String appName = ri.loadLabel(pm).toString(); + if (appName != null) { + // Support launching intent using package name or app name + mNameToIntent.put(ri.activityInfo.packageName, startIntent); + mNameToIntent.put(appName, startIntent); + } + } + } + } + + private AppLaunchResult startApp(String appName, boolean forceStopBeforeLaunch, + String launchReason) throws NameNotFoundException, RemoteException { + Log.i(TAG, "Starting " + appName); + + Intent startIntent = mNameToIntent.get(appName); + if (startIntent == null) { + Log.w(TAG, "App does not exist: " + appName); + mResult.putString(mNameToResultKey.get(appName), "App does not exist"); + return new AppLaunchResult(); + } + AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, forceStopBeforeLaunch, + launchReason); + Thread t = new Thread(runnable); + t.start(); + try { + t.join(JOIN_TIMEOUT); + } catch (InterruptedException e) { + // ignore + } + return runnable.getResult(); + } + + private void checkAccountSignIn() { + // ensure that the device has the required account types before starting test + // e.g. device must have a valid Google account sign in to measure a meaningful launch time + // for Gmail + if (mRequiredAccounts == null || mRequiredAccounts.isEmpty()) { + return; + } + final AccountManager am = + (AccountManager) getInstrumentation().getTargetContext().getSystemService( + Context.ACCOUNT_SERVICE); + Account[] accounts = am.getAccounts(); + // use set here in case device has multiple accounts of the same type + Set<String> foundAccounts = new HashSet<String>(); + for (Account account : accounts) { + if (mRequiredAccounts.contains(account.type)) { + foundAccounts.add(account.type); + } + } + // check if account type matches, if not, fail test with message on what account types + // are missing + if (mRequiredAccounts.size() != foundAccounts.size()) { + mRequiredAccounts.removeAll(foundAccounts); + StringBuilder sb = new StringBuilder("Device missing these accounts:"); + for (String account : mRequiredAccounts) { + sb.append(' '); + sb.append(account); + } + fail(sb.toString()); + } + } + + private void closeApp(String appName, boolean forceStopApp) { + Intent homeIntent = new Intent(Intent.ACTION_MAIN); + homeIntent.addCategory(Intent.CATEGORY_HOME); + homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + getInstrumentation().getContext().startActivity(homeIntent); + sleep(POST_LAUNCH_IDLE_TIMEOUT); + if (forceStopApp) { + Intent startIntent = mNameToIntent.get(appName); + if (startIntent != null) { + String packageName = startIntent.getComponent().getPackageName(); + try { + mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); + } catch (RemoteException e) { + Log.w(TAG, "Error closing app", e); + } + } + } + } + + private void sleep(int time) { + try { + Thread.sleep(time); + } catch (InterruptedException e) { + // ignore + } + } + + private void reportError(String appName, String processName) { + ActivityManager am = (ActivityManager) getInstrumentation() + .getContext().getSystemService(Context.ACTIVITY_SERVICE); + List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState(); + if (crashes != null) { + for (ProcessErrorStateInfo crash : crashes) { + if (!crash.processName.equals(processName)) + continue; + + Log.w(TAG, appName + " crashed: " + crash.shortMsg); + mResult.putString(mNameToResultKey.get(appName), crash.shortMsg); + return; + } + } + + mResult.putString(mNameToResultKey.get(appName), + "Crashed for unknown reason"); + Log.w(TAG, appName + + " not found in process list, most likely it is crashed"); + } + + private class LaunchOrder { + private String mApp; + private String mCompilerFilter; + private String mLaunchReason; + + LaunchOrder(String app, String compilerFilter, String launchReason){ + mApp = app; + mCompilerFilter = compilerFilter; + mLaunchReason = launchReason; + } + + public String getApp() { + return mApp; + } + + public void setApp(String app) { + mApp = app; + } + + public String getCompilerFilter() { + return mCompilerFilter; + } + + public String getLaunchReason() { + return mLaunchReason; + } + + public void setLaunchReason(String launchReason) { + mLaunchReason = launchReason; + } + } + + private class AppLaunchResult { + long mLaunchTime; + long mCpuCycles; + long mMajorFaults; + + AppLaunchResult() { + mLaunchTime = -1L; + mCpuCycles = -1L; + mMajorFaults = -1L; + } + + AppLaunchResult(String launchTime, String cpuCycles, String majorFaults) { + try { + mLaunchTime = Long.parseLong(launchTime, 10); + mCpuCycles = Long.parseLong(cpuCycles, 10); + mMajorFaults = Long.parseLong(majorFaults, 10); + } catch (NumberFormatException e) { + Log.e(TAG, "Error parsing result", e); + } + } + } + + private class AppLaunchRunnable implements Runnable { + private Intent mLaunchIntent; + private AppLaunchResult mLaunchResult; + private boolean mForceStopBeforeLaunch; + private String mLaunchReason; + + public AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch, + String launchReason) { + mLaunchIntent = intent; + mForceStopBeforeLaunch = forceStopBeforeLaunch; + mLaunchReason = launchReason; + mLaunchResult = new AppLaunchResult(); + } + + public AppLaunchResult getResult() { + return mLaunchResult; + } + + public void run() { + File launchFile = null; + try { + String packageName = mLaunchIntent.getComponent().getPackageName(); + String componentName = mLaunchIntent.getComponent().flattenToShortString(); + if (mForceStopBeforeLaunch) { + mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); + } + String launchCmd = String.format("%s %s", APP_LAUNCH_CMD, componentName); + if (mSimplePerfAppOnly) { + try { + // executeShellCommand cannot handle shell specific actions, like '&'. + // Therefore, we create a file containing the command and make that + // the command to launch. + launchFile = File.createTempFile(LAUNCH_SCRIPT_NAME, ".sh"); + launchFile.setExecutable(true); + try (FileOutputStream stream = new FileOutputStream(launchFile); + BufferedWriter writer = + new BufferedWriter(new OutputStreamWriter(stream))) { + String cmd = String.format(SIMPLEPERF_APP_CMD, packageName, launchCmd); + writer.write(cmd); + } + launchCmd = launchFile.getAbsolutePath(); + } catch (IOException e) { + Log.w(TAG, "Error writing the launch command", e); + return; + } + } else if (null != mSimplePerfCmd) { + launchCmd = String.format("%s %s", mSimplePerfCmd, launchCmd); + } + Log.v(TAG, "Final launch cmd:" + launchCmd); + ParcelFileDescriptor parcelDesc = getInstrumentation().getUiAutomation() + .executeShellCommand(launchCmd); + mLaunchResult = parseLaunchTimeAndWrite(parcelDesc, String.format + ("App Launch :%s %s", componentName, mLaunchReason)); + } catch (RemoteException e) { + Log.w(TAG, "Error launching app", e); + } finally { + if (launchFile != null) { + launchFile.delete(); + } + } + } + + /** + * Method to parse the launch time info and write the result to file + * + * @param parcelDesc + * @return + */ + private AppLaunchResult parseLaunchTimeAndWrite(ParcelFileDescriptor parcelDesc, + String headerInfo) { + String launchTime = "-1"; + String cpuCycles = "-1"; + String majorFaults = "-1"; + boolean launchSuccess = false; + try { + InputStream inputStream = new FileInputStream(parcelDesc.getFileDescriptor()); + /* SAMPLE OUTPUT : + Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator } + Status: ok + Activity: com.google.android.calculator/com.android.calculator2.Calculator + ThisTime: 357 + TotalTime: 357 + WaitTime: 377 + Complete*/ + /* WITH SIMPLEPERF : + Performance counter statistics, + 6595722690,cpu-cycles,4.511040,GHz,(100%), + 0,major-faults,0.000,/sec,(100%), + Total test time,1.462129,seconds,*/ + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( + inputStream)); + String line = null; + int lineCount = 1; + mBufferedWriter.newLine(); + mBufferedWriter.write(headerInfo); + mBufferedWriter.newLine(); + while ((line = bufferedReader.readLine()) != null) { + if (lineCount == 2 && line.contains(SUCCESS_MESSAGE)) { + launchSuccess = true; + } + // Parse TotalTime which is the launch time + if (launchSuccess && lineCount == 5) { + String launchSplit[] = line.split(":"); + launchTime = launchSplit[1].trim(); + } + + if (mSimplePerfAppOnly) { + // Parse simpleperf output. + if (lineCount == 9) { + if (!line.contains("cpu-cycles")) { + Log.e(TAG, "Error in simpleperf output"); + } else { + cpuCycles = line.split(",")[0].trim(); + } + } else if (lineCount == 10) { + if (!line.contains("major-faults")) { + Log.e(TAG, "Error in simpleperf output"); + } else { + majorFaults = line.split(",")[0].trim(); + } + } + } + mBufferedWriter.write(line); + mBufferedWriter.newLine(); + lineCount++; + } + mBufferedWriter.flush(); + inputStream.close(); + } catch (IOException e) { + Log.w(TAG, "Error writing the launch file", e); + } + return new AppLaunchResult(launchTime, cpuCycles, majorFaults); + } + + } +} |