Skip to content

Commit 31f4394

Browse files
Update screen recorder for tests of android_client, to make sure the screen recording is performed correctly (#469)
* Update screen recorder for tests of android_client, to make sure the screen recording is performed correctly. * Add compatibility in code for android client tests * Update command for device action usage. * Abstract retry logic into ADBOperateUtil.pullFileToDir; * Update var name with log * Update flag * Update related gradle plugin config
1 parent 889005a commit 31f4394

File tree

11 files changed

+116
-77
lines changed

11 files changed

+116
-77
lines changed

agent/src/main/java/com/microsoft/hydralab/agent/command/DeviceScriptCommandLoader.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,15 @@ private List<DeviceAction> command2Action(DeviceScriptCommand deviceCommand) {
6464
}
6565

6666
private enum ActionConverter {
67-
//generate action by command type
67+
/**
68+
* Generate action by command type.
69+
* ADBShell: execute command on mobile device only
70+
* AgentShell: execute command on agent only
71+
* - type = Agent: run for each Agent device, e.g. Windows, Mac, Linux. The commands would be run for only once.
72+
* Used for environment setup and build generation.
73+
* - type = Android: run for each Android device. The commands would be run on each Android device.
74+
* Used for specific device operation through PC.
75+
*/
6876
ADBShell() {
6977
@Override
7078
public DeviceAction getAction(String commandline, String deviceType) {

agent/src/main/java/com/microsoft/hydralab/agent/runner/ActionExecutor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public class ActionExecutor {
4040
private final Set<String> actionTypes =
4141
Set.of("setProperty", "setDefaultLauncher", "backToHome", "changeGlobalSetting",
4242
"changeSystemSetting", "execCommandOnDevice", "execCommandOnAgent", "pushFileToDevice",
43-
"pullFileFromDevice");
43+
"pullFileFromDevice", "addToBatteryWhiteList");
4444

4545
public List<Exception> doActions(@NotNull DeviceDriver deviceDriverManager,
4646
@NotNull TestRunDevice testRunDevice,

agent/src/main/java/com/microsoft/hydralab/agent/runner/TestRunner.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import com.microsoft.hydralab.common.entity.common.TestRunDevice;
1414
import com.microsoft.hydralab.common.entity.common.TestTask;
1515
import com.microsoft.hydralab.common.management.AgentManagementService;
16+
import com.microsoft.hydralab.common.screen.PhoneAppScreenRecorder;
17+
import com.microsoft.hydralab.common.util.Const;
1618
import com.microsoft.hydralab.common.util.DateUtil;
1719
import com.microsoft.hydralab.common.util.FlowUtil;
1820
import com.microsoft.hydralab.common.util.LogUtils;
@@ -25,8 +27,10 @@
2527
import org.slf4j.LoggerFactory;
2628

2729
import java.io.File;
30+
import java.util.ArrayList;
2831
import java.util.Date;
2932
import java.util.List;
33+
import java.util.Map;
3034
import java.util.concurrent.ExecutionException;
3135
import java.util.concurrent.FutureTask;
3236
import java.util.concurrent.TimeUnit;
@@ -192,6 +196,16 @@ protected TestRun createTestRun(TestRunDevice testRunDevice, TestTask testTask)
192196
}
193197

194198
protected void setUp(TestRunDevice testRunDevice, TestTask testTask, TestRun testRun) throws Exception {
199+
// grant battery white list when testing android_client
200+
if (PhoneAppScreenRecorder.RECORD_PACKAGE_NAME.equals(testTask.getPkgName())) {
201+
Map<String, List<DeviceAction>> deviceActionsMap = testTask.getDeviceActions();
202+
List<DeviceAction> setUpDeviceActions = deviceActionsMap.getOrDefault(DeviceAction.When.SET_UP, new ArrayList<>());
203+
DeviceAction deviceAction1 = new DeviceAction(Const.OperatedDevice.ANDROID, "addToBatteryWhiteList");
204+
deviceAction1.setArgs(List.of(PhoneAppScreenRecorder.RECORD_PACKAGE_NAME));
205+
setUpDeviceActions.add(deviceAction1);
206+
deviceActionsMap.put(DeviceAction.When.SET_UP, setUpDeviceActions);
207+
}
208+
195209
testRunDeviceOrchestrator.killAll(testRunDevice);
196210
// this key will be used to recover device status when lost the connection between agent and master
197211
testRunDeviceOrchestrator.addCurrentTask(testRunDevice, testTask);

common/src/main/java/com/microsoft/hydralab/common/entity/common/DeviceInfo.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class DeviceInfo extends MobileDevice {
4141
private String deviceId;
4242
private String runningTaskId;
4343
private String runningTestName;
44+
private String runningTaskPackageName;
4445
private String agentId;
4546
private Set<String> deviceGroup = new HashSet<>();
4647
private boolean supportScreenRecording = true;
@@ -90,17 +91,20 @@ public void addCurrentTask(TestTask testTask) {
9091
this.currentTask.put(Thread.currentThread(), testTask);
9192
this.status = DeviceInfo.TESTING;
9293
this.runningTaskId = testTask.getId();
94+
this.runningTaskPackageName = testTask.getPkgName();
9395
}
9496

9597
public void finishTask() {
9698
this.currentTask.remove(Thread.currentThread());
9799
this.status = DeviceInfo.ONLINE;
98100
this.runningTaskId = null;
101+
this.runningTaskPackageName = null;
99102
}
100103

101104
public void reset() {
102105
this.status = DeviceInfo.ONLINE;
103106
this.runningTaskId = null;
107+
this.runningTaskPackageName = null;
104108
killAll();
105109
}
106110

common/src/main/java/com/microsoft/hydralab/common/management/device/impl/AndroidDeviceDriver.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.microsoft.hydralab.common.management.AgentManagementService;
2222
import com.microsoft.hydralab.common.management.AppiumServerManager;
2323
import com.microsoft.hydralab.common.management.device.DeviceType;
24+
import com.microsoft.hydralab.common.screen.ADBScreenRecorder;
2425
import com.microsoft.hydralab.common.screen.PhoneAppScreenRecorder;
2526
import com.microsoft.hydralab.common.screen.ScreenRecorder;
2627
import com.microsoft.hydralab.common.util.ADBOperateUtil;
@@ -43,7 +44,6 @@
4344
import java.awt.image.BufferedImage;
4445
import java.io.File;
4546
import java.io.IOException;
46-
import java.util.ArrayList;
4747
import java.util.Arrays;
4848
import java.util.HashMap;
4949
import java.util.HashSet;
@@ -61,7 +61,7 @@
6161
import static com.android.ddmlib.IDevice.PROP_DEVICE_CPU_ABI_LIST;
6262
import static com.android.ddmlib.IDevice.PROP_DEVICE_MANUFACTURER;
6363
import static com.android.ddmlib.IDevice.PROP_DEVICE_MODEL;
64-
import static com.microsoft.hydralab.common.screen.PhoneAppScreenRecorder.recordPackageName;
64+
import static com.microsoft.hydralab.common.screen.PhoneAppScreenRecorder.RECORD_PACKAGE_NAME;
6565

6666
public class AndroidDeviceDriver extends AbstractDeviceDriver {
6767

@@ -355,6 +355,9 @@ public void pullFileFromDevice(@NotNull DeviceInfo deviceInfo, @NotNull String p
355355

356356
@Override
357357
public ScreenRecorder getScreenRecorder(DeviceInfo deviceInfo, File folder, Logger logger) {
358+
if (PhoneAppScreenRecorder.RECORD_PACKAGE_NAME.equals(deviceInfo.getRunningTaskPackageName())) {
359+
return new ADBScreenRecorder(this, this.adbOperateUtil, deviceInfo, logger, folder);
360+
}
358361
return new PhoneAppScreenRecorder(this, this.adbOperateUtil, deviceInfo, folder, logger);
359362
}
360363

@@ -652,7 +655,7 @@ private void stopPackageProcess(DeviceInfo deviceInfo, String packageName, Logge
652655
private void startRecordActivity(DeviceInfo deviceInfo, Logger logger) {
653656
try {
654657
adbOperateUtil.execOnDevice(Objects.requireNonNull(deviceInfo),
655-
"am start -n " + recordPackageName + "/.MainActivity",
658+
"am start -n " + RECORD_PACKAGE_NAME + "/.MainActivity",
656659
new MultiLineNoCancelLoggingReceiver(logger), logger);
657660
} catch (Exception e) {
658661
logger.error(e.getMessage(), e);

common/src/main/java/com/microsoft/hydralab/common/screen/ADBScreenRecorder.java

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
3+
34
package com.microsoft.hydralab.common.screen;
45

56
import cn.hutool.core.thread.ThreadUtil;
67
import com.microsoft.hydralab.common.entity.common.DeviceInfo;
8+
import com.microsoft.hydralab.common.management.device.DeviceDriver;
79
import com.microsoft.hydralab.common.util.ADBOperateUtil;
810
import com.microsoft.hydralab.common.util.DateUtil;
911
import com.microsoft.hydralab.common.util.ThreadUtils;
@@ -25,14 +27,16 @@ public class ADBScreenRecorder implements ScreenRecorder {
2527
private final File baseFolder;
2628

2729
private File mergedVideo;
30+
private final DeviceDriver deviceDriver;
2831
public int preSleepSeconds = 0;
2932
ADBOperateUtil adbOperateUtil;
3033
private Process recordingProcess;
3134
private Thread recordingThread;
3235
private boolean shouldStop = true;
3336
private boolean shouldInterrupt = false;
3437

35-
public ADBScreenRecorder(ADBOperateUtil adbOperateUtil, DeviceInfo deviceInfo, Logger logger, File baseFolder) {
38+
public ADBScreenRecorder(DeviceDriver deviceDriver, ADBOperateUtil adbOperateUtil, DeviceInfo deviceInfo, Logger logger, File baseFolder) {
39+
this.deviceDriver = deviceDriver;
3640
this.adbOperateUtil = adbOperateUtil;
3741
this.deviceInfo = deviceInfo;
3842
this.logger = logger;
@@ -50,7 +54,6 @@ public void setPreSleepSeconds(int preSleepSeconds) {
5054

5155
@Override
5256
public void setupDevice() {
53-
5457
}
5558

5659
@Override
@@ -71,12 +74,12 @@ public void startRecord(int maxTimeInSecond) {
7174
int totalTime = 0;
7275
List<File> list = new ArrayList<>();
7376
while (totalTime < maxTimeInSecond && !shouldStop) {
74-
String fileName = String.format("/sdcard/scr_rec_%d_%d.mp4", totalTime, totalTime + timeSpan);
75-
String command = String.format("shell screenrecord --bit-rate 3200000 --time-limit %d %s", timeSpan, fileName);
76-
deviceInfo.addCurrentCommand(command);
77+
String pathOnDevice = String.format("/sdcard/scr_rec_%d_%d.mp4", totalTime, totalTime + timeSpan);
78+
String recordCommand = String.format("shell screenrecord --bit-rate 3200000 --time-limit %d %s", timeSpan, pathOnDevice);
79+
deviceInfo.addCurrentCommand(recordCommand);
7780
// Blocking command
78-
recordingProcess = adbOperateUtil.executeDeviceCommandOnPC(deviceInfo, command, logger);
79-
logger.info("ADBDeviceScreenRecorder>> command: " + command);
81+
recordingProcess = adbOperateUtil.executeDeviceCommandOnPC(deviceInfo, recordCommand, logger);
82+
logger.info("ADBDeviceScreenRecorder>> command: " + recordCommand);
8083
logger.info(IOUtils.toString(recordingProcess.getInputStream(), StandardCharsets.UTF_8));
8184
logger.error(IOUtils.toString(recordingProcess.getErrorStream(), StandardCharsets.UTF_8));
8285
deviceInfo.addCurrentProcess(recordingProcess);
@@ -93,18 +96,14 @@ public void startRecord(int maxTimeInSecond) {
9396
recordingProcess.destroy();
9497
}
9598
deviceInfo.finishCommand();
99+
// make sure the recording procedure is stopped completely
100+
ThreadUtil.safeSleep(2000);
96101

97-
String outputFilePrefix = new File(baseFolder, DateUtil.fileNameDateDashFormat.format(new Date())).getAbsolutePath();
98-
99-
final String outFileFullPath = outputFilePrefix + "_" + totalTime + "_" + (totalTime + timeSpan) + ".mp4";
100-
String pullComm = String.format("pull %s %s", fileName, outFileFullPath);
101-
Process process = adbOperateUtil.executeDeviceCommandOnPC(deviceInfo, pullComm, logger);
102-
103-
logger.info(IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8));
104-
logger.error(IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8));
105-
process.destroy();
106-
107-
list.add(new File(outFileFullPath));
102+
final String outFileName = DateUtil.fileNameDateDashFormat.format(new Date()) + "_" + totalTime + "_" + (totalTime + timeSpan) + ".mp4";
103+
String pathOnAgent = new File(baseFolder, outFileName).getAbsolutePath();
104+
adbOperateUtil.pullFileToDir(deviceInfo, pathOnAgent, pathOnDevice, logger);
105+
list.add(new File(pathOnAgent));
106+
deviceDriver.removeFileInDevice(deviceInfo, pathOnDevice, logger);
108107

109108
totalTime += timeSpan;
110109
logger.info("ADBDeviceScreenRecorder>> Time recorded {}", totalTime);
@@ -118,7 +117,7 @@ public void startRecord(int maxTimeInSecond) {
118117
list.forEach(File::delete);
119118
}
120119

121-
} catch (IOException e) {
120+
} catch (IOException | InterruptedException e) {
122121
logger.warn("Exception from recordingThread {} {}", e.getClass().getName(), e.getMessage());
123122
} finally {
124123
if (recordingProcess != null) {

common/src/main/java/com/microsoft/hydralab/common/screen/PhoneAppScreenRecorder.java

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424

2525
public class PhoneAppScreenRecorder implements ScreenRecorder {
26-
public static final String recordPackageName = "com.microsoft.hydralab.android.client";
26+
public static final String RECORD_PACKAGE_NAME = "com.microsoft.hydralab.android.client";
2727
private static File recordApk;
2828
protected final File baseFolder;
2929
protected final Logger logger;
@@ -60,19 +60,19 @@ public static void copyAPK(File preAppDir) {
6060

6161
@Override
6262
public void setupDevice() {
63-
if (!deviceDriver.isAppInstalled(deviceInfo, recordPackageName, logger)) {
63+
if (!deviceDriver.isAppInstalled(deviceInfo, RECORD_PACKAGE_NAME, logger)) {
6464
installRecorderServiceApp();
6565
}
6666
try {
6767
deviceDriver.wakeUpDevice(deviceInfo, logger);
6868
deviceDriver.unlockDevice(deviceInfo, logger);
69-
deviceDriver.grantAllPackageNeededPermissions(deviceInfo, recordApk, recordPackageName, false, logger);
70-
deviceDriver.grantPermission(deviceInfo, recordPackageName, "android.permission.FOREGROUND_SERVICE", logger);
71-
deviceDriver.addToBatteryWhiteList(deviceInfo, recordPackageName, logger);
69+
deviceDriver.grantAllPackageNeededPermissions(deviceInfo, recordApk, RECORD_PACKAGE_NAME, false, logger);
70+
deviceDriver.grantPermission(deviceInfo, RECORD_PACKAGE_NAME, "android.permission.FOREGROUND_SERVICE", logger);
71+
deviceDriver.addToBatteryWhiteList(deviceInfo, RECORD_PACKAGE_NAME, logger);
7272
} catch (Exception e) {
7373
logger.error(e.getMessage(), e);
7474
}
75-
FlowUtil.retryWhenFalse(3, () -> deviceDriver.grantProjectionAndBatteryPermission(deviceInfo, recordPackageName, logger));
75+
FlowUtil.retryWhenFalse(3, () -> deviceDriver.grantProjectionAndBatteryPermission(deviceInfo, RECORD_PACKAGE_NAME, logger));
7676
}
7777

7878
@Override
@@ -107,7 +107,6 @@ public String finishRecording() {
107107
if (!started) {
108108
return null;
109109
}
110-
boolean tag = false;
111110
// wait 5s to record more info after testing
112111
ThreadUtils.safeSleep(5000);
113112
stopRecordService();
@@ -121,38 +120,16 @@ public String finishRecording() {
121120
// wait for screen recording to finish
122121
ThreadUtils.safeSleep(5000);
123122

124-
int retryTime = 1;
125-
while (retryTime < Const.AgentConfig.RETRY_TIME) {
126-
logger.info("Pull file round :" + retryTime);
127-
File videoFile = new File(pathOnAgent);
128-
if (videoFile.exists()) {
129-
videoFile.delete();
130-
}
131-
try {
132-
adbOperateUtil.pullFileToDir(deviceInfo, pathOnAgent, pathOnDevice, logger);
133-
} catch (IOException | InterruptedException e) {
134-
logger.error(e.getMessage(), e);
135-
}
136-
ThreadUtils.safeSleep(5000);
137-
138-
long phoneFileSize = adbOperateUtil.getFileLength(deviceInfo, logger, pathOnDevice);
139-
logger.info("PC file path:{} size:{} , Phone file path {} size {}", pathOnAgent, videoFile.length(), pathOnDevice, phoneFileSize);
140-
if (videoFile.length() == phoneFileSize) {
141-
logger.info("Pull video file success!");
142-
tag = true;
143-
break;
144-
}
145-
retryTime++;
146-
if (retryTime == Const.AgentConfig.RETRY_TIME) {
147-
logger.error("Pull video file fail!");
148-
}
123+
try {
124+
adbOperateUtil.pullFileToDir(deviceInfo, pathOnAgent, pathOnDevice, logger);
125+
} catch (IOException | InterruptedException e) {
126+
logger.error(e.getMessage(), e);
127+
pathOnAgent = null;
149128
}
150129
deviceDriver.removeFileInDevice(deviceInfo, pathOnDevice, logger);
151130
started = false;
152-
if (tag) {
153-
return pathOnAgent;
154-
}
155-
return null;
131+
132+
return pathOnAgent;
156133
}
157134

158135
@Override
@@ -163,7 +140,8 @@ public int getPreSleepSeconds() {
163140
public boolean startRecordService() {
164141
try {
165142
// am startservice --es fileName test.mp4 com.microsoft.hydralab.android.client/.ScreenRecorderService
166-
adbOperateUtil.execOnDevice(deviceInfo, String.format("am startservice -a %s.action.START --es fileName %s --es SNCode %s --ei width 720 --ei bitrate 1200000 %s/.ScreenRecorderService", recordPackageName, fileName, deviceInfo.getSerialNum(), recordPackageName), new MultiLineNoCancelLoggingReceiver(logger), logger);
143+
adbOperateUtil.execOnDevice(deviceInfo, String.format("am startservice -a %s.action.START --es fileName %s --es SNCode %s --ei width 720 --ei bitrate 1200000 %s/.ScreenRecorderService",
144+
RECORD_PACKAGE_NAME, fileName, deviceInfo.getSerialNum(), RECORD_PACKAGE_NAME), new MultiLineNoCancelLoggingReceiver(logger), logger);
167145
return true;
168146
} catch (Exception e) {
169147
logger.error(e.getMessage(), e);
@@ -173,7 +151,7 @@ public boolean startRecordService() {
173151

174152
public boolean stopRecordService() {
175153
try {
176-
adbOperateUtil.execOnDevice(deviceInfo, "am startservice -a " + recordPackageName + ".action.STOP " + recordPackageName + "/.ScreenRecorderService", new MultiLineNoCancelLoggingReceiver(logger), logger);
154+
adbOperateUtil.execOnDevice(deviceInfo, "am startservice -a " + RECORD_PACKAGE_NAME + ".action.STOP " + RECORD_PACKAGE_NAME + "/.ScreenRecorderService", new MultiLineNoCancelLoggingReceiver(logger), logger);
177155
return true;
178156
} catch (Exception e) {
179157
logger.error(e.getMessage(), e);
@@ -184,7 +162,7 @@ public boolean stopRecordService() {
184162
public boolean sendKeepAliveSignal() {
185163

186164
try {
187-
adbOperateUtil.execOnDevice(deviceInfo, "am startservice -a " + recordPackageName + ".action.SIGNAL " + recordPackageName + "/.ScreenRecorderService", new MultiLineNoCancelLoggingReceiver(logger), logger);
165+
adbOperateUtil.execOnDevice(deviceInfo, "am startservice -a " + RECORD_PACKAGE_NAME + ".action.SIGNAL " + RECORD_PACKAGE_NAME + "/.ScreenRecorderService", new MultiLineNoCancelLoggingReceiver(logger), logger);
188166
return true;
189167
} catch (Exception e) {
190168
logger.error(e.getMessage(), e);
@@ -198,7 +176,7 @@ private void installRecorderServiceApp() {
198176
} catch (HydraLabRuntimeException e) {
199177
// if failed to install app, uninstall app and try again.
200178
logger.error(e.getMessage(), e);
201-
deviceDriver.uninstallApp(deviceInfo, recordPackageName, logger);
179+
deviceDriver.uninstallApp(deviceInfo, RECORD_PACKAGE_NAME, logger);
202180
deviceDriver.installApp(deviceInfo, recordApk.getAbsolutePath(), logger);
203181
}
204182
}

0 commit comments

Comments
 (0)