Skip to content

Commit d9c2ccd

Browse files
author
Konstantin Baladurin
committed
Fix applaunch workload
This patch fixes following issues: * Starting from Android 11 it is not possible to use Runtime.exec to execute commands that requires shell permissions like `am start`. * Engineering Android versions has su that doesn't support -c argument.
1 parent e0bf766 commit d9c2ccd

File tree

7 files changed

+145
-45
lines changed

7 files changed

+145
-45
lines changed

wa/workloads/applaunch/__init__.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
#
1515
# pylint: disable=attribute-defined-outside-init
1616

17-
from wa import ApkUiautoWorkload, Parameter
17+
from wa import ApkUiautoWorkload, Parameter, TargetError
1818
from wa.framework import pluginloader
1919

2020

@@ -89,6 +89,54 @@ class Applaunch(ApkUiautoWorkload):
8989
"""),
9090
]
9191

92+
def __init__(self, target, **kwargs):
93+
super(Applaunch, self).__init__(target, **kwargs)
94+
# Android doesn't allow to writable dex files starting 14 version and
95+
# default location (/sdcard/devlib-target) doesn't support readonly files
96+
# so we use /data/local/tmp as asset directory for this workload.
97+
self.asset_directory = '/data/local/tmp'
98+
self._su_has_command_option = None
99+
100+
def workload_apk(self):
101+
return self.deployed_assets[-1]
102+
103+
def initialize(self, context):
104+
super(Applaunch, self).initialize(context)
105+
106+
worload_apk = self.workload_apk()
107+
self.gui.uiauto_params['workload_apk'] = worload_apk
108+
109+
# Make workload apk readonly to comply with Android >= 14.
110+
if self.target.get_sdk_version() >= 34:
111+
self.target.execute(f'chmod -w {worload_apk}')
112+
113+
# Check installed su version and return whether it supports -c argument.
114+
#
115+
# Targets can have different versions of su
116+
# - Targets with engineering Android version have su with following usage:
117+
# su [WHO [COMMAND...]]
118+
# - Targets with rooted user Android version have su that supports passing
119+
# command via -c argument.
120+
def su_has_command_option(self):
121+
if self._su_has_command_option is None:
122+
try:
123+
self.target.execute('su -c id')
124+
self._su_has_command_option = True
125+
except TargetError:
126+
self._su_has_command_option = False
127+
128+
if self._su_has_command_option is False:
129+
try:
130+
self.target.execute('su root id')
131+
except TargetError:
132+
raise WorkloadError(
133+
'su must be installed and support passing command '
134+
'via -c argument (su -c <command>) or as positional '
135+
'argument after user (su <user> <command>)'
136+
)
137+
138+
return self._su_has_command_option
139+
92140
def init_resources(self, context):
93141
super(Applaunch, self).init_resources(context)
94142
self.workload_params['markers_enabled'] = True
@@ -112,6 +160,7 @@ def pass_parameters(self):
112160
self.gui.uiauto_params['launch_activity'] = "None"
113161
self.gui.uiauto_params['applaunch_type'] = self.applaunch_type
114162
self.gui.uiauto_params['applaunch_iterations'] = self.applaunch_iterations
163+
self.gui.uiauto_params['su_has_command_option'] = self.su_has_command_option()
115164

116165
def setup(self, context):
117166
self.workload.gui.uiauto_params['package_name'] = self.workload.apk.apk_info.package
1.36 KB
Binary file not shown.

wa/workloads/applaunch/uiauto/app/build.gradle

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ apply plugin: 'com.android.application'
33
def packageName = "com.arm.wa.uiauto.applaunch"
44

55
android {
6-
compileSdkVersion 28
7-
buildToolsVersion "28.0.3"
6+
namespace "${packageName}"
7+
compileSdkVersion 34
88
defaultConfig {
99
applicationId "${packageName}"
1010
minSdkVersion 18
11-
targetSdkVersion 28
11+
targetSdkVersion 34
1212
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
1313
}
1414
buildTypes {
@@ -25,11 +25,5 @@ dependencies {
2525
implementation 'com.android.support.test:runner:0.5'
2626
implementation 'com.android.support.test:rules:0.5'
2727
implementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
28-
implementation(name: 'uiauto', ext:'aar')
29-
}
30-
31-
repositories {
32-
flatDir {
33-
dirs 'libs'
34-
}
28+
implementation files('libs/uiauto.aar')
3529
}

wa/workloads/applaunch/uiauto/app/src/main/AndroidManifest.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3-
package="com.arm.wa.uiauto.applaunch"
43
android:versionCode="1"
54
android:versionName="1.0"
65
android:allowBackup="false">

wa/workloads/applaunch/uiauto/app/src/main/java/com/arm/wa/uiauto/applaunch/UiAutomation.java

Lines changed: 89 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515

1616
package com.arm.wa.uiauto.applaunch;
1717

18+
import android.os.Build;
1819
import android.os.Bundle;
20+
import android.os.ParcelFileDescriptor;
1921
import android.support.test.runner.AndroidJUnit4;
2022
import android.support.test.uiautomator.UiObject;
2123
import android.util.Log;
@@ -30,7 +32,10 @@
3032
import org.junit.Test;
3133
import org.junit.runner.RunWith;
3234

35+
import java.io.BufferedReader;
36+
import java.io.IOException;
3337
import java.io.File;
38+
import java.io.InputStreamReader;
3439

3540
import dalvik.system.DexClassLoader;
3641

@@ -52,18 +57,19 @@ public class UiAutomation extends BaseUiAutomation {
5257
protected Bundle parameters;
5358
protected String packageName;
5459
protected String packageID;
60+
protected boolean suHasCommandOption;
5561

5662
@Before
5763
public void initialize() throws Exception {
5864
parameters = getParams();
5965
packageID = getPackageID(parameters);
6066

67+
suHasCommandOption = parameters.getBoolean("su_has_command_option");
68+
6169
// Get workload apk file parameters
62-
String packageName = parameters.getString("package_name");
70+
packageName = parameters.getString("package_name");
6371
String workload = parameters.getString("workload");
64-
String workloadAPKPath = parameters.getString("workdir");
65-
String workloadName = String.format("com.arm.wa.uiauto.%1s.apk", workload);
66-
String workloadAPKFile = String.format("%1s/%2s", workloadAPKPath, workloadName);
72+
String workloadAPKFile = parameters.getString("workload_apk");
6773

6874
// Load the apk file
6975
File apkFile = new File(workloadAPKFile);
@@ -153,7 +159,6 @@ private class AppLaunch {
153159
private String testTag;
154160
private String launchCommand;
155161
private ActionLogger logger;
156-
Process launch_p;
157162

158163
public AppLaunch(String testTag, String launchCommand) {
159164
this.testTag = testTag;
@@ -169,24 +174,13 @@ public void startLaunch() throws Exception{
169174

170175
// Launches the application.
171176
public void launchMain() throws Exception{
172-
launch_p = Runtime.getRuntime().exec(launchCommand);
173-
launchValidate(launch_p);
174-
}
175-
176-
// Called by launchMain() to check if app launch is successful
177-
public void launchValidate(Process launch_p) throws Exception {
178-
launch_p.waitFor();
179-
Integer exit_val = launch_p.exitValue();
180-
if (exit_val != 0) {
181-
throw new Exception("Application could not be launched");
182-
}
177+
executeShellCommand(launchCommand);
183178
}
184179

185180
// Marks the end of application launch of the workload.
186181
public void endLaunch() throws Exception{
187182
waitObject(launchEndObject, launch_timeout);
188183
logger.stop();
189-
launch_p.destroy();
190184
}
191185
}
192186

@@ -203,15 +197,14 @@ else if(applaunchType.equals("launch_from_long-idle")) {
203197

204198
// Kills the application process
205199
public void killApplication() throws Exception{
206-
Process kill_p;
207200
String command = String.format("am force-stop %s", packageName);
208-
kill_p = Runtime.getRuntime().exec(new String[] { "su", "-c", command});
209-
kill_p.waitFor();
210-
kill_p.destroy();
201+
executeShellCommand(command);
211202
}
212203

213204
// Kills the background processes
214205
public void killBackground() throws Exception{
206+
// Workload has KILL_BACKGROUND_PROCESSES permission so it can execute following
207+
// command and it is not necessary to execute it with shell permissions.
215208
Process kill_p;
216209
kill_p = Runtime.getRuntime().exec("am kill-all");
217210
kill_p.waitFor();
@@ -220,15 +213,80 @@ public void killBackground() throws Exception{
220213

221214
// Drop the caches
222215
public void dropCaches() throws Exception{
223-
Process sync;
224-
sync = Runtime.getRuntime().exec(new String[] { "su", "-c", "sync"});
225-
sync.waitFor();
226-
sync.destroy();
227-
228-
Process drop_cache;
229-
String command = "echo 3 > /proc/sys/vm/drop_caches";
230-
drop_cache = Runtime.getRuntime().exec(new String[] { "su", "-c", command});
231-
drop_cache.waitFor();
232-
drop_cache.destroy();
216+
executeShellCommand("sync", /*asRoot=*/true);
217+
executeShellCommand("echo 3 > /proc/sys/vm/drop_caches", /*asRoot=*/true);
218+
}
219+
220+
private String getSuCommand(String command) {
221+
if (suHasCommandOption) {
222+
return String.format("su -c '%s'", command);
223+
}
224+
// If su doesn't support -c argument we assume that it has following usage:
225+
// su [WHO [COMMAND]]
226+
// that corresponds to su from engineering Android version.
227+
return String.format("su root %s", command);
228+
}
229+
230+
private void executeShellCommand(String command) throws Exception {
231+
executeShellCommand(command, /*asRoot=*/false);
232+
}
233+
234+
private static ParcelFileDescriptor[] executeShellCommand(android.app.UiAutomation uiAutomation,
235+
String command) throws IOException {
236+
ParcelFileDescriptor[] result = new ParcelFileDescriptor[2];
237+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
238+
ParcelFileDescriptor[] fds = uiAutomation.executeShellCommandRwe(command);
239+
fds[1].close(); // close stdin
240+
result[0] = fds[0]; // stdout
241+
result[1] = fds[2]; // stderr
242+
return result;
243+
}
244+
245+
result[0] = uiAutomation.executeShellCommand(command);
246+
return result;
247+
}
248+
249+
private void executeShellCommand(String command, boolean asRoot) throws Exception {
250+
android.app.UiAutomation uiAutomation = mInstrumentation.getUiAutomation();
251+
252+
String shellCommand = command;
253+
if (asRoot) {
254+
shellCommand = getSuCommand(command);
255+
}
256+
257+
Log.d("Shell command: ", shellCommand);
258+
ParcelFileDescriptor[] fds = UiAutomation.executeShellCommand(uiAutomation, command);
259+
ParcelFileDescriptor fdOut = fds[0];
260+
ParcelFileDescriptor fdErr = fds[1];
261+
262+
String out = readStreamAndClose(fdOut);
263+
Log.d("Shell out: ", out);
264+
265+
String err = readStreamAndClose(fdErr);
266+
if (!err.isEmpty()) {
267+
Log.e("Shell err: ", err);
268+
String msg = String.format("Shell command '%s' failed with error: '%s'", command, err);
269+
throw new Exception(msg);
270+
}
271+
}
272+
273+
private static String readStreamAndClose(ParcelFileDescriptor fd) throws IOException {
274+
if (fd == null) {
275+
return "";
276+
}
277+
278+
try (BufferedReader in = new BufferedReader(new InputStreamReader(
279+
new ParcelFileDescriptor.AutoCloseInputStream(fd)))) {
280+
StringBuilder sb = new StringBuilder();
281+
while (true) {
282+
String line = in.readLine();
283+
if (line == null) {
284+
break;
285+
}
286+
sb.append(line);
287+
sb.append('\n');
288+
}
289+
return sb.toString();
290+
}
233291
}
234292
}

wa/workloads/applaunch/uiauto/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ buildscript {
66
google()
77
}
88
dependencies {
9-
classpath 'com.android.tools.build:gradle:7.2.1'
9+
classpath 'com.android.tools.build:gradle:8.4.0'
1010

1111
// NOTE: Do not place your application dependencies here; they belong
1212
// in the individual module build.gradle files

wa/workloads/applaunch/uiauto/gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6-
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
6+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip

0 commit comments

Comments
 (0)