aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-09 06:04:57 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-09 06:04:57 +0000
commita17351833c0afc5055a9b56bcf7647e48c454695 (patch)
tree34a81af095a69c5e94e06bc4bdedd0b0a9238b82
parent6fdf80e1975e95d1edd3811815fb6b9c4e59164b (diff)
parentf5008b11d543b2dbce00e0fa3a13f59775df05a2 (diff)
downloadcsuite-a17351833c0afc5055a9b56bcf7647e48c454695.tar.gz
Snap for 8558685 from f5008b11d543b2dbce00e0fa3a13f59775df05a2 to tm-frc-documentsui-releaset_frc_doc_330543000t_frc_doc_330443060t_frc_doc_330443000android13-frc-documentsui-release
Change-Id: I030f831add81d7a6145f86d5dd1a8547793584ec
-rw-r--r--harness/src/main/java/com/android/compatibility/targetprep/CheckGmsPreparer.java4
-rw-r--r--harness/src/main/java/com/android/csuite/core/ApkInstaller.java196
-rw-r--r--harness/src/main/java/com/android/csuite/core/AppCrawlTester.java51
-rw-r--r--harness/src/test/java/com/android/csuite/core/ApkInstallerTest.java94
-rw-r--r--harness/src/test/java/com/android/csuite/core/AppCrawlTesterTest.java177
-rw-r--r--test_scripts/Android.bp5
-rw-r--r--test_scripts/src/main/java/com/android/csuite/tests/AppCrawlTest.java23
-rw-r--r--test_scripts/src/main/java/com/android/csuite/tests/AppLaunchTest.java35
-rw-r--r--test_scripts/src/main/java/com/android/pixel/Android.bp47
-rw-r--r--test_scripts/src/main/java/com/android/pixel/AndroidManifest.xml27
-rw-r--r--test_scripts/src/main/java/com/android/pixel/tests/AppLaunchLockTest.java70
-rw-r--r--test_scripts/src/main/java/com/android/pixel/tests/AppLaunchRecentAppTest.java92
-rw-r--r--test_scripts/src/main/java/com/android/pixel/tests/AppLaunchRotateTest.java80
-rw-r--r--test_scripts/src/main/java/com/android/pixel/tests/PixelAppCompatTestBase.java81
-rw-r--r--test_scripts/src/main/java/com/android/pixel/utils/DeviceUtils.java204
-rw-r--r--test_scripts/src/main/java/com/android/webview/OWNERS2
-rw-r--r--test_scripts/src/main/java/com/android/webview/tests/WebviewAppLaunchTest.java296
-rw-r--r--test_targets/csuite-app-crawl/template.xml4
-rw-r--r--test_targets/csuite-app-launch/default.xml5
-rw-r--r--test_targets/csuite-test-package-launch/template.xml7
-rw-r--r--test_targets/drm-app-launch/template.xml7
-rw-r--r--test_targets/pixel-app-launch-lock/Android.bp22
-rw-r--r--test_targets/pixel-app-launch-lock/OWNERS2
-rw-r--r--test_targets/pixel-app-launch-lock/template.xml34
-rw-r--r--test_targets/pixel-app-launch-recentapp/Android.bp22
-rw-r--r--test_targets/pixel-app-launch-recentapp/OWNERS2
-rw-r--r--test_targets/pixel-app-launch-recentapp/template.xml34
-rw-r--r--test_targets/pixel-app-launch-rotate/Android.bp22
-rw-r--r--test_targets/pixel-app-launch-rotate/OWNERS2
-rw-r--r--test_targets/pixel-app-launch-rotate/template.xml34
-rw-r--r--test_targets/webview-app-launch/Android.bp22
-rw-r--r--test_targets/webview-app-launch/OWNERS2
-rw-r--r--test_targets/webview-app-launch/default.xml28
33 files changed, 1650 insertions, 83 deletions
diff --git a/harness/src/main/java/com/android/compatibility/targetprep/CheckGmsPreparer.java b/harness/src/main/java/com/android/compatibility/targetprep/CheckGmsPreparer.java
index 24c2762..4e4d565 100644
--- a/harness/src/main/java/com/android/compatibility/targetprep/CheckGmsPreparer.java
+++ b/harness/src/main/java/com/android/compatibility/targetprep/CheckGmsPreparer.java
@@ -20,6 +20,7 @@ import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.targetprep.ITargetPreparer;
import com.android.tradefed.targetprep.TargetSetupError;
import com.android.tradefed.util.CommandResult;
@@ -58,7 +59,8 @@ public final class CheckGmsPreparer implements ITargetPreparer {
mEnable = false;
throw new TargetSetupError(
"GMS required but did not detect a running GMS process after device reboot",
- testInfo.getDevice().getDeviceDescriptor());
+ testInfo.getDevice().getDeviceDescriptor(),
+ DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
}
}
diff --git a/harness/src/main/java/com/android/csuite/core/ApkInstaller.java b/harness/src/main/java/com/android/csuite/core/ApkInstaller.java
new file mode 100644
index 0000000..2882164
--- /dev/null
+++ b/harness/src/main/java/com/android/csuite/core/ApkInstaller.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2022 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.csuite.core;
+
+import com.android.csuite.core.TestUtils.TestUtilsException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.AaptParser;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.IRunUtil;
+import com.android.tradefed.util.RunUtil;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/** A utility class to install APKs. */
+public final class ApkInstaller {
+ private static long sCommandTimeOut = TimeUnit.MINUTES.toMillis(4);
+ private final String mDeviceSerial;
+ private final List<Path> mInstalledBaseApks = new ArrayList<>();
+ private final IRunUtil mRunUtil;
+ private final PackageNameParser mPackageNameParser;
+
+ public static ApkInstaller getInstance(ITestDevice device) {
+ return getInstance(device.getSerialNumber());
+ }
+
+ public static ApkInstaller getInstance(String deviceSerial) {
+ return new ApkInstaller(deviceSerial, new RunUtil(), new AaptPackageNameParser());
+ }
+
+ @VisibleForTesting
+ ApkInstaller(String deviceSerial, IRunUtil runUtil, PackageNameParser packageNameParser) {
+ mDeviceSerial = deviceSerial;
+ mRunUtil = runUtil;
+ mPackageNameParser = packageNameParser;
+ }
+
+ /**
+ * Installs a package.
+ *
+ * @param apkPath Path to the apk files. Only accept file/directory path containing a single APK
+ * or split APK files for one package.
+ * @param args Install args for the 'adb install-multiple' command.
+ * @throws ApkInstallerException If the installation failed.
+ * @throws IOException If an IO exception occurred.
+ */
+ public void install(Path apkPath, String... args) throws ApkInstallerException, IOException {
+ List<Path> apkFilePaths;
+ try {
+ apkFilePaths = TestUtils.listApks(apkPath);
+ } catch (TestUtilsException e) {
+ throw new ApkInstallerException("Failed to list APK files from the path " + apkPath, e);
+ }
+
+ CLog.d("Installing a package from " + apkPath);
+
+ String[] cmd = createInstallCommand(apkFilePaths, mDeviceSerial, args);
+
+ CommandResult res = mRunUtil.runTimedCmd(sCommandTimeOut, cmd);
+ if (res.getStatus() != CommandStatus.SUCCESS) {
+ throw new ApkInstallerException(
+ String.format(
+ "Failed to install APKs from the path %s: %s",
+ apkPath, res.toString()));
+ }
+
+ mInstalledBaseApks.add(apkFilePaths.get(0));
+
+ CLog.i("Successfully installed " + apkPath);
+ }
+
+ /**
+ * Attempts to uninstall all the installed packages.
+ *
+ * <p>When failed to uninstall one of the installed packages, this method will still attempt to
+ * uninstall all other packages before throwing an exception.
+ *
+ * @throws ApkInstallerException when failed to uninstall a package.
+ */
+ public void uninstallAllInstalledPackages() throws ApkInstallerException {
+ StringBuilder errorMessage = new StringBuilder();
+ mInstalledBaseApks.forEach(
+ baseApk -> {
+ String packageName;
+ try {
+ packageName = mPackageNameParser.parsePackageName(baseApk);
+ } catch (IOException e) {
+ errorMessage.append(
+ String.format(
+ "Failed to parse the package name from %s. Reason: %s.\n",
+ baseApk, e.getMessage()));
+ return;
+ }
+
+ String[] cmd =
+ new String[] {"adb", "-s", mDeviceSerial, "uninstall", packageName};
+
+ CommandResult res = mRunUtil.runTimedCmd(sCommandTimeOut, cmd);
+ if (res.getStatus() != CommandStatus.SUCCESS) {
+ errorMessage.append(
+ String.format(
+ "Failed to uninstall package %s from %s. Reason: %s.\n",
+ packageName, baseApk, res.toString()));
+ }
+ });
+
+ if (errorMessage.length() > 0) {
+ throw new ApkInstallerException(errorMessage.toString());
+ }
+ }
+
+ private String[] createInstallCommand(
+ List<Path> apkFilePaths, String deviceSerial, String[] args) {
+ ArrayList<String> cmd = new ArrayList<>();
+ cmd.addAll(Arrays.asList("adb", "-s", deviceSerial, "install-multiple"));
+
+ cmd.addAll(Arrays.asList(args));
+
+ apkFilePaths.stream().map(Path::toString).forEach(cmd::add);
+
+ return cmd.toArray(new String[cmd.size()]);
+ }
+
+ /** An exception class representing ApkInstaller error. */
+ public static final class ApkInstallerException extends Exception {
+ /**
+ * Constructs a new {@link ApkInstallerException} with a meaningful error message.
+ *
+ * @param message A error message describing the cause of the error.
+ */
+ private ApkInstallerException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@link ApkInstallerException} with a meaningful error message, and a
+ * cause.
+ *
+ * @param message A detailed error message.
+ * @param cause A {@link Throwable} capturing the original cause of the {@link
+ * ApkInstallerException}.
+ */
+ private ApkInstallerException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructs a new {@link ApkInstallerException} with a cause.
+ *
+ * @param cause A {@link Throwable} capturing the original cause of the {@link
+ * ApkInstallerException}.
+ */
+ private ApkInstallerException(Throwable cause) {
+ super(cause);
+ }
+ }
+
+ private static final class AaptPackageNameParser implements PackageNameParser {
+ @Override
+ public String parsePackageName(Path apkFile) throws IOException {
+ String packageName = AaptParser.parse(apkFile.toFile()).getPackageName();
+ if (packageName == null) {
+ throw new IOException(
+ String.format("Failed to parse package name with AAPT for %s", apkFile));
+ }
+ return packageName;
+ }
+ }
+
+ @VisibleForTesting
+ interface PackageNameParser {
+ String parsePackageName(Path apkFile) throws IOException;
+ }
+}
diff --git a/harness/src/main/java/com/android/csuite/core/AppCrawlTester.java b/harness/src/main/java/com/android/csuite/core/AppCrawlTester.java
index c26a011..96785ef 100644
--- a/harness/src/main/java/com/android/csuite/core/AppCrawlTester.java
+++ b/harness/src/main/java/com/android/csuite/core/AppCrawlTester.java
@@ -29,6 +29,7 @@ import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.ZipUtil;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
import com.google.common.io.MoreFiles;
import org.junit.Assert;
@@ -51,28 +52,26 @@ public final class AppCrawlTester {
private final RunUtilProvider mRunUtilProvider;
private final TestUtils mTestUtils;
private final String mPackageName;
- private final Path mApkRoot;
private static final long COMMAND_TIMEOUT_MILLIS = 4 * 60 * 1000;
private boolean mRecordScreen = false;
private boolean mCollectGmsVersion = false;
private boolean mCollectAppVersion = false;
+ private boolean mUiAutomatorMode = false;
+ private Path mApkRoot;
/**
* Creates an {@link AppCrawlTester} instance.
*
- * @param apkRoot The root path for an apk or a directory that contains apk files for a package.
* @param packageName The package name of the apk files.
* @param testInformation The TradeFed test information.
* @param testLogData The TradeFed test output receiver.
* @return an {@link AppCrawlTester} instance.
*/
public static AppCrawlTester newInstance(
- Path apkRoot,
String packageName,
TestInformation testInformation,
TestLogData testLogData) {
return new AppCrawlTester(
- apkRoot,
packageName,
TestUtils.getInstance(testInformation, testLogData),
() -> new RunUtil());
@@ -80,12 +79,10 @@ public final class AppCrawlTester {
@VisibleForTesting
AppCrawlTester(
- Path apkRoot,
String packageName,
TestUtils testUtils,
RunUtilProvider runUtilProvider) {
mRunUtilProvider = runUtilProvider;
- mApkRoot = apkRoot;
mPackageName = packageName;
mTestUtils = testUtils;
}
@@ -190,8 +187,7 @@ public final class AppCrawlTester {
throw new CrawlerException("Failed to create temp directory for output.", e);
}
- List<Path> apks = getApks(mApkRoot);
- String[] command = createCrawlerRunCommand(mTestUtils.getTestInformation(), apks);
+ String[] command = createCrawlerRunCommand(mTestUtils.getTestInformation());
CLog.d("Launching package: %s.", mPackageName);
@@ -348,7 +344,8 @@ public final class AppCrawlTester {
}
@VisibleForTesting
- String[] createCrawlerRunCommand(TestInformation testInfo, List<Path> apks) {
+ String[] createCrawlerRunCommand(TestInformation testInfo) throws CrawlerException {
+
ArrayList<String> cmd = new ArrayList<>();
cmd.addAll(
Arrays.asList(
@@ -370,13 +367,23 @@ public final class AppCrawlTester {
.toString(),
"--key-store-password",
// Using the publicly known default password of the debug keystore.
- "android",
- "--apk-file",
- apks.get(0).toString()));
+ "android"));
+
+ if (mUiAutomatorMode) {
+ cmd.addAll(Arrays.asList("--ui-automator-mode", "--app-package-name", mPackageName));
+ } else {
+ Preconditions.checkNotNull(
+ mApkRoot, "Apk file path is required when not running in UIAutomator mode");
- for (int i = 1; i < apks.size(); i++) {
- cmd.add("--split-apk-files");
- cmd.add(apks.get(i).toString());
+ List<Path> apks = getApks(mApkRoot);
+
+ cmd.add("--apk-file");
+ cmd.add(apks.get(0).toString());
+
+ for (int i = 1; i < apks.size(); i++) {
+ cmd.add("--split-apk-files");
+ cmd.add(apks.get(i).toString());
+ }
}
return cmd.toArray(new String[cmd.size()]);
@@ -410,6 +417,20 @@ public final class AppCrawlTester {
mCollectAppVersion = collectAppVersion;
}
+ /** Sets the option of whether to run the crawler with UIAutomator mode. */
+ public void setUiAutomatorMode(boolean uiAutomatorMode) {
+ mUiAutomatorMode = uiAutomatorMode;
+ }
+
+ /**
+ * Sets the apk file path. Required when not running in UIAutomator mode.
+ *
+ * @param apkRoot The root path for an apk or a directory that contains apk files for a package.
+ */
+ public void setApkPath(Path apkRoot) {
+ mApkRoot = apkRoot;
+ }
+
@VisibleForTesting
interface RunUtilProvider {
IRunUtil get();
diff --git a/harness/src/test/java/com/android/csuite/core/ApkInstallerTest.java b/harness/src/test/java/com/android/csuite/core/ApkInstallerTest.java
new file mode 100644
index 0000000..99962ab
--- /dev/null
+++ b/harness/src/test/java/com/android/csuite/core/ApkInstallerTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 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.csuite.core;
+
+import static org.junit.Assert.assertThrows;
+
+import com.android.csuite.core.ApkInstaller.ApkInstallerException;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.IRunUtil;
+
+import com.google.common.jimfs.Jimfs;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+@RunWith(JUnit4.class)
+public final class ApkInstallerTest {
+ private final FileSystem mFileSystem =
+ Jimfs.newFileSystem(com.google.common.jimfs.Configuration.unix());
+
+ @Test
+ public void install_failedToListApks_throwsException() throws Exception {
+ Path root = mFileSystem.getPath("apk");
+ Files.createDirectories(root);
+ Files.createFile(root.resolve("not_apk.file"));
+ ApkInstaller sut =
+ new ApkInstaller("serial", Mockito.mock(IRunUtil.class), apk -> "package.name");
+
+ assertThrows(ApkInstallerException.class, () -> sut.install(root));
+ }
+
+ @Test
+ public void install_installCommandFailed_throwsException() throws Exception {
+ Path root = mFileSystem.getPath("apk");
+ Files.createDirectories(root);
+ Files.createFile(root.resolve("base.apk"));
+ IRunUtil runUtil = Mockito.mock(IRunUtil.class);
+ Mockito.when(runUtil.runTimedCmd(Mockito.anyLong(), ArgumentMatchers.<String>any()))
+ .thenReturn(createFailedCommandResult());
+ ApkInstaller sut = new ApkInstaller("serial", runUtil, apk -> "package.name");
+
+ assertThrows(ApkInstallerException.class, () -> sut.install(root));
+ }
+
+ @Test
+ public void install_installCommandSucceed_doesNotThrow() throws Exception {
+ Path root = mFileSystem.getPath("apk");
+ Files.createDirectories(root);
+ Files.createFile(root.resolve("base.apk"));
+ IRunUtil runUtil = Mockito.mock(IRunUtil.class);
+ Mockito.when(runUtil.runTimedCmd(Mockito.anyLong(), ArgumentMatchers.<String>any()))
+ .thenReturn(createSuccessfulCommandResultWithStdout(""));
+ ApkInstaller sut = new ApkInstaller("serial", runUtil, apk -> "package.name");
+
+ sut.install(root);
+ }
+
+ private static CommandResult createSuccessfulCommandResultWithStdout(String stdout) {
+ CommandResult commandResult = new CommandResult(CommandStatus.SUCCESS);
+ commandResult.setExitCode(0);
+ commandResult.setStdout(stdout);
+ commandResult.setStderr("");
+ return commandResult;
+ }
+
+ private static CommandResult createFailedCommandResult() {
+ CommandResult commandResult = new CommandResult(CommandStatus.FAILED);
+ commandResult.setExitCode(1);
+ commandResult.setStdout("");
+ commandResult.setStderr("error");
+ return commandResult;
+ }
+}
diff --git a/harness/src/test/java/com/android/csuite/core/AppCrawlTesterTest.java b/harness/src/test/java/com/android/csuite/core/AppCrawlTesterTest.java
index b3d0ccc..dc9132b 100644
--- a/harness/src/test/java/com/android/csuite/core/AppCrawlTesterTest.java
+++ b/harness/src/test/java/com/android/csuite/core/AppCrawlTesterTest.java
@@ -49,7 +49,6 @@ import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
-import java.util.List;
@RunWith(JUnit4.class)
public final class AppCrawlTesterTest {
@@ -71,8 +70,17 @@ public final class AppCrawlTesterTest {
}
@Test
+ public void start_apkNotProvided_throwsException() throws Exception {
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setUiAutomatorMode(false);
+
+ assertThrows(NullPointerException.class, () -> suj.start());
+ }
+
+ @Test
public void startAndAssertAppNoCrash_noCrashDetected_doesNotThrow() throws Exception {
- AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks());
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(createApkPathWithSplitApks());
Mockito.doReturn(new DeviceUtils.DeviceTimestamp(1L))
.when(mDeviceUtils)
.currentTimeMillis();
@@ -87,7 +95,8 @@ public final class AppCrawlTesterTest {
@Test
public void startAndAssertAppNoCrash_dropboxEntriesDetected_throws() throws Exception {
- AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks());
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(createApkPathWithSplitApks());
Mockito.doReturn(new DeviceUtils.DeviceTimestamp(1L))
.when(mDeviceUtils)
.currentTimeMillis();
@@ -101,7 +110,8 @@ public final class AppCrawlTesterTest {
@Test
public void startAndAssertAppNoCrash_crawlerExceptionIsThrown_throws() throws Exception {
- AppCrawlTester suj = createNotPreparedTestSubject(createApkPathWithSplitApks());
+ AppCrawlTester suj = createNotPreparedTestSubject();
+ suj.setApkPath(createApkPathWithSplitApks());
Mockito.doReturn(new DeviceUtils.DeviceTimestamp(1L))
.when(mDeviceUtils)
.currentTimeMillis();
@@ -116,7 +126,8 @@ public final class AppCrawlTesterTest {
@Test
public void start_screenRecordEnabled_screenIsRecorded() throws Exception {
- AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks());
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(createApkPathWithSplitApks());
suj.setRecordScreen(true);
suj.start();
@@ -127,7 +138,8 @@ public final class AppCrawlTesterTest {
@Test
public void start_screenRecordDisabled_screenIsNotRecorded() throws Exception {
- AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks());
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(createApkPathWithSplitApks());
suj.setRecordScreen(false);
suj.start();
@@ -138,7 +150,8 @@ public final class AppCrawlTesterTest {
@Test
public void start_collectGmsVersionEnabled_versionIsCollected() throws Exception {
- AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks());
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(createApkPathWithSplitApks());
suj.setCollectGmsVersion(true);
suj.start();
@@ -148,7 +161,8 @@ public final class AppCrawlTesterTest {
@Test
public void start_collectGmsVersionDisabled_versionIsNotCollected() throws Exception {
- AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks());
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(createApkPathWithSplitApks());
suj.setCollectGmsVersion(false);
suj.start();
@@ -158,7 +172,8 @@ public final class AppCrawlTesterTest {
@Test
public void start_collectAppVersionEnabled_versionIsCollected() throws Exception {
- AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks());
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(createApkPathWithSplitApks());
suj.setCollectAppVersion(true);
suj.start();
@@ -168,7 +183,8 @@ public final class AppCrawlTesterTest {
@Test
public void start_collectAppVersionDisabled_versionIsNotCollected() throws Exception {
- AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks());
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(createApkPathWithSplitApks());
suj.setCollectAppVersion(false);
suj.start();
@@ -178,14 +194,16 @@ public final class AppCrawlTesterTest {
@Test
public void start_withSplitApksDirectory_doesNotThrowException() throws Exception {
- AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks());
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(createApkPathWithSplitApks());
suj.start();
}
@Test
public void start_credentialIsProvidedToCrawler() throws Exception {
- AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks());
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(createApkPathWithSplitApks());
suj.start();
@@ -200,7 +218,8 @@ public final class AppCrawlTesterTest {
Files.createDirectories(root.resolve("sub"));
Files.createFile(root.resolve("sub").resolve("base.apk"));
Files.createFile(root.resolve("sub").resolve("config.apk"));
- AppCrawlTester suj = createPreparedTestSubject(root);
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(root);
suj.start();
}
@@ -210,7 +229,8 @@ public final class AppCrawlTesterTest {
Path root = mFileSystem.getPath("apk");
Files.createDirectories(root);
Files.createFile(root.resolve("base.apk"));
- AppCrawlTester suj = createPreparedTestSubject(root);
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(root);
suj.start();
}
@@ -220,7 +240,8 @@ public final class AppCrawlTesterTest {
Path root = mFileSystem.getPath("apk");
Files.createDirectories(root);
Files.createFile(root.resolve("single.apk"));
- AppCrawlTester suj = createPreparedTestSubject(root);
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(root);
suj.start();
}
@@ -229,7 +250,8 @@ public final class AppCrawlTesterTest {
public void start_withSingleApkFile_doesNotThrowException() throws Exception {
Path root = mFileSystem.getPath("single.apk");
Files.createFile(root);
- AppCrawlTester suj = createPreparedTestSubject(root);
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(root);
suj.start();
}
@@ -241,7 +263,8 @@ public final class AppCrawlTesterTest {
Files.createDirectories(root);
Files.createFile(root.resolve("single.apk"));
Files.createFile(root.resolve("single.not_apk"));
- AppCrawlTester suj = createPreparedTestSubject(root);
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(root);
suj.start();
}
@@ -251,7 +274,8 @@ public final class AppCrawlTesterTest {
Path root = mFileSystem.getPath("apk");
Files.createDirectories(root);
Files.createFile(root.resolve("single.not_apk"));
- AppCrawlTester suj = createPreparedTestSubject(root);
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(root);
assertThrows(AppCrawlTester.CrawlerException.class, () -> suj.start());
}
@@ -260,7 +284,8 @@ public final class AppCrawlTesterTest {
public void start_withNonApkPath_throwException() throws Exception {
Path root = mFileSystem.getPath("single.not_apk");
Files.createFile(root);
- AppCrawlTester suj = createPreparedTestSubject(root);
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(root);
assertThrows(AppCrawlTester.CrawlerException.class, () -> suj.start());
}
@@ -273,21 +298,24 @@ public final class AppCrawlTesterTest {
Files.createDirectories(root.resolve("2"));
Files.createFile(root.resolve("1").resolve("single.apk"));
Files.createFile(root.resolve("2").resolve("single.apk"));
- AppCrawlTester suj = createPreparedTestSubject(root);
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(root);
assertThrows(AppCrawlTester.CrawlerException.class, () -> suj.start());
}
@Test
public void start_preparerNotRun_throwsException() throws Exception {
- AppCrawlTester suj = createNotPreparedTestSubject(createApkPathWithSplitApks());
+ AppCrawlTester suj = createNotPreparedTestSubject();
+ suj.setApkPath(createApkPathWithSplitApks());
assertThrows(AppCrawlTester.CrawlerException.class, () -> suj.start());
}
@Test
public void start_alreadyRun_throwsException() throws Exception {
- AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks());
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(createApkPathWithSplitApks());
suj.start();
assertThrows(AppCrawlTester.CrawlerException.class, () -> suj.start());
@@ -295,7 +323,8 @@ public final class AppCrawlTesterTest {
@Test
public void cleanUp_removesOutputDirectory() throws Exception {
- AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks());
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(createApkPathWithSplitApks());
suj.start();
assertTrue(Files.exists(suj.mOutput));
@@ -306,11 +335,14 @@ public final class AppCrawlTesterTest {
@Test
public void createCrawlerRunCommand_containsRequiredCrawlerParams() throws Exception {
- AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks());
+ Path apkRoot = mFileSystem.getPath("apk");
+ Files.createDirectories(apkRoot);
+ Files.createFile(apkRoot.resolve("some.apk"));
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(apkRoot);
suj.start();
- List<Path> apks = Arrays.asList(Path.of("some.apk"));
- String[] result = suj.createCrawlerRunCommand(mTestInfo, apks);
+ String[] result = suj.createCrawlerRunCommand(mTestInfo);
assertThat(result).asList().contains("--key-store-file");
assertThat(result).asList().contains("--key-store-password");
@@ -320,11 +352,14 @@ public final class AppCrawlTesterTest {
@Test
public void createCrawlerRunCommand_crawlerIsExecutedThroughJavaJar() throws Exception {
- AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks());
+ Path apkRoot = mFileSystem.getPath("apk");
+ Files.createDirectories(apkRoot);
+ Files.createFile(apkRoot.resolve("some.apk"));
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(apkRoot);
suj.start();
- List<Path> apks = Arrays.asList(Path.of("some.apk"));
- String[] result = suj.createCrawlerRunCommand(mTestInfo, apks);
+ String[] result = suj.createCrawlerRunCommand(mTestInfo);
assertThat(result).asList().contains("java");
assertThat(result).asList().contains("-jar");
@@ -333,12 +368,16 @@ public final class AppCrawlTesterTest {
@Test
public void createCrawlerRunCommand_splitApksProvided_useApkFileAndSplitApkFilesParams()
throws Exception {
- AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks());
+ Path apkRoot = mFileSystem.getPath("apk");
+ Files.createDirectories(apkRoot);
+ Files.createFile(apkRoot.resolve("base.apk"));
+ Files.createFile(apkRoot.resolve("config1.apk"));
+ Files.createFile(apkRoot.resolve("config2.apk"));
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(apkRoot);
suj.start();
- List<Path> apks =
- Arrays.asList(Path.of("base.apk"), Path.of("config1.apk"), Path.of("config2.apk"));
- String[] result = suj.createCrawlerRunCommand(mTestInfo, apks);
+ String[] result = suj.createCrawlerRunCommand(mTestInfo);
assertThat(Arrays.asList(result).stream().filter(s -> s.equals("--apk-file")).count())
.isEqualTo(1);
@@ -350,15 +389,71 @@ public final class AppCrawlTesterTest {
}
@Test
+ public void createCrawlerRunCommand_uiAutomatorModeEnabled_doesNotContainApks()
+ throws Exception {
+ Path apkRoot = mFileSystem.getPath("apk");
+ Files.createDirectories(apkRoot);
+ Files.createFile(apkRoot.resolve("base.apk"));
+ Files.createFile(apkRoot.resolve("config1.apk"));
+ Files.createFile(apkRoot.resolve("config2.apk"));
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(apkRoot);
+ suj.setUiAutomatorMode(true);
+ suj.start();
+
+ String[] result = suj.createCrawlerRunCommand(mTestInfo);
+
+ assertThat(Arrays.asList(result).stream().filter(s -> s.equals("--apk-file")).count())
+ .isEqualTo(0);
+ assertThat(
+ Arrays.asList(result).stream()
+ .filter(s -> s.equals("--split-apk-files"))
+ .count())
+ .isEqualTo(0);
+ }
+
+ @Test
+ public void createCrawlerRunCommand_uiAutomatorModeEnabled_containsUiAutomatorParam()
+ throws Exception {
+ Path apkRoot = mFileSystem.getPath("apk");
+ Files.createDirectories(apkRoot);
+ Files.createFile(apkRoot.resolve("base.apk"));
+ Files.createFile(apkRoot.resolve("config1.apk"));
+ Files.createFile(apkRoot.resolve("config2.apk"));
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(apkRoot);
+ suj.setUiAutomatorMode(true);
+ suj.start();
+
+ String[] result = suj.createCrawlerRunCommand(mTestInfo);
+
+ assertThat(
+ Arrays.asList(result).stream()
+ .filter(s -> s.equals("--ui-automator-mode"))
+ .count())
+ .isEqualTo(1);
+ assertThat(
+ Arrays.asList(result).stream()
+ .filter(s -> s.equals("--app-package-name"))
+ .count())
+ .isEqualTo(1);
+ }
+
+ @Test
public void createCrawlerRunCommand_doesNotContainNullOrEmptyStrings() throws Exception {
- AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks());
+ Path apkRoot = mFileSystem.getPath("apk");
+ Files.createDirectories(apkRoot);
+ Files.createFile(apkRoot.resolve("base.apk"));
+ Files.createFile(apkRoot.resolve("config1.apk"));
+ Files.createFile(apkRoot.resolve("config2.apk"));
+ AppCrawlTester suj = createPreparedTestSubject();
+ suj.setApkPath(apkRoot);
suj.start();
- List<Path> apks =
- Arrays.asList(Path.of("base.apk"), Path.of("config1.apk"), Path.of("config2.apk"));
- String[] result = suj.createCrawlerRunCommand(mTestInfo, apks);
+ String[] result = suj.createCrawlerRunCommand(mTestInfo);
assertThat(Arrays.asList(result).stream().filter(s -> s == null).count()).isEqualTo(0);
+
assertThat(Arrays.asList(result).stream().map(String::trim).filter(String::isEmpty).count())
.isEqualTo(0);
}
@@ -382,19 +477,19 @@ public final class AppCrawlTesterTest {
preparer.setUp(mTestInfo);
}
- private AppCrawlTester createNotPreparedTestSubject(Path apkPath) {
+ private AppCrawlTester createNotPreparedTestSubject() {
Mockito.when(mRunUtil.runTimedCmd(Mockito.anyLong(), ArgumentMatchers.<String>any()))
.thenReturn(createSuccessfulCommandResult());
Mockito.when(mDevice.getSerialNumber()).thenReturn("serial");
- return new AppCrawlTester(apkPath, "package.name", mTestUtils, () -> mRunUtil);
+ return new AppCrawlTester("package.name", mTestUtils, () -> mRunUtil);
}
- private AppCrawlTester createPreparedTestSubject(Path apkPath)
+ private AppCrawlTester createPreparedTestSubject()
throws IOException, ConfigurationException, TargetSetupError {
simulatePreparerWasExecutedSuccessfully();
Mockito.when(mRunUtil.runTimedCmd(Mockito.anyLong(), ArgumentMatchers.<String>any()))
.thenReturn(createSuccessfulCommandResult());
- return new AppCrawlTester(apkPath, "package.name", mTestUtils, () -> mRunUtil);
+ return new AppCrawlTester("package.name", mTestUtils, () -> mRunUtil);
}
private TestUtils createTestUtils() throws DeviceNotAvailableException {
diff --git a/test_scripts/Android.bp b/test_scripts/Android.bp
index 5bf5f24..62bfcf0 100644
--- a/test_scripts/Android.bp
+++ b/test_scripts/Android.bp
@@ -21,6 +21,9 @@ java_library_host {
srcs: [
"src/main/java/**/*.java",
],
+ exclude_srcs: [
+ "src/main/java/com/android/pixel/**/*.java",
+ ],
java_resource_dirs: [
"src/main/resources",
],
@@ -28,4 +31,4 @@ java_library_host {
"tradefed",
"csuite-harness",
],
-} \ No newline at end of file
+}
diff --git a/test_scripts/src/main/java/com/android/csuite/tests/AppCrawlTest.java b/test_scripts/src/main/java/com/android/csuite/tests/AppCrawlTest.java
index 3af69c1..ae7bb09 100644
--- a/test_scripts/src/main/java/com/android/csuite/tests/AppCrawlTest.java
+++ b/test_scripts/src/main/java/com/android/csuite/tests/AppCrawlTest.java
@@ -23,6 +23,8 @@ import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.google.common.base.Preconditions;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -59,7 +61,7 @@ public class AppCrawlTest extends BaseHostJUnit4Test {
@Option(
name = "apk",
- mandatory = true,
+ mandatory = false,
description =
"Path to an apk file or a directory containing apk files of a single package.")
private File mApk;
@@ -67,14 +69,27 @@ public class AppCrawlTest extends BaseHostJUnit4Test {
@Option(name = "package-name", mandatory = true, description = "Package name of testing app.")
private String mPackageName;
+ @Option(
+ name = "ui-automator-mode",
+ mandatory = false,
+ description =
+ "Run the crawler with UIAutomator mode. Apk option is not required in this"
+ + " mode.")
+ private boolean mUiAutomatorMode = false;
+
@Before
public void setUp() {
- mCrawler =
- AppCrawlTester.newInstance(
- mApk.toPath(), mPackageName, getTestInformation(), mLogData);
+ if (!mUiAutomatorMode) {
+ Preconditions.checkNotNull(
+ mApk, "Apk file path is required when not running in UIAutomator mode");
+ }
+
+ mCrawler = AppCrawlTester.newInstance(mPackageName, getTestInformation(), mLogData);
mCrawler.setRecordScreen(mRecordScreen);
mCrawler.setCollectGmsVersion(mCollectGmsVersion);
mCrawler.setCollectAppVersion(mCollectAppVersion);
+ mCrawler.setUiAutomatorMode(mUiAutomatorMode);
+ mCrawler.setApkPath(mApk.toPath());
}
@Test
diff --git a/test_scripts/src/main/java/com/android/csuite/tests/AppLaunchTest.java b/test_scripts/src/main/java/com/android/csuite/tests/AppLaunchTest.java
index 507f5b1..49f27fa 100644
--- a/test_scripts/src/main/java/com/android/csuite/tests/AppLaunchTest.java
+++ b/test_scripts/src/main/java/com/android/csuite/tests/AppLaunchTest.java
@@ -16,6 +16,8 @@
package com.android.csuite.tests;
+import com.android.csuite.core.ApkInstaller;
+import com.android.csuite.core.ApkInstaller.ApkInstallerException;
import com.android.csuite.core.DeviceUtils;
import com.android.csuite.core.DeviceUtils.DeviceTimestamp;
import com.android.csuite.core.DeviceUtils.DeviceUtilsException;
@@ -37,7 +39,10 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
/** A test that verifies that a single app can be successfully launched. */
@RunWith(DeviceJUnit4ClassRunner.class)
@@ -47,6 +52,7 @@ public class AppLaunchTest extends BaseHostJUnit4Test {
@VisibleForTesting static final String COLLECT_GMS_VERSION = "collect-gms-version";
@VisibleForTesting static final String RECORD_SCREEN = "record-screen";
@Rule public TestLogData mLogData = new TestLogData();
+ private ApkInstaller mApkInstaller;
@Option(name = RECORD_SCREEN, description = "Whether to record screen during test.")
private boolean mRecordScreen;
@@ -70,6 +76,18 @@ public class AppLaunchTest extends BaseHostJUnit4Test {
+ " test log files.")
private boolean mCollectGmsVersion;
+ @Option(
+ name = "install-apk",
+ description =
+ "The path to an apk file or a directory of apk files of a singe package to be"
+ + " installed on device. Can be repeated.")
+ private final List<File> mApkPaths = new ArrayList<>();
+
+ @Option(
+ name = "install-arg",
+ description = "Arguments for the 'adb install-multiple' package installation command.")
+ private final List<String> mInstallArgs = new ArrayList<>();
+
@Option(name = "package-name", description = "Package name of testing app.")
private String mPackageName;
@@ -79,12 +97,19 @@ public class AppLaunchTest extends BaseHostJUnit4Test {
private int mAppLaunchTimeoutMs = 15000;
@Before
- public void setUp() throws DeviceNotAvailableException {
+ public void setUp() throws DeviceNotAvailableException, ApkInstallerException, IOException {
Assert.assertNotNull("Package name cannot be null", mPackageName);
DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice());
TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData);
+ mApkInstaller = ApkInstaller.getInstance(getDevice());
+ for (File apkPath : mApkPaths) {
+ CLog.d("Installing " + apkPath);
+ mApkInstaller.install(
+ apkPath.toPath(), mInstallArgs.toArray(new String[mInstallArgs.size()]));
+ }
+
if (mCollectGmsVersion) {
testUtils.collectGmsVersion(mPackageName);
}
@@ -113,7 +138,7 @@ public class AppLaunchTest extends BaseHostJUnit4Test {
}
@After
- public void tearDown() throws DeviceNotAvailableException {
+ public void tearDown() throws DeviceNotAvailableException, ApkInstallerException {
DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice());
TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData);
@@ -123,6 +148,8 @@ public class AppLaunchTest extends BaseHostJUnit4Test {
deviceUtils.stopPackage(mPackageName);
deviceUtils.unfreezeRotation();
+
+ mApkInstaller.uninstallAllInstalledPackages();
}
private void launchPackageAndCheckForCrash() throws DeviceNotAvailableException {
@@ -145,9 +172,7 @@ public class AppLaunchTest extends BaseHostJUnit4Test {
try {
String crashLog = testUtils.getDropboxPackageCrashLog(mPackageName, startTime, true);
- if (crashLog != null) {
- Assert.fail(crashLog);
- }
+ Assert.assertNull(crashLog, crashLog);
} catch (IOException e) {
Assert.fail("Error while getting dropbox crash log: " + e);
}
diff --git a/test_scripts/src/main/java/com/android/pixel/Android.bp b/test_scripts/src/main/java/com/android/pixel/Android.bp
new file mode 100644
index 0000000..2f3e2e8
--- /dev/null
+++ b/test_scripts/src/main/java/com/android/pixel/Android.bp
@@ -0,0 +1,47 @@
+// Copyright (C) 2022 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "PixelAppCompTests",
+ compile_multilib: "both",
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "compatibility-device-util-axt",
+ "collector-device-lib",
+ "ub-uiautomator",
+ ],
+ // Use multi-dex as the compatibility-common-util-devicesidelib dependency
+ // on compatibility-device-util-axt pushes us beyond 64k methods.
+ dxflags: ["--multi-dex"],
+ srcs: [
+ "tests/*.java",
+ "utils/*.java",
+ ],
+ platform_apis: true,
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "device-tests",
+ "csuite",
+ ],
+ min_sdk_version: "29",
+}
diff --git a/test_scripts/src/main/java/com/android/pixel/AndroidManifest.xml b/test_scripts/src/main/java/com/android/pixel/AndroidManifest.xml
new file mode 100644
index 0000000..2b88677
--- /dev/null
+++ b/test_scripts/src/main/java/com/android/pixel/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.pixel.tests">
+ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.pixel.tests" />
+
+</manifest>
diff --git a/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchLockTest.java b/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchLockTest.java
new file mode 100644
index 0000000..92c2f59
--- /dev/null
+++ b/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchLockTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 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.pixel.tests;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Until;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AppLaunchLockTest extends PixelAppCompatTestBase {
+ private static final int LAUNCH_TIME_MS = 30000; // 30 seconds
+ private static final long WAIT_ONE_SECOND_IN_MS = 1000;
+ private static final String DISMISS_KEYGUARD = "wm dismiss-keyguard";
+
+ @Test
+ public void testLockDevice() throws Exception {
+ // Launch the 3P app
+ getDeviceUtils().launchApp(getPackage());
+
+ // Wait for the 3P app to appear
+ getUiDevice().wait(Until.hasObject(By.pkg(getPackage()).depth(0)), LAUNCH_TIME_MS);
+ getUiDevice().waitForIdle();
+ Assert.assertTrue(
+ "3P app main page should show up",
+ getUiDevice().hasObject(By.pkg(getPackage()).depth(0)));
+
+ if (getUiDevice().isScreenOn()) {
+ getUiDevice().sleep();
+ SystemClock.sleep(WAIT_ONE_SECOND_IN_MS);
+ }
+ getDeviceUtils().takeScreenshot(getPackage(), "sleep_device");
+ Assert.assertFalse("The screen should be off", getUiDevice().isScreenOn());
+
+ getUiDevice().wakeUp();
+ SystemClock.sleep(WAIT_ONE_SECOND_IN_MS);
+ getDeviceUtils().takeScreenshot(getPackage(), "wake_up_device");
+ Assert.assertTrue("The screen should be off", getUiDevice().isScreenOn());
+ Assert.assertTrue("The keyguard should show up", getKeyguardManager().isKeyguardLocked());
+
+ getUiDevice().executeShellCommand(DISMISS_KEYGUARD);
+ SystemClock.sleep(WAIT_ONE_SECOND_IN_MS);
+ getDeviceUtils().takeScreenshot(getPackage(), "dismiss_keyguard");
+ getUiDevice().wait(Until.hasObject(By.pkg(getPackage()).depth(0)), LAUNCH_TIME_MS);
+ Assert.assertFalse(
+ "The keyguard should be dismissed", getKeyguardManager().isKeyguardLocked());
+ Assert.assertTrue(
+ "3P app main page should show up after unlocking the screen",
+ getUiDevice().hasObject(By.pkg(getPackage()).depth(0)));
+ }
+}
diff --git a/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchRecentAppTest.java b/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchRecentAppTest.java
new file mode 100644
index 0000000..6c694c8
--- /dev/null
+++ b/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchRecentAppTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 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.pixel.tests;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Until;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AppLaunchRecentAppTest extends PixelAppCompatTestBase {
+ private static final int WAIT_FIFTEEN_SECONDS_IN_MS = 15000;
+ private static final long WAIT_ONE_SECOND_IN_MS = 1000;
+ private static final String CLEAR_ALL = "Clear all";
+ private static final String NO_RECENT_ITEMS = "No recent items";
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ getUiDevice().pressRecentApps();
+ getUiDevice()
+ .waitForWindowUpdate(
+ getUiDevice().getLauncherPackageName(), WAIT_FIFTEEN_SECONDS_IN_MS);
+ getUiDevice().wait(Until.findObject(By.text(NO_RECENT_ITEMS)), WAIT_FIFTEEN_SECONDS_IN_MS);
+ if (!getUiDevice().hasObject(By.text(NO_RECENT_ITEMS))) {
+ int midY = getUiDevice().getDisplayHeight() / 2;
+ int startX = getUiDevice().getDisplayWidth() * 1 / 10;
+ int endX = getUiDevice().getDisplayWidth() * 9 / 10;
+ for (int i = 0; i < 20; i++) {
+ getUiDevice().swipe(startX, midY, endX, midY, (endX - startX) / 100);
+ getUiDevice().waitForIdle();
+ if (getUiDevice().hasObject(By.text(CLEAR_ALL))) {
+ break;
+ }
+ }
+ if (getUiDevice().hasObject(By.text(CLEAR_ALL))) {
+ getUiDevice().findObject(By.text(CLEAR_ALL)).click();
+ }
+ SystemClock.sleep(WAIT_ONE_SECOND_IN_MS);
+ }
+ getDeviceUtils().backToHome(getUiDevice().getLauncherPackageName());
+ }
+
+ @Test
+ public void testLaunchFromRecentApps() throws Exception {
+ // Launch the 3P app
+ getDeviceUtils().launchApp(getPackage());
+
+ // Wait for the 3P app to appear
+ getUiDevice()
+ .wait(Until.hasObject(By.pkg(getPackage()).depth(0)), WAIT_FIFTEEN_SECONDS_IN_MS);
+ getUiDevice().waitForIdle();
+ Assert.assertTrue(
+ "3P app main page should show up",
+ getUiDevice().hasObject(By.pkg(getPackage()).depth(0)));
+
+ getUiDevice().pressRecentApps();
+ getUiDevice().wait(Until.hasObject(By.text("Screenshot")), WAIT_FIFTEEN_SECONDS_IN_MS);
+ getDeviceUtils().takeScreenshot(getPackage(), "press_recent_apps_1");
+ Assert.assertTrue(
+ "3P app should be in background", getUiDevice().hasObject(By.text("Screenshot")));
+
+ getUiDevice().pressRecentApps();
+ getUiDevice()
+ .wait(Until.hasObject(By.pkg(getPackage()).depth(0)), WAIT_FIFTEEN_SECONDS_IN_MS);
+ getDeviceUtils().takeScreenshot(getPackage(), "press_recent_apps_2");
+ Assert.assertTrue(
+ "3P app main page should be re-launched",
+ getUiDevice().hasObject(By.pkg(getPackage()).depth(0)));
+ }
+}
diff --git a/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchRotateTest.java b/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchRotateTest.java
new file mode 100644
index 0000000..62f6e1a
--- /dev/null
+++ b/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchRotateTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 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.pixel.tests;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Until;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AppLaunchRotateTest extends PixelAppCompatTestBase {
+ private static final String ROTATE_LANDSCAPE =
+ "content insert --uri content://settings/system"
+ + " --bind name:s:user_rotation --bind value:i:1";
+ private static final String ROTATE_PORTRAIT =
+ "content insert --uri content://settings/system"
+ + " --bind name:s:user_rotation --bind value:i:0";
+ private static final int LAUNCH_TIME_MS = 30000; // 30 seconds
+ private static final long WAIT_ONE_SECOND_IN_MS = 1000;
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ getUiDevice().unfreezeRotation();
+ }
+
+ @Test
+ public void testRotateDevice() throws Exception {
+ // Launch the 3P app
+ getDeviceUtils().launchApp(getPackage());
+
+ // Wait for the 3P app to appear
+ getUiDevice().wait(Until.hasObject(By.pkg(getPackage()).depth(0)), LAUNCH_TIME_MS);
+ getUiDevice().waitForIdle();
+ Assert.assertTrue(
+ "3P app main page should show up",
+ getUiDevice().hasObject(By.pkg(getPackage()).depth(0)));
+
+ // Turn off the automatic rotation
+ getUiDevice().freezeRotation();
+ getUiDevice().executeShellCommand(ROTATE_PORTRAIT);
+ SystemClock.sleep(WAIT_ONE_SECOND_IN_MS);
+ getDeviceUtils().takeScreenshot(getPackage(), "set_portrait_mode");
+ Assert.assertTrue(
+ "Screen should be in portrait mode", getUiDevice().isNaturalOrientation());
+
+ getUiDevice().executeShellCommand(ROTATE_LANDSCAPE);
+ SystemClock.sleep(WAIT_ONE_SECOND_IN_MS);
+ getDeviceUtils().takeScreenshot(getPackage(), "rotate_landscape");
+ Assert.assertFalse(
+ "Screen should be in landscape mode", getUiDevice().isNaturalOrientation());
+
+ getUiDevice().executeShellCommand(ROTATE_PORTRAIT);
+ SystemClock.sleep(WAIT_ONE_SECOND_IN_MS);
+ getDeviceUtils().takeScreenshot(getPackage(), "rotate_portrait");
+ Assert.assertTrue(
+ "Screen should be in portrait mode", getUiDevice().isNaturalOrientation());
+ }
+}
diff --git a/test_scripts/src/main/java/com/android/pixel/tests/PixelAppCompatTestBase.java b/test_scripts/src/main/java/com/android/pixel/tests/PixelAppCompatTestBase.java
new file mode 100644
index 0000000..d5fb892
--- /dev/null
+++ b/test_scripts/src/main/java/com/android/pixel/tests/PixelAppCompatTestBase.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 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.pixel.tests;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getArguments;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.KeyguardManager;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.pixel.utils.DeviceUtils;
+
+import org.junit.After;
+import org.junit.Before;
+
+/** Base class for Pixel app compatibility tests. */
+public abstract class PixelAppCompatTestBase {
+ private static final String KEY_PACKAGE_NAME = "package";
+
+ private DeviceUtils mDeviceUtils;
+ private UiDevice mDevice;
+ private KeyguardManager mKeyguardManager;
+ private String mPackage;
+
+ @Before
+ public void setUp() throws Exception {
+ getDeviceUtils().createLogDataDir();
+ getDeviceUtils().wakeAndUnlockScreen();
+ // Start from the home screen
+ getDeviceUtils().backToHome(getUiDevice().getLauncherPackageName());
+ getDeviceUtils().startRecording(getPackage());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ getDeviceUtils().stopRecording();
+ }
+
+ protected UiDevice getUiDevice() {
+ if (mDevice == null) {
+ mDevice = UiDevice.getInstance(getInstrumentation());
+ }
+ return mDevice;
+ }
+
+ protected DeviceUtils getDeviceUtils() {
+ if (mDeviceUtils == null) {
+ mDeviceUtils = new DeviceUtils(getUiDevice());
+ }
+ return mDeviceUtils;
+ }
+
+ protected KeyguardManager getKeyguardManager() {
+ if (mKeyguardManager == null) {
+ mKeyguardManager =
+ getInstrumentation().getContext().getSystemService(KeyguardManager.class);
+ }
+ return mKeyguardManager;
+ }
+
+ protected String getPackage() {
+ if (mPackage == null) {
+ mPackage = getArguments().getString(KEY_PACKAGE_NAME);
+ }
+ return mPackage;
+ }
+}
diff --git a/test_scripts/src/main/java/com/android/pixel/utils/DeviceUtils.java b/test_scripts/src/main/java/com/android/pixel/utils/DeviceUtils.java
new file mode 100644
index 0000000..9d29dbc
--- /dev/null
+++ b/test_scripts/src/main/java/com/android/pixel/utils/DeviceUtils.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2022 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.pixel.utils;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import org.junit.Assert;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+
+public class DeviceUtils {
+ private static final String TAG = DeviceUtils.class.getSimpleName();
+ private static final String LOG_DATA_DIR = "/sdcard/logData";
+ private static final int MAX_RECORDING_PARTS = 5;
+ private static final long WAIT_ONE_SECOND_IN_MS = 1000;
+ private static final long VIDEO_TAIL_BUFFER = 500;
+ private static final String DISMISS_KEYGUARD = "wm dismiss-keyguard";
+
+ private RecordingThread mCurrentThread;
+ private File mLogDataDir;
+ private UiDevice mDevice;
+
+ public DeviceUtils(UiDevice device) {
+ mDevice = device;
+ }
+
+ /** Create a directory to save test screenshots, screenrecord and text files. */
+ public void createLogDataDir() {
+ mLogDataDir = new File(LOG_DATA_DIR);
+ if (mLogDataDir.exists()) {
+ String[] children = mLogDataDir.list();
+ for (String file : children) {
+ new File(mLogDataDir, file).delete();
+ }
+ } else {
+ mLogDataDir.mkdirs();
+ }
+ }
+
+ /** Wake up the device and dismiss the keyguard. */
+ public void wakeAndUnlockScreen() throws Exception {
+ mDevice.wakeUp();
+ SystemClock.sleep(WAIT_ONE_SECOND_IN_MS);
+ mDevice.executeShellCommand(DISMISS_KEYGUARD);
+ SystemClock.sleep(WAIT_ONE_SECOND_IN_MS);
+ }
+
+ /**
+ * Go back to home screen by pressing back key five times and home key to avoid the infinite
+ * loop since some apps' activities cannot be exited to home screen by back key event.
+ */
+ public void backToHome(String launcherPkg) {
+ for (int i = 0; i < 5; i++) {
+ mDevice.pressBack();
+ mDevice.waitForIdle();
+ if (mDevice.hasObject(By.pkg(launcherPkg))) {
+ break;
+ }
+ }
+ mDevice.pressHome();
+ }
+
+ /**
+ * Launch an app with the given package name
+ *
+ * @param packageName Name of package to be launched
+ */
+ public void launchApp(String packageName) {
+ Context context = getInstrumentation().getContext();
+ Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ }
+
+ /**
+ * Take a screenshot on the device and save it in {@code logDataDir}.
+ *
+ * @param packageName The package name of 3P apps screenshotted.
+ * @param description The description of actions or operations on the device.
+ */
+ public void takeScreenshot(String packageName, String description) {
+ File screenshot =
+ new File(
+ LOG_DATA_DIR,
+ String.format("%s_screenshot_%s.png", packageName, description));
+ mDevice.takeScreenshot(screenshot);
+ }
+
+ /**
+ * Start the screen recording.
+ *
+ * @param packageName The package name of 3P apps screenrecorded.
+ */
+ public void startRecording(String packageName) {
+ Log.v(TAG, "Started Recording");
+ mCurrentThread =
+ new RecordingThread(
+ "test-screen-record", String.format("%s_screenrecord", packageName));
+ mCurrentThread.start();
+ }
+
+ /** Stop already started screen recording. */
+ public void stopRecording() {
+ // Skip if not directory.
+ if (mLogDataDir == null) {
+ return;
+ }
+ // Add some extra time to the video end.
+ SystemClock.sleep(VIDEO_TAIL_BUFFER);
+ // Ctrl + C all screen record processes.
+ mCurrentThread.cancel();
+ // Wait for the thread to completely die.
+ try {
+ mCurrentThread.join();
+ } catch (InterruptedException ex) {
+ Log.e(TAG, "Interrupted when joining the recording thread.", ex);
+ }
+ Log.v(TAG, "Stopped Recording");
+ }
+
+ /** Returns the recording's name for {@code part} of launch description. */
+ public File getOutputFile(String description, int part) {
+ // Omit the iteration number for the first iteration.
+ final String fileName = String.format("%s-video%s.mp4", description, part == 1 ? "" : part);
+ return Paths.get(mLogDataDir.getAbsolutePath(), fileName).toFile();
+ }
+
+ /**
+ * Encapsulates the start and stop screen recording logic. Copied from ScreenRecordCollector.
+ */
+ private class RecordingThread extends Thread {
+ private final String mDescription;
+
+ private boolean mContinue;
+
+ RecordingThread(String name, String description) {
+ super(name);
+
+ mContinue = true;
+
+ Assert.assertNotNull("No test description provided for recording.", description);
+ mDescription = description;
+ }
+
+ @Override
+ public void run() {
+ try {
+ // Start at i = 1 to encode parts as X.mp4, X2.mp4, X3.mp4, etc.
+ for (int i = 1; i <= MAX_RECORDING_PARTS && mContinue; i++) {
+ File output = getOutputFile(mDescription, i);
+ Log.d(TAG, String.format("Recording screen to %s", output.getAbsolutePath()));
+ // Make sure not to block on this background command in the main thread so
+ // that the test continues to run, but block in this thread so it does not
+ // trigger a new screen recording session before the prior one completes.
+ mDevice.executeShellCommand(
+ String.format("screenrecord %s", output.getAbsolutePath()));
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Caught exception while screen recording.");
+ }
+ }
+
+ public void cancel() {
+ mContinue = false;
+ // Identify the screenrecord PIDs and send SIGINT 2 (Ctrl + C) to each.
+ try {
+ String[] pids = mDevice.executeShellCommand("pidof screenrecord").split(" ");
+ for (String pid : pids) {
+ // Avoid empty process ids, because of weird splitting behavior.
+ if (pid.isEmpty()) {
+ continue;
+ }
+ mDevice.executeShellCommand(String.format("kill -2 %s", pid));
+ Log.d(TAG, String.format("Sent SIGINT 2 to screenrecord process (%s)", pid));
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to kill screen recording process.");
+ }
+ }
+ }
+}
diff --git a/test_scripts/src/main/java/com/android/webview/OWNERS b/test_scripts/src/main/java/com/android/webview/OWNERS
new file mode 100644
index 0000000..af3a7c8
--- /dev/null
+++ b/test_scripts/src/main/java/com/android/webview/OWNERS
@@ -0,0 +1,2 @@
+amitku@google.com
+rmhasan@google.com \ No newline at end of file
diff --git a/test_scripts/src/main/java/com/android/webview/tests/WebviewAppLaunchTest.java b/test_scripts/src/main/java/com/android/webview/tests/WebviewAppLaunchTest.java
new file mode 100644
index 0000000..d08d2e3
--- /dev/null
+++ b/test_scripts/src/main/java/com/android/webview/tests/WebviewAppLaunchTest.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2022 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.webview.tests;
+
+import com.android.csuite.core.ApkInstaller;
+import com.android.csuite.core.ApkInstaller.ApkInstallerException;
+import com.android.csuite.core.DeviceUtils;
+import com.android.csuite.core.DeviceUtils.DeviceTimestamp;
+import com.android.csuite.core.DeviceUtils.DeviceUtilsException;
+import com.android.csuite.core.TestUtils;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.Option.Importance;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.AaptParser;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.RunUtil;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** A test that verifies that a single app can be successfully launched. */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class WebviewAppLaunchTest extends BaseHostJUnit4Test {
+ @Rule public TestLogData mLogData = new TestLogData();
+ private ApkInstaller mApkInstaller;
+ private List<File> mOrderedWebviewApks = new ArrayList<>();
+
+ @Option(name = "record-screen", description = "Whether to record screen during test.")
+ private boolean mRecordScreen;
+
+ @Option(name = "package-name", description = "Package name of testing app.")
+ private String mPackageName;
+
+ @Option(
+ name = "install-apk",
+ description =
+ "The path to an apk file or a directory of apk files of a singe package to be"
+ + " installed on device. Can be repeated.")
+ private List<File> mApkPaths = new ArrayList<>();
+
+ @Option(
+ name = "install-arg",
+ description = "Arguments for the 'adb install-multiple' package installation command.")
+ private final List<String> mInstallArgs = new ArrayList<>();
+
+ @Option(
+ name = "app-launch-timeout-ms",
+ description = "Time to wait for an app to launch in msecs.")
+ private int mAppLaunchTimeoutMs = 20000;
+
+ @Option(
+ name = "webview-apk-dir",
+ description = "The path to the webview apk.",
+ importance = Importance.ALWAYS)
+ private File mWebviewApkDir;
+
+ @Before
+ public void setUp() throws DeviceNotAvailableException, ApkInstallerException, IOException {
+ Assert.assertNotNull("Package name cannot be null", mPackageName);
+
+ readWebviewApkDirectory();
+
+ mApkInstaller = ApkInstaller.getInstance(getDevice());
+ for (File apkPath : mApkPaths) {
+ CLog.d("Installing " + apkPath);
+ mApkInstaller.install(
+ apkPath.toPath(), mInstallArgs.toArray(new String[mInstallArgs.size()]));
+ }
+
+ DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice());
+ deviceUtils.freezeRotation();
+
+ printWebviewVersion();
+ }
+
+ @Test
+ public void testAppLaunch()
+ throws DeviceNotAvailableException, ApkInstallerException, IOException {
+ AssertionError lastError = null;
+ // Try the latest webview version
+ WebviewPackage lastWebviewInstalled = installWebview(mOrderedWebviewApks.get(0));
+ try {
+ assertAppLaunchNoCrash();
+ } catch (AssertionError e) {
+ lastError = e;
+ } finally {
+ uninstallWebview();
+ }
+
+ // If the app doesn't crash, complete the test.
+ if (lastError == null) {
+ return;
+ }
+
+ // If the app crashes, try the app with the original webview version that comes with the
+ // device.
+ try {
+ assertAppLaunchNoCrash();
+ } catch (AssertionError newError) {
+ CLog.w(
+ "The app %s crashed both with and without the webview installation,"
+ + " ignoring the failure...",
+ mPackageName);
+ return;
+ }
+
+ for (int idx = 1; idx < mOrderedWebviewApks.size(); idx++) {
+ lastWebviewInstalled = installWebview(mOrderedWebviewApks.get(idx));
+ try {
+ assertAppLaunchNoCrash();
+ } catch (AssertionError e) {
+ lastError = e;
+ continue;
+ } finally {
+ uninstallWebview();
+ }
+ break;
+ }
+
+ throw new AssertionError(
+ String.format(
+ "Package %s crashed since webview version %s",
+ mPackageName, lastWebviewInstalled.getVersion()),
+ lastError);
+ }
+
+ @After
+ public void tearDown() throws DeviceNotAvailableException, ApkInstallerException {
+ TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData);
+ testUtils.collectScreenshot(mPackageName);
+
+ DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice());
+ deviceUtils.stopPackage(mPackageName);
+ deviceUtils.unfreezeRotation();
+
+ mApkInstaller.uninstallAllInstalledPackages();
+ printWebviewVersion();
+ }
+
+ private void readWebviewApkDirectory() {
+ mOrderedWebviewApks = Arrays.asList(mWebviewApkDir.listFiles());
+ Collections.sort(
+ mOrderedWebviewApks,
+ new Comparator<File>() {
+ @Override
+ public int compare(File apk1, File apk2) {
+ return getVersionCode(apk2).compareTo(getVersionCode(apk1));
+ }
+
+ private Long getVersionCode(File apk) {
+ return Long.parseLong(AaptParser.parse(apk).getVersionCode());
+ }
+ });
+ }
+
+ private void printWebviewVersion(WebviewPackage currentWebview)
+ throws DeviceNotAvailableException {
+ CLog.i("Current webview implementation: %s", currentWebview.getPackageName());
+ CLog.i("Current webview version: %s", currentWebview.getVersion());
+ }
+
+ private void printWebviewVersion() throws DeviceNotAvailableException {
+ WebviewPackage currentWebview = getCurrentWebviewPackage();
+ printWebviewVersion(currentWebview);
+ }
+
+ private WebviewPackage installWebview(File apk)
+ throws ApkInstallerException, IOException, DeviceNotAvailableException {
+ ApkInstaller.getInstance(getDevice()).install(apk.toPath());
+ CommandResult res =
+ getDevice()
+ .executeShellV2Command(
+ "cmd webviewupdate set-webview-implementation com.android.webview");
+ Assert.assertEquals(
+ "Failed to set webview update: " + res, res.getStatus(), CommandStatus.SUCCESS);
+ WebviewPackage currentWebview = getCurrentWebviewPackage();
+ printWebviewVersion(currentWebview);
+ return currentWebview;
+ }
+
+ private void uninstallWebview() throws DeviceNotAvailableException {
+ getDevice()
+ .executeShellCommand(
+ "cmd webviewupdate set-webview-implementation com.google.android.webview");
+ getDevice().executeAdbCommand("uninstall", "com.android.webview");
+ }
+
+ private WebviewPackage getCurrentWebviewPackage() throws DeviceNotAvailableException {
+ String dumpsys = getDevice().executeShellCommand("dumpsys webviewupdate");
+ return WebviewPackage.parseFrom(dumpsys);
+ }
+
+ private static class WebviewPackage {
+ private final String mPackageName;
+ private final String mVersion;
+
+ private WebviewPackage(String packageName, String version) {
+ mPackageName = packageName;
+ mVersion = version;
+ }
+
+ static WebviewPackage parseFrom(String dumpsys) {
+ Pattern pattern =
+ Pattern.compile("Current WebView package \\(name, version\\): \\((.*?)\\)");
+ Matcher matcher = pattern.matcher(dumpsys);
+ Assert.assertTrue("Cannot parse webview package info from: " + dumpsys, matcher.find());
+ String[] packageInfo = matcher.group(1).split(",");
+ return new WebviewPackage(packageInfo[0].strip(), packageInfo[1].strip());
+ }
+
+ String getPackageName() {
+ return mPackageName;
+ }
+
+ String getVersion() {
+ return mVersion;
+ }
+ }
+
+ private void assertAppLaunchNoCrash() throws DeviceNotAvailableException {
+ DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice());
+ deviceUtils.resetPackage(mPackageName);
+ TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData);
+
+ if (mRecordScreen) {
+ testUtils.collectScreenRecord(
+ () -> {
+ launchPackageAndCheckForCrash();
+ },
+ mPackageName);
+ } else {
+ launchPackageAndCheckForCrash();
+ }
+ }
+
+ private void launchPackageAndCheckForCrash() throws DeviceNotAvailableException {
+ CLog.d("Launching package: %s.", mPackageName);
+
+ DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice());
+ TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData);
+
+ DeviceTimestamp startTime = deviceUtils.currentTimeMillis();
+ try {
+ deviceUtils.launchPackage(mPackageName);
+ } catch (DeviceUtilsException e) {
+ Assert.fail(e.getMessage());
+ }
+
+ CLog.d("Waiting %s milliseconds for the app to launch fully.", mAppLaunchTimeoutMs);
+ RunUtil.getDefault().sleep(mAppLaunchTimeoutMs);
+
+ CLog.d("Completed launching package: %s", mPackageName);
+
+ try {
+ String crashLog = testUtils.getDropboxPackageCrashLog(mPackageName, startTime, true);
+ if (crashLog != null) {
+ Assert.fail(crashLog);
+ }
+ } catch (IOException e) {
+ Assert.fail("Error while getting dropbox crash log: " + e);
+ }
+ }
+}
diff --git a/test_targets/csuite-app-crawl/template.xml b/test_targets/csuite-app-crawl/template.xml
index 42c91a4..406cf68 100644
--- a/test_targets/csuite-app-crawl/template.xml
+++ b/test_targets/csuite-app-crawl/template.xml
@@ -14,14 +14,14 @@
limitations under the License.
-->
<configuration description="C-Suite Crawler test configuration">
- <option name="package-name" value="{package}"/>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="input keyevent KEYCODE_WAKEUP"/>
<option name="run-command" value="input keyevent KEYCODE_MENU"/>
<option name="run-command" value="input keyevent KEYCODE_HOME"/>
</target_preparer>
<test class="com.android.tradefed.testtype.HostTest" >
- <option name="set-option" value="apk:app://{package}"/>
+ <option name="set-option" value="package-name:{package}"/>
+ <option name="set-option" value="apk:app\://{package}"/>
<option name="class" value="com.android.csuite.tests.AppCrawlTest" />
</test>
</configuration> \ No newline at end of file
diff --git a/test_targets/csuite-app-launch/default.xml b/test_targets/csuite-app-launch/default.xml
index d012df9..e583002 100644
--- a/test_targets/csuite-app-launch/default.xml
+++ b/test_targets/csuite-app-launch/default.xml
@@ -14,10 +14,6 @@
limitations under the License.
-->
<configuration description="Launches an app and check for crashes">
- <option name="package-name" value="{package}"/>
- <target_preparer class="com.android.compatibility.targetprep.AppSetupPreparer">
- <option name="test-file-name" value="app://{package}"/>
- </target_preparer>
<target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="input keyevent KEYCODE_WAKEUP"/>
@@ -26,6 +22,7 @@
</target_preparer>
<test class="com.android.tradefed.testtype.HostTest" >
<option name="set-option" value="package-name:{package}"/>
+ <option name="set-option" value="install-apk:app\://{package}"/>
<option name="class" value="com.android.csuite.tests.AppLaunchTest" />
</test>
</configuration> \ No newline at end of file
diff --git a/test_targets/csuite-test-package-launch/template.xml b/test_targets/csuite-test-package-launch/template.xml
index f77ddc3..34ba321 100644
--- a/test_targets/csuite-test-package-launch/template.xml
+++ b/test_targets/csuite-test-package-launch/template.xml
@@ -14,11 +14,6 @@
limitations under the License.
-->
<configuration description="Installs a test package with -t arg and check for launch crashes">
- <option name="package-name" value="{package}"/>
- <target_preparer class="com.android.compatibility.targetprep.AppSetupPreparer">
- <option name="test-file-name" value="app://{package}"/>
- <option name="install-arg" value="-t"/>
- </target_preparer>
<target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="input keyevent KEYCODE_WAKEUP"/>
@@ -27,6 +22,8 @@
</target_preparer>
<test class="com.android.tradefed.testtype.HostTest" >
<option name="set-option" value="package-name:{package}"/>
+ <option name="set-option" value="install-apk:app\://{package}"/>
+ <option name="set-option" value="install-arg:-t"/>
<option name="class" value="com.android.csuite.tests.AppLaunchTest" />
</test>
</configuration> \ No newline at end of file
diff --git a/test_targets/drm-app-launch/template.xml b/test_targets/drm-app-launch/template.xml
index e723cac..f7abcdd 100644
--- a/test_targets/drm-app-launch/template.xml
+++ b/test_targets/drm-app-launch/template.xml
@@ -14,11 +14,6 @@
limitations under the License.
-->
<configuration description="DRM launch test configuration">
- <option name="package-name" value="{package}"/>
- <target_preparer class="com.android.compatibility.targetprep.AppSetupPreparer">
- <option name="test-file-name" value="{package_install_file}"/>
- <option name="install-arg" value="-t"/>
- </target_preparer>
<target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="input keyevent KEYCODE_WAKEUP"/>
@@ -27,6 +22,8 @@
</target_preparer>
<test class="com.android.tradefed.testtype.HostTest" >
<option name="set-option" value="package-name:{package}"/>
+ <option name="set-option" value="install-apk:{package_install_file}"/>
+ <option name="set-option" value="install-arg:-t"/>
<option name="class" value="com.android.csuite.tests.AppLaunchTest" />
</test>
</configuration> \ No newline at end of file
diff --git a/test_targets/pixel-app-launch-lock/Android.bp b/test_targets/pixel-app-launch-lock/Android.bp
new file mode 100644
index 0000000..8261309
--- /dev/null
+++ b/test_targets/pixel-app-launch-lock/Android.bp
@@ -0,0 +1,22 @@
+// Copyright (C) 2022 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+csuite_test {
+ name: "pixel-app-launch-lock",
+ test_config_template: "template.xml",
+}
diff --git a/test_targets/pixel-app-launch-lock/OWNERS b/test_targets/pixel-app-launch-lock/OWNERS
new file mode 100644
index 0000000..05ffe9a
--- /dev/null
+++ b/test_targets/pixel-app-launch-lock/OWNERS
@@ -0,0 +1,2 @@
+murphykuo@google.com
+huilingchi@google.com
diff --git a/test_targets/pixel-app-launch-lock/template.xml b/test_targets/pixel-app-launch-lock/template.xml
new file mode 100644
index 0000000..7751426
--- /dev/null
+++ b/test_targets/pixel-app-launch-lock/template.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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
+
+
+ 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.
+-->
+<configuration description="Launches an app and lock/unlock the device">
+ <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="app://{package}"/>
+ <option name="test-file-name" value="PixelAppCompTests.apk" />
+ </target_preparer>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <!-- repeatable: The key of the DIRECTORY to pull -->
+ <option name = "directory-keys" value = "/sdcard/logData" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="package" value="com.android.pixel.tests" />
+ <option name="include-filter" value="com.android.pixel.tests.AppLaunchLockTest" />
+ <option name="instrumentation-arg" key="package" value="{package}" />
+ <option name="isolated-storage" value="false" />
+ </test>
+</configuration>
diff --git a/test_targets/pixel-app-launch-recentapp/Android.bp b/test_targets/pixel-app-launch-recentapp/Android.bp
new file mode 100644
index 0000000..4458cf3
--- /dev/null
+++ b/test_targets/pixel-app-launch-recentapp/Android.bp
@@ -0,0 +1,22 @@
+// Copyright (C) 2022 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+csuite_test {
+ name: "pixel-app-launch-recentapp",
+ test_config_template: "template.xml",
+}
diff --git a/test_targets/pixel-app-launch-recentapp/OWNERS b/test_targets/pixel-app-launch-recentapp/OWNERS
new file mode 100644
index 0000000..05ffe9a
--- /dev/null
+++ b/test_targets/pixel-app-launch-recentapp/OWNERS
@@ -0,0 +1,2 @@
+murphykuo@google.com
+huilingchi@google.com
diff --git a/test_targets/pixel-app-launch-recentapp/template.xml b/test_targets/pixel-app-launch-recentapp/template.xml
new file mode 100644
index 0000000..86742b7
--- /dev/null
+++ b/test_targets/pixel-app-launch-recentapp/template.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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
+
+
+ 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.
+-->
+<configuration description="Launches an app by tapping recent app key">
+ <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="app://{package}"/>
+ <option name="test-file-name" value="PixelAppCompTests.apk" />
+ </target_preparer>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <!-- repeatable: The key of the DIRECTORY to pull -->
+ <option name = "directory-keys" value = "/sdcard/logData" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="package" value="com.android.pixel.tests" />
+ <option name="include-filter" value="com.android.pixel.tests.AppLaunchRecentAppTest" />
+ <option name="instrumentation-arg" key="package" value="{package}" />
+ <option name="isolated-storage" value="false" />
+ </test>
+</configuration>
diff --git a/test_targets/pixel-app-launch-rotate/Android.bp b/test_targets/pixel-app-launch-rotate/Android.bp
new file mode 100644
index 0000000..89419ae
--- /dev/null
+++ b/test_targets/pixel-app-launch-rotate/Android.bp
@@ -0,0 +1,22 @@
+// Copyright (C) 2022 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+csuite_test {
+ name: "pixel-app-launch-rotate",
+ test_config_template: "template.xml",
+}
diff --git a/test_targets/pixel-app-launch-rotate/OWNERS b/test_targets/pixel-app-launch-rotate/OWNERS
new file mode 100644
index 0000000..05ffe9a
--- /dev/null
+++ b/test_targets/pixel-app-launch-rotate/OWNERS
@@ -0,0 +1,2 @@
+murphykuo@google.com
+huilingchi@google.com
diff --git a/test_targets/pixel-app-launch-rotate/template.xml b/test_targets/pixel-app-launch-rotate/template.xml
new file mode 100644
index 0000000..ff7871a
--- /dev/null
+++ b/test_targets/pixel-app-launch-rotate/template.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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
+
+
+ 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.
+-->
+<configuration description="Launches an app, rotate screen, and check for crashes">
+ <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="app://{package}"/>
+ <option name="test-file-name" value="PixelAppCompTests.apk" />
+ </target_preparer>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <!-- repeatable: The key of the DIRECTORY to pull -->
+ <option name = "directory-keys" value = "/sdcard/logData" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="package" value="com.android.pixel.tests" />
+ <option name="include-filter" value="com.android.pixel.tests.AppLaunchRotateTest" />
+ <option name="instrumentation-arg" key="package" value="{package}" />
+ <option name="isolated-storage" value="false" />
+ </test>
+</configuration>
diff --git a/test_targets/webview-app-launch/Android.bp b/test_targets/webview-app-launch/Android.bp
new file mode 100644
index 0000000..3658018
--- /dev/null
+++ b/test_targets/webview-app-launch/Android.bp
@@ -0,0 +1,22 @@
+// Copyright (C) 2022 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+csuite_test {
+ name: "webview-app-launch",
+ test_config_template: "default.xml",
+}
diff --git a/test_targets/webview-app-launch/OWNERS b/test_targets/webview-app-launch/OWNERS
new file mode 100644
index 0000000..af3a7c8
--- /dev/null
+++ b/test_targets/webview-app-launch/OWNERS
@@ -0,0 +1,2 @@
+amitku@google.com
+rmhasan@google.com \ No newline at end of file
diff --git a/test_targets/webview-app-launch/default.xml b/test_targets/webview-app-launch/default.xml
new file mode 100644
index 0000000..0b5470b
--- /dev/null
+++ b/test_targets/webview-app-launch/default.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<configuration description="Launches an app and check for crashes">
+ <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP"/>
+ <option name="run-command" value="input keyevent KEYCODE_MENU"/>
+ <option name="run-command" value="input keyevent KEYCODE_HOME"/>
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="set-option" value="package-name:{package}"/>
+ <option name="set-option" value="install-apk:app\://{package}"/>
+ <option name="class" value="com.android.webview.tests.WebviewAppLaunchTest" />
+ </test>
+</configuration> \ No newline at end of file