diff --git a/wa/workloads/applaunch/__init__.py b/wa/workloads/applaunch/__init__.py index 4a58ca045..b2277e6d7 100644 --- a/wa/workloads/applaunch/__init__.py +++ b/wa/workloads/applaunch/__init__.py @@ -14,7 +14,7 @@ # # pylint: disable=attribute-defined-outside-init -from wa import ApkUiautoWorkload, Parameter +from wa import ApkUiautoWorkload, Parameter, TargetError from wa.framework import pluginloader @@ -89,6 +89,54 @@ class Applaunch(ApkUiautoWorkload): """), ] + def __init__(self, target, **kwargs): + super(Applaunch, self).__init__(target, **kwargs) + # Android doesn't allow to writable dex files starting 14 version and + # default location (/sdcard/devlib-target) doesn't support readonly files + # so we use /data/local/tmp as asset directory for this workload. + self.asset_directory = '/data/local/tmp' + self._su_has_command_option = None + + def workload_apk(self): + return self.deployed_assets[-1] + + def initialize(self, context): + super(Applaunch, self).initialize(context) + + worload_apk = self.workload_apk() + self.gui.uiauto_params['workload_apk'] = worload_apk + + # Make workload apk readonly to comply with Android >= 14. + if self.target.get_sdk_version() >= 34: + self.target.execute(f'chmod -w {worload_apk}') + + # Check installed su version and return whether it supports -c argument. + # + # Targets can have different versions of su + # - Targets with engineering Android version have su with following usage: + # su [WHO [COMMAND...]] + # - Targets with rooted user Android version have su that supports passing + # command via -c argument. + def su_has_command_option(self): + if self._su_has_command_option is None: + try: + self.target.execute('su -c id') + self._su_has_command_option = True + except TargetError: + self._su_has_command_option = False + + if self._su_has_command_option is False: + try: + self.target.execute('su root id') + except TargetError: + raise WorkloadError( + 'su must be installed and support passing command ' + 'via -c argument (su -c ) or as positional ' + 'argument after user (su )' + ) + + return self._su_has_command_option + def init_resources(self, context): super(Applaunch, self).init_resources(context) self.workload_params['markers_enabled'] = True @@ -112,6 +160,7 @@ def pass_parameters(self): self.gui.uiauto_params['launch_activity'] = "None" self.gui.uiauto_params['applaunch_type'] = self.applaunch_type self.gui.uiauto_params['applaunch_iterations'] = self.applaunch_iterations + self.gui.uiauto_params['su_has_command_option'] = self.su_has_command_option() def setup(self, context): self.workload.gui.uiauto_params['package_name'] = self.workload.apk.apk_info.package diff --git a/wa/workloads/applaunch/com.arm.wa.uiauto.applaunch.apk b/wa/workloads/applaunch/com.arm.wa.uiauto.applaunch.apk index 6d78f8cc5..14fc12352 100644 Binary files a/wa/workloads/applaunch/com.arm.wa.uiauto.applaunch.apk and b/wa/workloads/applaunch/com.arm.wa.uiauto.applaunch.apk differ diff --git a/wa/workloads/applaunch/uiauto/app/build.gradle b/wa/workloads/applaunch/uiauto/app/build.gradle index 82f254420..77986e040 100644 --- a/wa/workloads/applaunch/uiauto/app/build.gradle +++ b/wa/workloads/applaunch/uiauto/app/build.gradle @@ -3,12 +3,12 @@ apply plugin: 'com.android.application' def packageName = "com.arm.wa.uiauto.applaunch" android { - compileSdkVersion 28 - buildToolsVersion "28.0.3" + namespace "${packageName}" + compileSdkVersion 34 defaultConfig { applicationId "${packageName}" minSdkVersion 18 - targetSdkVersion 28 + targetSdkVersion 34 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { @@ -25,11 +25,5 @@ dependencies { implementation 'com.android.support.test:runner:0.5' implementation 'com.android.support.test:rules:0.5' implementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2' - implementation(name: 'uiauto', ext:'aar') -} - -repositories { - flatDir { - dirs 'libs' - } + implementation files('libs/uiauto.aar') } diff --git a/wa/workloads/applaunch/uiauto/app/src/main/AndroidManifest.xml b/wa/workloads/applaunch/uiauto/app/src/main/AndroidManifest.xml index f8275d577..ac95132ef 100644 --- a/wa/workloads/applaunch/uiauto/app/src/main/AndroidManifest.xml +++ b/wa/workloads/applaunch/uiauto/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ diff --git a/wa/workloads/applaunch/uiauto/app/src/main/java/com/arm/wa/uiauto/applaunch/UiAutomation.java b/wa/workloads/applaunch/uiauto/app/src/main/java/com/arm/wa/uiauto/applaunch/UiAutomation.java index 32dbd867c..661dadadc 100755 --- a/wa/workloads/applaunch/uiauto/app/src/main/java/com/arm/wa/uiauto/applaunch/UiAutomation.java +++ b/wa/workloads/applaunch/uiauto/app/src/main/java/com/arm/wa/uiauto/applaunch/UiAutomation.java @@ -15,7 +15,9 @@ package com.arm.wa.uiauto.applaunch; +import android.os.Build; import android.os.Bundle; +import android.os.ParcelFileDescriptor; import android.support.test.runner.AndroidJUnit4; import android.support.test.uiautomator.UiObject; import android.util.Log; @@ -30,7 +32,10 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.io.BufferedReader; +import java.io.IOException; import java.io.File; +import java.io.InputStreamReader; import dalvik.system.DexClassLoader; @@ -52,18 +57,19 @@ public class UiAutomation extends BaseUiAutomation { protected Bundle parameters; protected String packageName; protected String packageID; + protected boolean suHasCommandOption; @Before public void initialize() throws Exception { parameters = getParams(); packageID = getPackageID(parameters); + suHasCommandOption = parameters.getBoolean("su_has_command_option"); + // Get workload apk file parameters - String packageName = parameters.getString("package_name"); + packageName = parameters.getString("package_name"); String workload = parameters.getString("workload"); - String workloadAPKPath = parameters.getString("workdir"); - String workloadName = String.format("com.arm.wa.uiauto.%1s.apk", workload); - String workloadAPKFile = String.format("%1s/%2s", workloadAPKPath, workloadName); + String workloadAPKFile = parameters.getString("workload_apk"); // Load the apk file File apkFile = new File(workloadAPKFile); @@ -153,7 +159,6 @@ private class AppLaunch { private String testTag; private String launchCommand; private ActionLogger logger; - Process launch_p; public AppLaunch(String testTag, String launchCommand) { this.testTag = testTag; @@ -169,24 +174,13 @@ public void startLaunch() throws Exception{ // Launches the application. public void launchMain() throws Exception{ - launch_p = Runtime.getRuntime().exec(launchCommand); - launchValidate(launch_p); - } - - // Called by launchMain() to check if app launch is successful - public void launchValidate(Process launch_p) throws Exception { - launch_p.waitFor(); - Integer exit_val = launch_p.exitValue(); - if (exit_val != 0) { - throw new Exception("Application could not be launched"); - } + executeShellCommand(launchCommand); } // Marks the end of application launch of the workload. public void endLaunch() throws Exception{ waitObject(launchEndObject, launch_timeout); logger.stop(); - launch_p.destroy(); } } @@ -203,15 +197,14 @@ else if(applaunchType.equals("launch_from_long-idle")) { // Kills the application process public void killApplication() throws Exception{ - Process kill_p; String command = String.format("am force-stop %s", packageName); - kill_p = Runtime.getRuntime().exec(new String[] { "su", "-c", command}); - kill_p.waitFor(); - kill_p.destroy(); + executeShellCommand(command); } // Kills the background processes public void killBackground() throws Exception{ + // Workload has KILL_BACKGROUND_PROCESSES permission so it can execute following + // command and it is not necessary to execute it with shell permissions. Process kill_p; kill_p = Runtime.getRuntime().exec("am kill-all"); kill_p.waitFor(); @@ -220,15 +213,80 @@ public void killBackground() throws Exception{ // Drop the caches public void dropCaches() throws Exception{ - Process sync; - sync = Runtime.getRuntime().exec(new String[] { "su", "-c", "sync"}); - sync.waitFor(); - sync.destroy(); - - Process drop_cache; - String command = "echo 3 > /proc/sys/vm/drop_caches"; - drop_cache = Runtime.getRuntime().exec(new String[] { "su", "-c", command}); - drop_cache.waitFor(); - drop_cache.destroy(); + executeShellCommand("sync", /*asRoot=*/true); + executeShellCommand("echo 3 > /proc/sys/vm/drop_caches", /*asRoot=*/true); + } + + private String getSuCommand(String command) { + if (suHasCommandOption) { + return String.format("su -c '%s'", command); + } + // If su doesn't support -c argument we assume that it has following usage: + // su [WHO [COMMAND]] + // that corresponds to su from engineering Android version. + return String.format("su root %s", command); + } + + private void executeShellCommand(String command) throws Exception { + executeShellCommand(command, /*asRoot=*/false); + } + + private static ParcelFileDescriptor[] executeShellCommand(android.app.UiAutomation uiAutomation, + String command) throws IOException { + ParcelFileDescriptor[] result = new ParcelFileDescriptor[2]; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + ParcelFileDescriptor[] fds = uiAutomation.executeShellCommandRwe(command); + fds[1].close(); // close stdin + result[0] = fds[0]; // stdout + result[1] = fds[2]; // stderr + return result; + } + + result[0] = uiAutomation.executeShellCommand(command); + return result; + } + + private void executeShellCommand(String command, boolean asRoot) throws Exception { + android.app.UiAutomation uiAutomation = mInstrumentation.getUiAutomation(); + + String shellCommand = command; + if (asRoot) { + shellCommand = getSuCommand(command); + } + + Log.d("Shell command: ", shellCommand); + ParcelFileDescriptor[] fds = UiAutomation.executeShellCommand(uiAutomation, command); + ParcelFileDescriptor fdOut = fds[0]; + ParcelFileDescriptor fdErr = fds[1]; + + String out = readStreamAndClose(fdOut); + Log.d("Shell out: ", out); + + String err = readStreamAndClose(fdErr); + if (!err.isEmpty()) { + Log.e("Shell err: ", err); + String msg = String.format("Shell command '%s' failed with error: '%s'", command, err); + throw new Exception(msg); + } + } + + private static String readStreamAndClose(ParcelFileDescriptor fd) throws IOException { + if (fd == null) { + return ""; + } + + try (BufferedReader in = new BufferedReader(new InputStreamReader( + new ParcelFileDescriptor.AutoCloseInputStream(fd)))) { + StringBuilder sb = new StringBuilder(); + while (true) { + String line = in.readLine(); + if (line == null) { + break; + } + sb.append(line); + sb.append('\n'); + } + return sb.toString(); + } } } diff --git a/wa/workloads/applaunch/uiauto/build.gradle b/wa/workloads/applaunch/uiauto/build.gradle index a40e2d1bc..c7074121f 100644 --- a/wa/workloads/applaunch/uiauto/build.gradle +++ b/wa/workloads/applaunch/uiauto/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' + classpath 'com.android.tools.build:gradle:8.4.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/wa/workloads/applaunch/uiauto/gradle/wrapper/gradle-wrapper.properties b/wa/workloads/applaunch/uiauto/gradle/wrapper/gradle-wrapper.properties index 4acb4f9cb..2fc16c0a3 100644 --- a/wa/workloads/applaunch/uiauto/gradle/wrapper/gradle-wrapper.properties +++ b/wa/workloads/applaunch/uiauto/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip