aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkuantung <kuantung@google.com>2017-09-07 12:57:39 -0700
committerkuantung <kuantung@google.com>2017-09-07 12:57:39 -0700
commit5da3c14f79d42c132c3663775254cb0b2d111319 (patch)
tree646d291bb7cf65b95219d1037cc1ae2a084e9e2d
parent307c18ff4aa64197526f06cb1db3d756c389065b (diff)
parent2f6763757c432159b9e8668669b8b3cb14cff03d (diff)
downloadAfwTestHarness-oreo-dev.tar.gz
import history from sso://googleplex-android/platform/test/AfwTestHarness oc-dev b/65199386oreo-dev
-rw-r--r--AfwTestCaseList.mk55
-rw-r--r--AfwTestHarnessBuild.mk65
-rw-r--r--AfwTestHarnessBuildUtil.mk31
-rw-r--r--Android.mk21
-rw-r--r--LICENSE202
-rw-r--r--README113
-rw-r--r--afw-test.props17
-rw-r--r--apps/Android.mk18
-rw-r--r--apps/DeviceAdmin/Android.mk33
-rw-r--r--apps/DeviceAdmin/AndroidManifest.xml45
-rw-r--r--apps/DeviceAdmin/README13
-rw-r--r--apps/DeviceAdmin/res/xml/device_admin.xml21
-rw-r--r--apps/DeviceAdmin/src/com/android/afwtest/deviceadmin/AdminReceiver.java25
-rw-r--r--apps/DeviceAdmin/src/com/android/afwtest/deviceadmin/FactoryResetActivity.java65
-rw-r--r--apps/SystemUtil/Android.mk33
-rw-r--r--apps/SystemUtil/AndroidManifest.xml65
-rw-r--r--apps/SystemUtil/README34
-rw-r--r--apps/SystemUtil/afw-test-system-util-permissions.xml23
-rw-r--r--apps/SystemUtil/src/com/android/afwtest/systemutil/ChangeLocale.java113
-rw-r--r--apps/SystemUtil/src/com/android/afwtest/systemutil/NfcBumpSender.java61
-rw-r--r--apps/SystemUtil/src/com/android/afwtest/systemutil/RemoveGoogleAccount.java151
-rw-r--r--apps/SystemUtil/src/com/android/afwtest/systemutil/StartQRCodeProvisioning.java58
-rw-r--r--apps/Util/Android.mk33
-rw-r--r--apps/Util/AndroidManifest.xml34
-rw-r--r--apps/Util/README.txt17
-rw-r--r--apps/Util/src/com/android/afwtest/util/NetworkMonitor.java98
-rw-r--r--apps/Util/src/com/android/afwtest/util/Wifi.java133
-rw-r--r--apps/Util/src/com/android/afwtest/util/WifiConfig.java145
-rw-r--r--apps/Util/src/com/android/afwtest/util/WifiConnector.java145
-rw-r--r--build/config.mk27
-rw-r--r--build/module_test_config.mk24
-rw-r--r--build/support_package.mk35
-rw-r--r--build/test_host_java_library.mk30
-rw-r--r--build/test_package.mk29
-rw-r--r--libs/Android.mk18
-rw-r--r--libs/CommonLib/Android.mk29
-rw-r--r--libs/CommonLib/src/com/android/afwtest/common/AccountManagerUtils.java79
-rw-r--r--libs/CommonLib/src/com/android/afwtest/common/Constants.java214
-rw-r--r--libs/CommonLib/src/com/android/afwtest/common/FileUtils.java86
-rw-r--r--libs/CommonLib/src/com/android/afwtest/common/NetworkUtils.java104
-rw-r--r--libs/CommonLib/src/com/android/afwtest/common/PkgMgrUtils.java45
-rw-r--r--libs/CommonLib/src/com/android/afwtest/common/Preconditions.java45
-rw-r--r--libs/CommonLib/src/com/android/afwtest/common/Timer.java95
-rw-r--r--libs/CommonLib/src/com/android/afwtest/common/nfcprovisioning/NfcBumpSimulator.java128
-rw-r--r--libs/CommonLib/src/com/android/afwtest/common/nfcprovisioning/Utils.java72
-rw-r--r--libs/CommonLib/src/com/android/afwtest/common/qrcodeprovisioning/QRCodeSimulator.java84
-rw-r--r--libs/CommonLib/src/com/android/afwtest/common/qrcodeprovisioning/Utils.java70
-rw-r--r--libs/CommonLib/src/com/android/afwtest/common/test/OemWidget.java177
-rw-r--r--libs/CommonLib/src/com/android/afwtest/common/test/StatsLogger.java153
-rw-r--r--libs/CommonLib/src/com/android/afwtest/common/test/TestConfig.java472
-rw-r--r--libs/UiAutomatorLib/Android.mk29
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/Constants.java282
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/LandingPage.java65
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/PageSkipper.java233
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/UiPage.java161
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/AddAccountPage.java110
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/BackUpToDrivePage.java72
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/EnterPasswordPage.java116
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleAppsDevicePolicyPage.java122
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleServicesPage.java118
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/BasePage.java122
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/ErrorPage.java74
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourDevicePage.java113
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/TermsPage.java81
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/packageinstaller/DeviceAccessPage.java59
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/FinishSetupPage.java133
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishedPage.java122
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementDoPage.java66
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementPoPage.java67
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/provisioning/AutomationDriver.java318
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/test/AbstractTestCase.java57
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/test/AfwTestUiWatcher.java266
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/BySelectorHelper.java71
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/Device.java46
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/TextField.java153
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/WidgetUtils.java657
-rw-r--r--licenses.html311
-rw-r--r--tests/Android.mk17
-rw-r--r--tests/NfcProvisioning/Android.mk33
-rw-r--r--tests/NfcProvisioning/AndroidManifest.xml32
-rw-r--r--tests/NfcProvisioning/AndroidTest.xml44
-rw-r--r--tests/NfcProvisioning/src/com/android/afwtest/nfcprovisioning/NfcProvisioningTest.java97
-rw-r--r--tests/NonSuwPoProvisioning/Android.mk33
-rw-r--r--tests/NonSuwPoProvisioning/AndroidManifest.xml32
-rw-r--r--tests/NonSuwPoProvisioning/AndroidTest.xml60
-rw-r--r--tests/NonSuwPoProvisioning/src/com/android/afwtest/nonsuwpoprovisioning/NonSuwPoProvisioningTest.java91
-rw-r--r--tests/QRCodeProvisioning/Android.mk33
-rw-r--r--tests/QRCodeProvisioning/AndroidManifest.xml32
-rw-r--r--tests/QRCodeProvisioning/AndroidTest.xml43
-rw-r--r--tests/QRCodeProvisioning/src/com/android/afwtest/qrcodeprovisioning/QRCodeProvisioningTest.java111
-rw-r--r--tests/SuwDoProvisioning/Android.mk33
-rw-r--r--tests/SuwDoProvisioning/AndroidManifest.xml32
-rw-r--r--tests/SuwDoProvisioning/AndroidTest.xml47
-rw-r--r--tests/SuwDoProvisioning/src/com/android/afwtest/suwdoprovisioning/SuwDoProvisioningTest.java78
-rw-r--r--tests/SuwPoProvisioning/Android.mk33
-rw-r--r--tests/SuwPoProvisioning/AndroidManifest.xml32
-rw-r--r--tests/SuwPoProvisioning/AndroidTest.xml57
-rw-r--r--tests/SuwPoProvisioning/src/com/android/afwtest/suwpoprovisioning/SuwPoProvisioningTest.java62
-rw-r--r--tools/Android.mk31
-rw-r--r--tools/prebuilt/CtsDeviceInfo.apkbin0 -> 2148631 bytes
-rw-r--r--tools/prebuilt/README5
-rw-r--r--tools/prebuilt/TestDpc.apkbin0 -> 2861928 bytes
-rw-r--r--tools/tradefed-host/Android.mk47
-rw-r--r--tools/tradefed-host/MANIFEST.mf3
-rw-r--r--tools/tradefed-host/README50
-rw-r--r--tools/tradefed-host/etc/Android.mk26
-rwxr-xr-xtools/tradefed-host/etc/afw-test-tradefed103
-rw-r--r--tools/tradefed-host/res/config/afw-test-common-base.xml38
-rw-r--r--tools/tradefed-host/res/config/afw-test-common.xml22
-rw-r--r--tools/tradefed-host/res/config/afw-test-create-managed-profile.xml22
-rw-r--r--tools/tradefed-host/res/config/afw-test-encrypt-device.xml19
-rw-r--r--tools/tradefed-host/res/config/afw-test-factory-reset.xml47
-rw-r--r--tools/tradefed-host/res/config/afw-test-push-test-suite-config.xml25
-rw-r--r--tools/tradefed-host/res/config/afw-test-remove-users.xml25
-rw-r--r--tools/tradefed-host/res/config/afw-test-uninstall-testdpc-after-test.xml24
-rw-r--r--tools/tradefed-host/res/config/afw-test-uninstall-testdpc-and-reset-users.xml28
-rw-r--r--tools/tradefed-host/res/config/afw-test-uninstall-testdpc-before-test.xml24
-rw-r--r--tools/tradefed-host/res/config/afw-test-wifi.xml32
-rw-r--r--tools/tradefed-host/res/config/afw-user-build.xml24
-rw-r--r--tools/tradefed-host/res/config/afw-userdebug-build.xml27
-rw-r--r--tools/tradefed-host/res/config/cts.xml55
-rw-r--r--tools/tradefed-host/res/report/cts_result.xsl610
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/TestConfig.java186
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestAdbRootOptionPreparer.java62
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestAppUninstaller.java110
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEncryptDevice.java155
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEnvDumper.java183
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestFactoryReset.java179
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestGoogleAccountRemover.java201
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestManagedProfileCreator.java89
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPrivAppInstaller.java118
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPullExternalFile.java78
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestSyncTime.java45
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestTargetPreparer.java204
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestUserRemover.java114
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestWifiPreparer.java305
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/AfwTestMetricsCollectorTargetPreparer.java216
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/LogcatMetricsCollector.java171
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/MemoryMetricsCollector.java81
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/MetricsCollector.java45
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/MetricsCollectorException.java48
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/MetricsUtils.java48
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/memory/MemoryByProcessGrouper.java56
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/memory/MemoryByUserGrouper.java61
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/memory/MemoryCollectorTask.java110
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/memory/MemoryInfoGrouper.java49
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/metrics.proto100
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/AfwAndroidJUnitTest.java54
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/AfwTest.java161
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/ScreenshotListener.java93
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/utils/AdbShellUtils.java191
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/utils/ReflectionUtils.java75
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/utils/StreamDiscarder.java56
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/utils/TimeUtil.java66
-rw-r--r--zip_exclude.lst3
155 files changed, 13939 insertions, 0 deletions
diff --git a/AfwTestCaseList.mk b/AfwTestCaseList.mk
new file mode 100644
index 0000000..8146e37
--- /dev/null
+++ b/AfwTestCaseList.mk
@@ -0,0 +1,55 @@
+#
+# Copyright (C) 2016 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.
+#
+
+# Test packages that require an associated test package XML.
+afw_th_test_packages := \
+ AfwTestNfcProvisioningTestCases \
+ AfwTestNonSuwPoProvisioningTestCases \
+ AfwTestQRCodeProvisioningTestCases \
+ AfwTestSuwDoProvisioningTestCases \
+ AfwTestSuwPoProvisioningTestCases
+
+# Support packages
+afw_th_support_packages := \
+ AfwThDeviceAdmin \
+ AfwThSystemUtil \
+ AfwThUtil
+
+# Additional prebuilt utilities used by tests
+afw_th_prebuilt_utils := \
+ CtsDeviceInfo.apk \
+ TestDpc.apk
+
+afw_th_host_libraries :=
+
+
+# All the test case apk files that will end up under the repository/testcases
+# directory of the final AfW Test distribution.
+AFW_TH_TEST_CASES := $(call afw-th-get-package-paths,$(afw_th_test_packages)) \
+ $(call afw-th-get-lib-paths,$(afw_th_host_libraries))
+
+# All the support apk files that will end up under repository/testcases
+# directory of the final AfW Test distribution.
+AFW_TH_SUPPORT_PACKAGE_APKS := $(call afw-th-get-package-paths,$(afw_th_support_packages))
+
+# All the XMLs that will end up under the repository/testcases
+# and that need to be created before making the final AfW Test Harness distribution.
+AFW_TH_TEST_CONFIGS := $(call afw-th-get-test-configs,$(afw_th_test_packages)) \
+ $(call afw-th-get-test-configs,$(afw_th_host_libraries))
+
+# The following files will be placed in the tools directory of the Harness distribution
+AFW_TH_TOOLS_LIST :=
+
diff --git a/AfwTestHarnessBuild.mk b/AfwTestHarnessBuild.mk
new file mode 100644
index 0000000..5a0293d
--- /dev/null
+++ b/AfwTestHarnessBuild.mk
@@ -0,0 +1,65 @@
+#
+# Copyright (C) 2016 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.
+#
+
+-include test/AfwTestHarness/AfwTestCaseList.mk
+
+afw_th_src_dir := test/AfwTestHarness
+afw_th_dir := $(HOST_OUT)/afw-th
+afw_th_tools_src_dir := $(afw_th_src_dir)/tools
+afw_th_tests_src_dir := $(afw_th_src_dir)/tests
+
+TH_COMPATIBILITY_HOST_UTIL_JAR := $(HOST_OUT_JAVA_LIBRARIES)/compatibility-host-util.jar
+TF_JAR := $(HOST_OUT_JAVA_LIBRARIES)/tradefed.jar
+AFW_TH_TF_JAR := $(HOST_OUT_JAVA_LIBRARIES)/afw-test-tradefed.jar
+AFW_TH_TF_EXEC_PATH ?= $(HOST_OUT_EXECUTABLES)/afw-test-tradefed
+AFW_TH_TF_README_PATH := $(afw_th_tools_src_dir)/tradefed-host/README
+
+AFW_TH_PREBUILT_UTILS_PATH := $(foreach prebuilt, $(afw_th_prebuilt_utils), $(afw_th_tools_src_dir)/prebuilt/$(prebuilt))
+
+$(afw_th_dir)/all_afw_th_files_stamp: $(AFW_TH_TEST_CASES) $(AFW_TH_SUPPORT_PACKAGE_APKS) $(TF_JAR) $(AFW_TH_TF_JAR) $(AFW_TH_TF_EXEC_PATH) $(AFW_TH_TF_README_PATH) $(TH_COMPATIBILITY_HOST_UTIL_JAR) $(ACP)
+
+# Make necessary directory for afw-test
+ $(hide) mkdir -p $(TMP_DIR)
+ $(hide) mkdir -p $(PRIVATE_DIR)/tools
+ $(hide) mkdir -p $(PRIVATE_DIR)/testcases
+# Copy executable and JARs to afw-test directory
+ $(hide) $(ACP) -fp $(TF_JAR) $(AFW_TH_TF_JAR) $(AFW_TH_TF_EXEC_PATH) $(AFW_TH_TF_README_PATH) $(TH_COMPATIBILITY_HOST_UTIL_JAR) $(PRIVATE_DIR)/tools
+ $(hide) $(ACP) -fp $(AFW_TH_PREBUILT_UTILS_PATH) $(PRIVATE_DIR)/testcases
+ $(hide) touch $@
+
+
+afw_th_name := android-afw-test-harness
+
+# Package afw-test-harness and clean up.
+#
+INTERNAL_AFW_TH_TARGET := $(afw_th_dir)/$(afw_th_name).zip
+$(INTERNAL_AFW_TH_TARGET): PRIVATE_NAME := android-cts
+$(INTERNAL_AFW_TH_TARGET): PRIVATE_AFW_TH_DIR := $(afw_th_dir)
+$(INTERNAL_AFW_TH_TARGET): PRIVATE_DIR := $(afw_th_dir)/android-cts
+$(INTERNAL_AFW_TH_TARGET): TMP_DIR := $(afw_th_dir)/temp
+$(INTERNAL_AFW_TH_TARGET): $(afw_th_dir)/all_afw_th_files_stamp $(AFW_TH_TEST_CONFIGS)
+ $(hide) echo "Package Android for Work Test Harness: $@"
+ $(hide) $(ACP) -f $(afw_th_src_dir)/afw-test.props $(PRIVATE_DIR)/testcases
+ $(hide) $(ACP) -f $(afw_th_src_dir)/zip_exclude.lst $(afw_th_dir)
+ $(hide) $(ACP) -f $(afw_th_src_dir)/apps/SystemUtil/afw-test-system-util-permissions.xml $(PRIVATE_DIR)/testcases
+ $(hide) cd $(dir $@) && zip -rq $(notdir $@) $(PRIVATE_NAME) -x@zip_exclude.lst
+
+.PHONY: afw-test-harness
+afw-test-harness: $(INTERNAL_AFW_TH_TARGET) adb
+ $(hide) echo "********************************************"
+ $(hide) echo "To start tradefed, run: afw-test-tradefed"
+ $(hide) echo "********************************************"
+$(call dist-for-goals,afw-test-harness,$(INTERNAL_AFW_TH_TARGET))
diff --git a/AfwTestHarnessBuildUtil.mk b/AfwTestHarnessBuildUtil.mk
new file mode 100644
index 0000000..3bb8629
--- /dev/null
+++ b/AfwTestHarnessBuildUtil.mk
@@ -0,0 +1,31 @@
+#
+# Copyright (C) 2016 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+# Functions to get the paths of the build outputs.
+define afw-th-get-package-paths
+ $(foreach pkg,$(1),$(AFW_TH_TESTCASES_OUT)/$(pkg).apk)
+endef
+
+define afw-th-get-test-configs
+ $(foreach name,$(1),$(AFW_TH_TESTCASES_OUT)/$(name).config)
+endef
+
+define afw-th-get-lib-paths
+ $(foreach lib,$(1),$(AFW_TH_TESTCASES_OUT)/$(lib).jar)
+endef
+
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..2201d13
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,21 @@
+#
+# Copyright (C) 2016 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.
+#
+
+include test/AfwTestHarness/build/config.mk
+include test/AfwTestHarness/AfwTestHarnessBuildUtil.mk
+include test/AfwTestHarness/AfwTestHarnessBuild.mk
+
+include $(call all-subdir-makefiles)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/README b/README
new file mode 100644
index 0000000..0ab5400
--- /dev/null
+++ b/README
@@ -0,0 +1,113 @@
+Android For Work (Afw) Test Harness
+-----------------------------------------------------
+About
+
+Afw Test Harness is designed to validate Android for Work compatibility of
+Android devices. It's a test suite consisting of support apps, test cases,
+test runner and configuration files. The test runner, afw-test-tradefed,
+is built on top of cts-tradefed. The way of building & running Afw Test
+Harness is quite similar to CTS.
+
+
+Host Machine Setup
+------------------
+1. Ensure 'adb' is in your current PATH. adb can be found in the
+Android SDK available from http://developer.android.com
+
+Example:
+ PATH=$PATH:/home/myuser/android-sdk-linux_x86/platform-tools
+
+2. Connect the device to the host machine.
+
+3. Ensure device is visible via 'adb devices'
+
+4. Make sure Java Runtime 1.7 or 1.8 is installed
+
+
+Afw Test Harness Configurations
+-------------------------------
+1. Get Testing Work account
+
+Testing work account is required to run the test harness. Testing work account
+can be obtained from https://android-for-work-test-harness.appspot.com/.
+Please reach out to the Google point of contact within your company to generate
+these accounts.
+Once obtained the testing account, specify it in the test harness configuration
+file, afw-test.props, with the following properties:
+
+work_account_username
+work_account_password
+
+2. Configure WIFI network
+
+Configure the Wi-Fi network in afw-test.props with the following properties:
+
+wifi_ssid
+wifi_password (optional)
+wifi_security_type (optional, available options are: NONE, WEP or WPA)
+
+
+Build Afw Test Harness
+----------------------
+Before building or running the harness, init the environment variables:
+
+ $ source build/envsetup.sh
+ $ lunch
+
+Select a proper device type and press Enter.
+
+To build Afw Test Harness, browse to this directory and then:
+
+ $ make afw-test-harness -j24
+
+Similar to CTS, "make afw-test-harness" will create a directory,
+
+ out/host/<platform>/afw-th/android-cts
+
+which contains all necessary binaries, configuration files and tools to run the
+test suite. This directory is also zipped into a file, android-afw-test-harness.zip,
+for distribution.
+
+
+Run Afw Test Harness
+----------------------------------------------
+1. From building environment, launch the test runner from the command line:
+
+ $ afw-test-tradefed
+
+2. From the unzipped folder of android-afw-test-harness.zip, launch the test
+ runner from the command line:
+
+ $ ./android‐cts/tools/afw-test‐tradefed
+
+ Make sure the ./android‐cts/testcases/afw-test.props has the
+ testing work account and WIFI configuration.
+
+3. Run the test plan "afw-userdebug-build": run afw-userdebug-build
+
+ Type 'list plans' to see all the test plans.
+
+ Plan definitions can be found in:
+ out/host/<platform>/afw-th/android-cts/tools/afw-test-tradefed.jar/config.
+
+ Each test plan is a xml file which contains all or several test packages from
+ AfwTestHarness/tests.
+
+ Plan 'afw-userdebug-build' contains all test packages that require a userdebug build.
+ Plan 'afw-user-build' can run on user build but requires the test device to be setup
+ properly, including completing Setup Wizard and enabling USB debugging.
+
+4. Run a single test module, for example "AfwTestNfcProvisioningTestCases"
+
+ cts-tf > run cts --module AfwTestNfcProvisioningTestCases
+
+ All modules can be found by executing "list modules" command in
+ afw-test-tradefed console.
+
+5. If the device supports both 64 bits & 32 bits abi, you can force the test
+ harness to run on a specific abi, e.g.:
+
+ cts-tf > run afw-userdebug-build --abi arm64-v8a -l DEBUG
+
+6.For more options:
+ cts-tf > run cts --help
diff --git a/afw-test.props b/afw-test.props
new file mode 100644
index 0000000..2e72d51
--- /dev/null
+++ b/afw-test.props
@@ -0,0 +1,17 @@
+device_admin_pkg_name=com.afwsamples.testdpc
+device_admin_receiver=com.afwsamples.testdpc.DeviceAdminReceiver
+device_admin_pkg_location=https://testdpc-latest-apk.appspot.com
+device_admin_pkg_signature_hash=gJD2YwtOiWJHkSMkkIfLRlj-quNqG1fb6v100QmzM9w=
+wifi_ssid=
+wifi_password=
+wifi_security_type=
+work_account_username=
+work_account_password=
+factory_reset_timeout_min=20
+timeout_size=M
+test_timeout_min=
+mute_app_crash_dialogs=true
+app_crash_whitelist=
+oem_widgets=
+leave_all_system_apps_enabled=
+admin_extras_bundle=
diff --git a/apps/Android.mk b/apps/Android.mk
new file mode 100644
index 0000000..6801acf
--- /dev/null
+++ b/apps/Android.mk
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2016 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.
+#
+
+include $(call all-subdir-makefiles)
+
diff --git a/apps/DeviceAdmin/Android.mk b/apps/DeviceAdmin/Android.mk
new file mode 100644
index 0000000..872af31
--- /dev/null
+++ b/apps/DeviceAdmin/Android.mk
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2016 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := AfwThCommonLib
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := AfwThDeviceAdmin
+
+LOCAL_SDK_VERSION := 22
+
+include $(BUILD_AFW_TEST_SUPPORT_PACKAGE)
diff --git a/apps/DeviceAdmin/AndroidManifest.xml b/apps/DeviceAdmin/AndroidManifest.xml
new file mode 100644
index 0000000..990cf22
--- /dev/null
+++ b/apps/DeviceAdmin/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.afwtest.deviceadmin" >
+
+ <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="23"/>
+
+ <application>
+ <receiver
+ android:name=".AdminReceiver"
+ android:permission="android.permission.BIND_DEVICE_ADMIN"
+ android:exported="true">
+ <meta-data android:name="android.app.device_admin"
+ android:resource="@xml/device_admin" />
+ <intent-filter>
+ <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+ </intent-filter>
+ </receiver>
+
+ <activity
+ android:name="com.android.afwtest.deviceadmin.FactoryResetActivity"
+ android:label="Factory Reset"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/apps/DeviceAdmin/README b/apps/DeviceAdmin/README
new file mode 100644
index 0000000..c618be6
--- /dev/null
+++ b/apps/DeviceAdmin/README
@@ -0,0 +1,13 @@
+- About
+
+ An admin app that helps factory resetting a device.
+
+- Build & Install:
+
+ make AfwThDeviceAdmin
+ adb install -d -r AfwThDeviceAdmin.apk
+
+- Factory reset a device
+
+ adb shell dpm set-active-admin com.android.afwtest.deviceadmin/com.android.afwtest.deviceadmin.AdminReceiver
+ adb shell am start -n com.android.afwtest.deviceadmin/.FactoryResetActivity
diff --git a/apps/DeviceAdmin/res/xml/device_admin.xml b/apps/DeviceAdmin/res/xml/device_admin.xml
new file mode 100644
index 0000000..4ecda10
--- /dev/null
+++ b/apps/DeviceAdmin/res/xml/device_admin.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="false">
+ <uses-policies>
+ <wipe-data />
+ </uses-policies>
+</device-admin>
diff --git a/apps/DeviceAdmin/src/com/android/afwtest/deviceadmin/AdminReceiver.java b/apps/DeviceAdmin/src/com/android/afwtest/deviceadmin/AdminReceiver.java
new file mode 100644
index 0000000..c0215bf
--- /dev/null
+++ b/apps/DeviceAdmin/src/com/android/afwtest/deviceadmin/AdminReceiver.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 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.afwtest.deviceadmin;
+
+import android.app.admin.DeviceAdminReceiver;
+
+/**
+ * Admin receiver.
+ */
+public class AdminReceiver extends DeviceAdminReceiver {
+}
diff --git a/apps/DeviceAdmin/src/com/android/afwtest/deviceadmin/FactoryResetActivity.java b/apps/DeviceAdmin/src/com/android/afwtest/deviceadmin/FactoryResetActivity.java
new file mode 100644
index 0000000..b5e56b8
--- /dev/null
+++ b/apps/DeviceAdmin/src/com/android/afwtest/deviceadmin/FactoryResetActivity.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 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.afwtest.deviceadmin;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+
+
+/**
+ * Activity to handle factory reset intent.
+ */
+public class FactoryResetActivity extends Activity {
+
+ private static final String TAG = "afwtest.FactoryResetActivity";
+
+ private static final String EXTRA_WIPE_PROTECTION_DATA = "afwtest.wipe.protection.data";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ ComponentName admin = new ComponentName(getApplicationContext().getPackageName(),
+ AdminReceiver.class.getName());
+
+ DevicePolicyManager devicePolicyManager =
+ (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ boolean wipeReset = getIntent().getBooleanExtra(EXTRA_WIPE_PROTECTION_DATA, false);
+
+ String adminName = admin.flattenToString();
+ if (!devicePolicyManager.isAdminActive(admin)) {
+ Log.e(TAG, adminName + " is not active admin");
+ Log.e(TAG, "Please run command: adb shell dpm set-active-admin " + adminName);
+ } else if (wipeReset && !devicePolicyManager.isDeviceOwnerApp(getPackageName())) {
+ Log.e(TAG, getPackageName() + " is not device-owner");
+ Log.e(TAG, "Please run command: adb shell dpm set-device-owner " + adminName);
+ } else {
+ Log.d(TAG, "Active admin: " + adminName);
+ if (wipeReset) {
+ devicePolicyManager.wipeData(DevicePolicyManager.WIPE_RESET_PROTECTION_DATA);
+ } else {
+ devicePolicyManager.wipeData(0);
+ }
+ }
+ finish();
+ }
+}
diff --git a/apps/SystemUtil/Android.mk b/apps/SystemUtil/Android.mk
new file mode 100644
index 0000000..6c14d18
--- /dev/null
+++ b/apps/SystemUtil/Android.mk
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2016 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := AfwThCommonLib
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := AfwThSystemUtil
+
+LOCAL_SDK_VERSION := 22
+
+include $(BUILD_AFW_TEST_SUPPORT_PACKAGE)
diff --git a/apps/SystemUtil/AndroidManifest.xml b/apps/SystemUtil/AndroidManifest.xml
new file mode 100644
index 0000000..e770ea4
--- /dev/null
+++ b/apps/SystemUtil/AndroidManifest.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.afwtest.systemutil" >
+
+ <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="23"/>
+
+ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"/>
+ <uses-permission android:name="android.permission.DISPATCH_NFC_MESSAGE" />
+ <uses-permission android:name="android.permission.DISPATCH_PROVISIONING_MESSAGE"/>
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+ <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
+
+ <instrumentation
+ android:name=".RemoveGoogleAccount"
+ android:label="Remove Google Account"
+ android:targetPackage="com.android.afwtest.systemutil" >
+ </instrumentation>
+
+ <instrumentation
+ android:name=".ChangeLocale"
+ android:label="Change the device locale"
+ android:targetPackage="com.android.afwtest.systemutil" >
+ </instrumentation>
+
+ <application android:label="Afw Test Harness System Util">
+ <!-- NFC Bump Sender activity -->
+ <activity
+ android:name="com.android.afwtest.systemutil.NfcBumpSender"
+ android:label="NFC Bump Sender"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.afwtest.systemutil.action.SEND_NFC_BUMP" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <!-- Start QR Code provisioning activity -->
+ <activity
+ android:name="com.android.afwtest.systemutil.StartQRCodeProvisioning"
+ android:label="QR code provisioning"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.afwtest.systemutil.action.START_QR_CODE_PROVISIONING" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ </application>
+</manifest>
diff --git a/apps/SystemUtil/README b/apps/SystemUtil/README
new file mode 100644
index 0000000..fc4e3a0
--- /dev/null
+++ b/apps/SystemUtil/README
@@ -0,0 +1,34 @@
+- About
+
+ System util app. It must be pushed to /system/priv-app on the device.
+ Requires a device where 'adb root' is possible, typically a userdebug build.
+
+- Build & Install:
+
+ make AfwThSystemUtil
+ adb root
+ adb remount
+ adb push AfwThSystemUtil.apk /system/priv-app
+ adb reboot
+
+- Usage
+
+ - Remove all Google accounts:
+ adb shell am instrument -w com.android.afwtest.systemutil/.RemoveGoogleAccount
+
+ - Remove a Google account:
+ adb shell am instrument \
+ -w -e account accountName com.android.afwtest.systemutil/.RemoveGoogleAccount
+
+ - Change system locale
+ adb shell am instrument \
+ -w -e locale locale_code com.android.afwtest.systemutil/.ChangeLocale
+
+ - locale_code can be language code only or languageCode_COUNTRYCODE, e.g. en, en_US, en_UK, zh_CN.
+
+ - Simulate a NFC bump
+ Configure the parameters you would like to be sent in the nfc bump by editing afw-test.props
+
+ adb push afw-test.props /data/local/tmp/afw-test.props
+ adb shell am start -n com.android.afwtest.systemutil/.NfcBumpSender
+ -e NFCBumpFile "/data/local/tmp/afw-test.props"
diff --git a/apps/SystemUtil/afw-test-system-util-permissions.xml b/apps/SystemUtil/afw-test-system-util-permissions.xml
new file mode 100644
index 0000000..48810f7
--- /dev/null
+++ b/apps/SystemUtil/afw-test-system-util-permissions.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<permissions>
+ <privapp-permissions package="com.android.afwtest.systemutil">
+ <permission name="android.permission.CHANGE_CONFIGURATION"/>
+ <permission name="android.permission.DISPATCH_NFC_MESSAGE"/>
+ <permission name="android.permission.DISPATCH_PROVISIONING_MESSAGE"/>
+ </privapp-permissions>
+</permissions>
diff --git a/apps/SystemUtil/src/com/android/afwtest/systemutil/ChangeLocale.java b/apps/SystemUtil/src/com/android/afwtest/systemutil/ChangeLocale.java
new file mode 100644
index 0000000..34c9da4
--- /dev/null
+++ b/apps/SystemUtil/src/com/android/afwtest/systemutil/ChangeLocale.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 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.afwtest.systemutil;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Locale;
+
+/**
+ * Change the system locale.
+ **/
+public class ChangeLocale extends Instrumentation {
+ private static final String TAG = "afwtest.ChangeLocale";
+
+ // Locale code, expecting something like: en, en_US, en_UK, zh, zh_CN, zh_TW.
+ private String mLocale;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle arguments) {
+ super.onCreate(arguments);
+ mLocale = arguments.getString("locale", "");
+ start();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ // Locale must be specified.
+ if (mLocale.isEmpty()) {
+ handleError("Locale is not specified");
+ return;
+ }
+
+ Log.i(TAG, String.format("Attempting to change locale to %s", mLocale));
+
+ try {
+ // It could be en_US or en.
+ final String[] codes = mLocale.split("_");
+ final Locale locale =
+ codes.length > 1 ? new Locale(codes[0], codes[1]) : new Locale(codes[0]);
+
+ // Change locale through reflection.
+ final Class amClass = Class.forName("android.app.ActivityManager");
+ final Method getService = amClass.getMethod("getService");
+ getService.setAccessible(true);
+ final Object am = getService.invoke(amClass);
+ final Class iamClass = am.getClass();
+
+ final Method getConfig = iamClass.getMethod("getConfiguration");
+ getConfig.setAccessible(true);
+ final Configuration config = (Configuration) getConfig.invoke(am);
+
+ final Field field = config.getClass().getField("userSetLocale");
+ field.setBoolean(config, true);
+
+ // Set new locale.
+ config.locale = locale;
+
+ // Update configuration.
+ final Method updateConfig =
+ iamClass.getMethod("updateConfiguration", Configuration.class);
+ updateConfig.setAccessible(true);
+ updateConfig.invoke(am, config);
+
+ // Success
+ final Bundle results = new Bundle();
+ results.putString("result", "SUCCESS");
+ finish(Activity.RESULT_OK, results);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to change locale", e);
+ handleError(String.format("Failed to change locale: %s", e));
+ }
+ }
+
+ /**
+ * Handles error by exiting instrumentation.
+ *
+ * @param errorMsg error msg
+ */
+ private void handleError(String errorMsg) {
+ Bundle results = new Bundle();
+ results.putString("error", errorMsg);
+ finish(Activity.RESULT_CANCELED, results);
+ }
+
+}
diff --git a/apps/SystemUtil/src/com/android/afwtest/systemutil/NfcBumpSender.java b/apps/SystemUtil/src/com/android/afwtest/systemutil/NfcBumpSender.java
new file mode 100644
index 0000000..5cda272
--- /dev/null
+++ b/apps/SystemUtil/src/com/android/afwtest/systemutil/NfcBumpSender.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 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.afwtest.systemutil;
+
+import static com.android.afwtest.common.Constants.NFC_BUMP_FILE;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.afwtest.common.nfcprovisioning.NfcBumpSimulator;
+
+import java.io.IOException;
+
+/**
+ * Activity to send a nfc bump.
+ */
+public class NfcBumpSender extends Activity {
+
+ private static final String TAG = "afwtest.NfcBumpSender";
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ try {
+ Intent intent = getIntent();
+ // Gets the path of the file that containing the parameters to be sent
+ // in the nfc bump.
+ if (intent.hasExtra(NFC_BUMP_FILE)) {
+ String bumpFileLocation = getIntent().getStringExtra(NFC_BUMP_FILE);
+ NfcBumpSimulator.sendNfcBump(this, bumpFileLocation);
+ Log.d(TAG, String.format("%s=%s", NFC_BUMP_FILE, bumpFileLocation));
+ } else {
+ Log.e(TAG, String.format("Nfc bump not sent."
+ + "Intent doesn't contain extra %s.", NFC_BUMP_FILE));
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to send Nfc Bump", e);
+ }
+ finish();
+ }
+}
diff --git a/apps/SystemUtil/src/com/android/afwtest/systemutil/RemoveGoogleAccount.java b/apps/SystemUtil/src/com/android/afwtest/systemutil/RemoveGoogleAccount.java
new file mode 100644
index 0000000..dc57c1d
--- /dev/null
+++ b/apps/SystemUtil/src/com/android/afwtest/systemutil/RemoveGoogleAccount.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 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.afwtest.systemutil;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.concurrent.Semaphore;
+
+/**
+ * An instrumentation utility to remove Google account from device.
+ */
+public class RemoveGoogleAccount extends Instrumentation {
+
+ private static final String TAG = "afwtest.RemoveGoogleAccount";
+
+ // Account to remove; remove all Google accounts if null.
+ private String mAccount;
+
+ @Override
+ public void onCreate(Bundle arguments) {
+ super.onCreate(arguments);
+ mAccount = arguments.getString("account", null);
+ start();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ Log.d(TAG, "Attempting to remove Google accounts");
+ Bundle results = new Bundle();
+
+ AccountManager accountManager = AccountManager.get(getContext());
+ Account[] accounts = accountManager.getAccountsByType("com.google");
+ for (Account account : accounts) {
+ if (mAccount == null || account.name.equals(mAccount)) {
+ Log.i(TAG, String.format("Removing account %s", account.name));
+ RemoveCallback callback = new RemoveCallback();
+ accountManager.removeAccount(account, callback, null /* handler */);
+ if (!callback.waitForRemoveCompletion()) {
+ String error = String.format("Failed to remove account %s: Reason: %s",
+ account.name, callback.getErrorMessage());
+ results.putString("error", error);
+ finish(Activity.RESULT_CANCELED, results);
+ return;
+ }
+ }
+ }
+ results.putString("result", "SUCCESS");
+ finish(Activity.RESULT_OK, results);
+ }
+
+ /**
+ * Callback which will block until account removal result is available or exception throws.
+ */
+ private static class RemoveCallback implements AccountManagerCallback<Boolean> {
+
+ private static final String TAG = "afwtest.RemoveCallback";
+
+ /**
+ * Stores the result of account removal.
+ */
+ private boolean mResult = false;
+
+ /**
+ * Error message if any.
+ */
+ private String mErrorMessage = null;
+
+ /**
+ * {@link Semaphore}, indicating result is available if it's value > 0.
+ */
+ private Semaphore mLock = new Semaphore(0);
+
+ /**
+ * Block and wait for the remove callback to complete.
+ *
+ * @return the {@link Bundle} result from the remove op.
+ */
+
+ public Boolean waitForRemoveCompletion() {
+ try {
+ // Blocks until semaphore count > 0
+ mLock.acquire();
+ } catch (InterruptedException e) {
+ handleException(e);
+ }
+ return mResult;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void run(AccountManagerFuture<Boolean> future) {
+ try {
+ mResult = future.getResult();
+ } catch (OperationCanceledException | IOException | AuthenticatorException e) {
+ handleException(e);
+ } finally {
+ // Increment semaphore count by 1.
+ mLock.release();
+ }
+ }
+
+ /**
+ * Gets the error message.
+ *
+ * @return error message
+ */
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /**
+ * Creates a result bundle for given exception
+ */
+ private void handleException(Exception e) {
+ Log.e(TAG, "Failed to remove account", e);
+ mResult = false;
+ mErrorMessage = e.toString();
+ }
+ }
+}
diff --git a/apps/SystemUtil/src/com/android/afwtest/systemutil/StartQRCodeProvisioning.java b/apps/SystemUtil/src/com/android/afwtest/systemutil/StartQRCodeProvisioning.java
new file mode 100644
index 0000000..e7f04aa
--- /dev/null
+++ b/apps/SystemUtil/src/com/android/afwtest/systemutil/StartQRCodeProvisioning.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 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.afwtest.systemutil;
+
+import static com.android.afwtest.common.Constants.QR_CODE_FILE;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.afwtest.common.qrcodeprovisioning.QRCodeSimulator;
+
+import java.io.IOException;
+
+/**
+ * Activity to start QR code provisioning.
+ */
+public class StartQRCodeProvisioning extends Activity {
+
+ private static final String TAG = "afwtest.StartQRCodeProvisioning";
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ try {
+ Intent intent = getIntent();
+ if (intent.hasExtra(QR_CODE_FILE)) {
+ QRCodeSimulator.sendQRCode(this, intent.getStringExtra(QR_CODE_FILE));
+ } else {
+ Log.e(TAG, String.format(
+ "QR code provisioning not started. Intent doesn't contain extra %s.",
+ QR_CODE_FILE));
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to start QR code provisioning", e);
+ }
+ finish();
+ }
+}
diff --git a/apps/Util/Android.mk b/apps/Util/Android.mk
new file mode 100644
index 0000000..89742c4
--- /dev/null
+++ b/apps/Util/Android.mk
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2016 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := AfwThCommonLib
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := AfwThUtil
+
+LOCAL_SDK_VERSION := 22
+
+include $(BUILD_AFW_TEST_SUPPORT_PACKAGE)
diff --git a/apps/Util/AndroidManifest.xml b/apps/Util/AndroidManifest.xml
new file mode 100644
index 0000000..f270fcc
--- /dev/null
+++ b/apps/Util/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.afwtest.util" >
+
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+
+ <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="22" />
+
+ <instrumentation android:name=".Wifi"
+ android:targetPackage="com.android.afwtest.util"
+ android:label="Afw Test Harness Utility App">
+ </instrumentation>
+
+ <application android:label="Afw Test Harness Util">
+ </application>
+</manifest>
diff --git a/apps/Util/README.txt b/apps/Util/README.txt
new file mode 100644
index 0000000..8d17880
--- /dev/null
+++ b/apps/Util/README.txt
@@ -0,0 +1,17 @@
+- About
+
+ Common util app that works on user builds.
+
+- Usage: connect a wifi
+
+ adb shell am instrument -w -e action connect \
+ -e ssid <ssid> -e security_type <security_type> -e password <password> \
+ com.android.afwtest.util/.Wifi
+
+ security_type could be WPA, WEP or NONE. Default to be NONE if no password is specified; default
+ to be WPA if password is given.
+
+- Usage: disconnect from wifi
+
+ adb shell am instrument -w -e action disconnect com.android.afwtest.util/.Wifi
+
diff --git a/apps/Util/src/com/android/afwtest/util/NetworkMonitor.java b/apps/Util/src/com/android/afwtest/util/NetworkMonitor.java
new file mode 100644
index 0000000..b432549
--- /dev/null
+++ b/apps/Util/src/com/android/afwtest/util/NetworkMonitor.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016 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.afwtest.util;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.util.Log;
+
+import com.android.afwtest.common.NetworkUtils;
+
+/**
+ * Monitor the state of the data network. Invoke a callback when the network is connected
+ */
+public class NetworkMonitor {
+ private static final String TAG = "afwtest.NetworkMonitor";
+
+ /** State notification callback. Expect some duplicate notifications. */
+ public interface Callback {
+ /** Notify on network connected. */
+ void onNetworkConnected();
+ /** Notify on network disconnected. */
+ void onNetworkDisconnected();
+ }
+
+ /** Application context. */
+ private final Context mContext;
+
+ /** Registered callback that is listening to network events. */
+ private final Callback mCallback;
+
+ /** Whether receiver is registered. */
+ private boolean mReceiverRegistered;
+
+ /**
+ * Start watching the network and monitoring the checkin service. Immediately invokes one of the
+ * callback methods to report the current state, and then invokes callback methods over time as
+ * the state changes.
+ *
+ * @param context to use for intent observers and such
+ * @param callback to invoke when the network status changes
+ */
+ public NetworkMonitor(Context context, Callback callback) {
+ mContext = context;
+ mCallback = callback;
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+
+ context.registerReceiver(mBroadcastReceiver, filter);
+
+ mReceiverRegistered = true;
+ }
+
+ /**
+ * Stop watching the network and checkin service.
+ */
+ public synchronized void close() {
+ if (mReceiverRegistered) {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ mReceiverRegistered = false;
+ }
+ }
+
+ /**
+ * Broadcast receiver.
+ */
+ public final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(TAG, String.format("onReceive: %s", intent.toString()));
+
+ if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ if (NetworkUtils.isConnectedToWifi(context)) {
+ mCallback.onNetworkConnected();
+ } else {
+ mCallback.onNetworkDisconnected();
+ }
+ }
+ }
+ };
+}
diff --git a/apps/Util/src/com/android/afwtest/util/Wifi.java b/apps/Util/src/com/android/afwtest/util/Wifi.java
new file mode 100644
index 0000000..e55ab40
--- /dev/null
+++ b/apps/Util/src/com/android/afwtest/util/Wifi.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2016 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.afwtest.util;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.afwtest.common.NetworkUtils;
+
+/**
+ * Adds a wifi network to system.
+ */
+public class Wifi extends Instrumentation {
+
+ private static final String TAG = "afwtest.Wifi";
+
+ private static final String ACTION_CONNECT = "connect";
+ private static final String ACTION_DISCONNECT = "disconnect";
+
+ /** Supported actions: connect, disconnect*/
+ private String mAction;
+
+ private String mSSID, mSecurityType, mPassword;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle arguments) {
+ super.onCreate(arguments);
+ mAction = arguments.getString("action", "");
+ mSSID = arguments.getString("ssid", "");
+ if (!mSSID.startsWith("\"") || !mSSID.endsWith("\"")) {
+ mSSID = String.format("\"%s\"", mSSID);
+ }
+ mSecurityType = arguments.getString("security_type", "");
+ mPassword = arguments.getString("password", "");
+ start();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onStart() {
+ // Handle connect action
+ if (ACTION_CONNECT.equals(mAction)) {
+ handleConnect();
+ } else if (ACTION_DISCONNECT.equals(mAction)) {
+ handleDisconnect();
+ } else {
+ handleError(String.format("Invalid action: %s", mAction));
+ }
+ }
+
+ /**
+ * Handles connecting wifi request.
+ */
+ private void handleConnect() {
+ if (!NetworkUtils.enableWifi(getContext())) {
+ handleError("Failed to enable wifi.");
+ }
+
+ // If the expected wifi is already connected, return.
+ if (NetworkUtils.isConnectedToSpecifiedWifi(getContext(), mSSID)) {
+ Log.i(TAG, String.format("Wifi already connected: %s (%s)", mSSID, mSecurityType));
+ handleSuccess();
+ } else {
+ WifiConnector wifiConnector = new WifiConnector(getContext(),
+ mSSID,
+ mSecurityType,
+ mPassword);
+ if (wifiConnector.connect()) {
+ Log.i(TAG, String.format("Connected to wifi: %s (%s)", mSSID, mSecurityType));
+ handleSuccess();
+ } else {
+ handleError(String.
+ format("Failed to connect to wifi: %s (%s)", mSSID, mSecurityType));
+ }
+ }
+ }
+
+ /**
+ * Handles wifi disconnecting request.
+ *
+ * Handled by removing all configured networks.
+ */
+ private void handleDisconnect() {
+ if (!NetworkUtils.disconnectFromWifi(getContext())) {
+ handleError("Failed to disconnect from Wifi");
+ } else {
+ Log.i(TAG, "Wifi disconnected.");
+ handleSuccess();
+ }
+ }
+
+ /**
+ * Handles error by exiting instrumentation.
+ *
+ * @param errorMsg error msg
+ */
+ private void handleError(String errorMsg) {
+ Log.i(TAG, errorMsg);
+ Bundle results = new Bundle();
+ results.putString("error", errorMsg);
+ finish(Activity.RESULT_CANCELED, results);
+ }
+
+ /**
+ * Handles success execution
+ */
+ private void handleSuccess() {
+ Bundle results = new Bundle();
+ results.putString("result", "SUCCESS");
+ finish(Activity.RESULT_OK, results);
+ }
+} \ No newline at end of file
diff --git a/apps/Util/src/com/android/afwtest/util/WifiConfig.java b/apps/Util/src/com/android/afwtest/util/WifiConfig.java
new file mode 100644
index 0000000..f49d710
--- /dev/null
+++ b/apps/Util/src/com/android/afwtest/util/WifiConfig.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2016 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.afwtest.util;
+
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Locale;
+
+/**
+ * Utility class for configuring a new WiFi network.
+ */
+public final class WifiConfig {
+
+ private static final String TAG = "afwtest.WifiConfig";
+
+ /** Security type enum. */
+ enum SecurityType {
+ NONE,
+ WPA,
+ WEP
+ }
+
+ /** {@link WifiManager} instance. */
+ private final WifiManager mWifiManager;
+
+ /**
+ * Constructor.
+ *
+ * @param manager {@link WifiManager} object
+ */
+ public WifiConfig(WifiManager manager) {
+ mWifiManager = manager;
+ }
+
+ /**
+ * Adds a new WiFi network.
+ *
+ * @param ssid Wifi SSID
+ * @param type Wifi security type
+ * @param password Wifi password
+ * @return the ID of the newly created network description. Returns -1 on failure.
+ */
+ public int addNetwork(String ssid, String type, String password) {
+ WifiConfiguration wifiConf = new WifiConfiguration();
+ SecurityType securityType;
+ if (type == null || TextUtils.isEmpty(type)) {
+ securityType = SecurityType.NONE;
+ } else {
+ try {
+ securityType = Enum.valueOf(SecurityType.class, type.toUpperCase(Locale.US));
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, String.format("Invalid Wifi security type: %s", type));
+ return -1;
+ }
+ }
+ // If we have a password, and no security type, assume WPA.
+ if (securityType.equals(SecurityType.NONE) && !TextUtils.isEmpty(password)) {
+ securityType = SecurityType.WPA;
+ }
+
+ wifiConf.SSID = ssid;
+ wifiConf.status = WifiConfiguration.Status.ENABLED;
+ switch (securityType) {
+ case NONE:
+ wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+ wifiConf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
+ break;
+ case WPA:
+ updateForWPAConfiguration(wifiConf, password);
+ break;
+ case WEP:
+ updateForWEPConfiguration(wifiConf, password);
+ break;
+ }
+
+ int netId = mWifiManager.addNetwork(wifiConf);
+
+ if (netId != -1) {
+ // Setting disableOthers to 'true' should trigger a connection attempt.
+ mWifiManager.enableNetwork(netId, true);
+ mWifiManager.saveConfiguration();
+ }
+
+ return netId;
+ }
+
+ /**
+ * Updates WAP Wifi configuration.
+ *
+ * @param wifiConf {@link WifiConfiguration} object to update
+ * @param password Wifi password
+ */
+ protected void updateForWPAConfiguration(WifiConfiguration wifiConf, String password) {
+ wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+ wifiConf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
+ wifiConf.allowedProtocols.set(WifiConfiguration.Protocol.WPA); // For WPA
+ wifiConf.allowedProtocols.set(WifiConfiguration.Protocol.RSN); // For WPA2
+ wifiConf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
+ wifiConf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+ wifiConf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
+ wifiConf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
+ if (!TextUtils.isEmpty(password)) {
+ wifiConf.preSharedKey = "\"" + password + "\"";
+ }
+ }
+
+ /**
+ * Updates WEP Wifi configuration.
+ *
+ * @param wifiConf {@link WifiConfiguration} object to update
+ * @param password Wifi password
+ */
+ protected void updateForWEPConfiguration(WifiConfiguration wifiConf, String password) {
+ wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+ wifiConf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
+ wifiConf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
+ wifiConf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
+ wifiConf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
+ wifiConf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
+ wifiConf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
+ int length = password.length();
+ if ((length == 10 || length == 26 || length == 58) && password.matches("[0-9A-Fa-f]*")) {
+ wifiConf.wepKeys[0] = password;
+ } else {
+ wifiConf.wepKeys[0] = '"' + password + '"';
+ }
+ wifiConf.wepTxKeyIndex = 0;
+ }
+} \ No newline at end of file
diff --git a/apps/Util/src/com/android/afwtest/util/WifiConnector.java b/apps/Util/src/com/android/afwtest/util/WifiConnector.java
new file mode 100644
index 0000000..f10ab2d
--- /dev/null
+++ b/apps/Util/src/com/android/afwtest/util/WifiConnector.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2016 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.afwtest.util;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.afwtest.common.NetworkUtils;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Utility class responsible for connecting wifi.
+ */
+public final class WifiConnector implements NetworkMonitor.Callback {
+
+ private static final String TAG = "afwtest.WifiConnector";
+
+ /** Connection retry base duration. */
+ private static final int RETRY_SLEEP_DURATION_BASE_MS = 500;
+ /** Connection retry duration multiplier. */
+ private static final int RETRY_SLEEP_MULTIPLIER = 2;
+ /** Max connection attempts. */
+ private static final int MAX_ATTEMPS = 6;
+ /** Wifi reconnection timeout. */
+ private static final int RECONNECT_TIMEOUT_MS = (int)TimeUnit.MINUTES.toMillis(1);
+
+ private final Context mContext;
+ private final String mSSID, mSecurityType, mPassword;
+ private final WifiManager mWifiManager;
+ private final WifiConfig mWifiConfig;
+
+ /**
+ * {@link Semaphore}, indicating result is available if it's value > 0.
+ */
+ private Semaphore mLock = new Semaphore(0);
+
+ /**
+ * Constructor.
+ *
+ * @param context {@link Context} object
+ * @param ssid Wifi SSID
+ * @param securityType Wifi security type
+ * @param password Wifi password
+ */
+ public WifiConnector(Context context, String ssid, String securityType, String password) {
+ mContext = context;
+ mSSID = ssid;
+ mSecurityType = securityType;
+ mPassword = password;
+ mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ mWifiConfig = new WifiConfig(mWifiManager);
+ }
+
+ /**
+ * Connects to WIFI.
+ *
+ * @return {@code true} if the given WIFI is connected; {@code false} othwerise
+ */
+ public boolean connect() {
+ if (!NetworkUtils.enableWifi(mContext)) {
+ Log.e(TAG, "Failed to enable WIFI.");
+ return false;
+ }
+
+ NetworkMonitor networkMonitor = new NetworkMonitor(mContext, this);
+
+ try {
+ int netId = -1;
+
+ int nextSleepTimeMs = RETRY_SLEEP_DURATION_BASE_MS;
+ int attempts = MAX_ATTEMPS;
+ while (attempts > 0) {
+ if (netId == -1) {
+ netId = mWifiConfig.addNetwork(mSSID, mSecurityType, mPassword);
+ }
+
+ if (netId == -1) {
+ Log.e(TAG, "Failed to save network.");
+ } else if (!mWifiManager.reconnect()) {
+ Log.e(TAG, "Unable to connect to wifi");
+ } else {
+ // Connected
+ break;
+ }
+
+ --attempts;
+ // Sleep before next attempt
+ Log.e(TAG, String.format("Retrying in %s ms.", nextSleepTimeMs));
+ SystemClock.sleep(nextSleepTimeMs);
+ // Increase the next sleep time.
+ nextSleepTimeMs *= RETRY_SLEEP_MULTIPLIER;
+ }
+
+ // Failed to add network or reconnect.
+ if (netId == -1 || attempts <= 0) {
+ return false;
+ }
+
+ // Wait for connection event
+ mLock.tryAcquire(RECONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ return true;
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Failed to connect to wifi", e);
+ } finally {
+ networkMonitor.close();
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onNetworkConnected() {
+ if (NetworkUtils.isConnectedToSpecifiedWifi(mContext, mSSID)) {
+ // Let's the waiter know it's connected.
+ mLock.release();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onNetworkDisconnected() {
+ }
+} \ No newline at end of file
diff --git a/build/config.mk b/build/config.mk
new file mode 100644
index 0000000..3260950
--- /dev/null
+++ b/build/config.mk
@@ -0,0 +1,27 @@
+#
+# Copyright (C) 2016 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.
+#
+
+AFW_TH_TESTCASES_OUT := $(HOST_OUT)/afw-th/android-cts/testcases
+
+# default module config filename
+AFW_TEST_MODULE_TEST_CONFIG := AndroidTest.xml
+
+# customized test package build rule
+BUILD_AFW_TEST_PACKAGE := test/AfwTestHarness/build/test_package.mk
+BUILD_AFW_TEST_SUPPORT_PACKAGE := test/AfwTestHarness/build/support_package.mk
+BUILD_AFW_TEST_MODULE_TEST_CONFIG := test/AfwTestHarness/build/module_test_config.mk
+BUILD_AFW_TEST_HOST_JAVA_LIBRARY := test/AfwTestHarness/build/test_host_java_library.mk
+
diff --git a/build/module_test_config.mk b/build/module_test_config.mk
new file mode 100644
index 0000000..1ef889f
--- /dev/null
+++ b/build/module_test_config.mk
@@ -0,0 +1,24 @@
+#
+# Copyright (C) 2016 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.
+#
+
+ifneq ($(LOCAL_AFW_TEST_MODULE_CONFIG),)
+afw_test_module_test_config := $(AFW_TH_TESTCASES_OUT)/$(LOCAL_MODULE).config
+$(afw_test_module_test_config): $(LOCAL_AFW_TEST_MODULE_CONFIG) | $(ACP)
+ $(call copy-file-to-target)
+endif
+# clear var
+LOCAL_AFW_TEST_MODULE_CONFIG :=
+
diff --git a/build/support_package.mk b/build/support_package.mk
new file mode 100644
index 0000000..666a1da
--- /dev/null
+++ b/build/support_package.mk
@@ -0,0 +1,35 @@
+#
+# Copyright (C) 2016 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.
+#
+
+#
+# Builds a package which is needed by a test package
+#
+# Replace "include $(BUILD_PACKAGE)" with "include $(BUILD_AFW_TEST_SUPPORT_PACKAGE)"
+#
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
+
+afw_test_support_apks :=
+$(foreach fp, $(ALL_MODULES.$(LOCAL_PACKAGE_NAME).BUILT_INSTALLED),\
+ $(eval pair := $(subst :,$(space),$(fp)))\
+ $(eval built := $(word 1,$(pair)))\
+ $(eval installed := $(AFW_TH_TESTCASES_OUT)/$(notdir $(word 2,$(pair))))\
+ $(eval $(call copy-one-file, $(built), $(installed)))\
+ $(eval afw_test_support_apks += $(installed)))
+
+$(my_register_name) : $(afw_test_support_apks)
diff --git a/build/test_host_java_library.mk b/build/test_host_java_library.mk
new file mode 100644
index 0000000..2964116
--- /dev/null
+++ b/build/test_host_java_library.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2016 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.
+
+#
+# Builds a host library and defines a rule to generate the associated test
+# package XML needed by CTS.
+#
+# Replace "include $(BUILD_HOST_JAVA_LIBRARY)" with "include $(BUILD_AFW_TEST_HOST_JAVA_LIBRARY)"
+#
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+include $(BUILD_AFW_TEST_MODULE_TEST_CONFIG)
+
+afw_test_library_jar := $(AFW_TH_TESTCASES_OUT)/$(LOCAL_MODULE).jar
+$(afw_test_library_jar): $(LOCAL_BUILT_MODULE)
+ $(copy-file-to-target)
+
+# Have the module name depend on the cts files; so the cts files get generated when you run mm/mmm/mma/mmma.
+$(my_register_name) : $(afw_test_library_jar) $(afw_test_module_test_config)
diff --git a/build/test_package.mk b/build/test_package.mk
new file mode 100644
index 0000000..816f065
--- /dev/null
+++ b/build/test_package.mk
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2016 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.
+#
+#
+# Builds a package and defines a rule to generate the associated test
+# package XML needed by the tradefed.
+#
+# Replace "include $(BUILD_PACKAGE)" with "include $(BUILD_AFW_TEST_PACKAGE)"
+#
+
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_AFW_TEST_SUPPORT_PACKAGE)
+include $(BUILD_AFW_TEST_MODULE_TEST_CONFIG)
+
+$(my_register_name) : $(afw_test_module_test_config)
diff --git a/libs/Android.mk b/libs/Android.mk
new file mode 100644
index 0000000..6801acf
--- /dev/null
+++ b/libs/Android.mk
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2016 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.
+#
+
+include $(call all-subdir-makefiles)
+
diff --git a/libs/CommonLib/Android.mk b/libs/CommonLib/Android.mk
new file mode 100644
index 0000000..b6a2e3b
--- /dev/null
+++ b/libs/CommonLib/Android.mk
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2016 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := AfwThCommonLib
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/libs/CommonLib/src/com/android/afwtest/common/AccountManagerUtils.java b/libs/CommonLib/src/com/android/afwtest/common/AccountManagerUtils.java
new file mode 100644
index 0000000..8384a6c
--- /dev/null
+++ b/libs/CommonLib/src/com/android/afwtest/common/AccountManagerUtils.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2016 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.afwtest.common;
+
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import java.io.IOException;
+
+/**
+ * Help class to wrap AccountManager ralated functionalities.
+ */
+public final class AccountManagerUtils {
+
+ private static final String TAG = "afwtest.AccountManagerUtils";
+
+ private static final String GOOGLE_ACCOUNT_TYPE = "com.google";
+ private static final String EXTRA_SETUP_WIZARD = "setupWizard";
+
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private AccountManagerUtils() {
+ }
+
+ /**
+ * Starts Add Account activity by firing a proper intent.
+ *
+ * @param context {@link Context} object
+ * @param isSetupWizard {@code true} if simulating setup wizard,
+ * {@code false} if simulating Settings->Add Account
+ */
+ public static void startAddGoogleAccountActivity(Context context, boolean isSetupWizard)
+ throws IOException, AuthenticatorException, OperationCanceledException {
+ final AccountManager accountManager = AccountManager.get(context);
+
+ // Options for the Add Account activity.
+ Bundle options = new Bundle();
+ options.putBoolean(EXTRA_SETUP_WIZARD, isSetupWizard);
+ if (isSetupWizard) {
+ // Skip "Got another device?" page
+ options.putBoolean("suppress_device_to_device_setup", true);
+ }
+
+ AccountManagerFuture<Bundle> amf = accountManager.addAccount(
+ GOOGLE_ACCOUNT_TYPE,
+ null, /* authTokenType*/
+ null, /* requiredFeatures */
+ options,
+ null, /* Activity context, null to not start the intent automatically */
+ null, /* callback */
+ null /* handler */
+ );
+
+ // Fire the intent to start the UI.
+ Intent intent = (Intent) amf.getResult().get(AccountManager.KEY_INTENT);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ }
+}
diff --git a/libs/CommonLib/src/com/android/afwtest/common/Constants.java b/libs/CommonLib/src/com/android/afwtest/common/Constants.java
new file mode 100644
index 0000000..580b703
--- /dev/null
+++ b/libs/CommonLib/src/com/android/afwtest/common/Constants.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2016 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.afwtest.common;
+
+/***
+ * Common constants.
+ */
+public final class Constants {
+
+ /**
+ * Property key of device admin package name, used in the test configuration file.
+ */
+ public static final String KEY_DEVICE_ADMIN_PKG_NAME = "device_admin_pkg_name";
+
+ /**
+ * Property key of device admin receiver, used in the test configuration file.
+ */
+ public static final String KEY_DEVICE_ADMIN_RECEIVER = "device_admin_receiver";
+
+ /**
+ * Property key of device admin package location, used in the test configuration file.
+ */
+ public static final String KEY_DEVICE_ADMIN_PKG_LOCATION = "device_admin_pkg_location";
+
+ /**
+ * Property key of device admin package checksum, used in the test configuration file.
+ */
+ public static final String KEY_DEVICE_ADMIN_PKG_CHECKSUM = "device_admin_pkg_checksum";
+
+ /**
+ * Property key of device admin package signature hash, used in the test configuration file.
+ */
+ public static final String KEY_DEVICE_ADMIN_PKG_SIGNATURE_HASH
+ = "device_admin_pkg_signature_hash";
+
+ /**
+ * Property key of timeout size, used in the test configuration file.
+ */
+ public static final String KEY_TIMEOUT_SIZE = "timeout_size";
+
+ /**
+ * Property key of test timeout in minute, used in the test configuration file.
+ */
+ public static final String KEY_TEST_TIMEOUT_MIN = "test_timeout_min";
+
+ /**
+ * Property key of wifi ssid, used in the test configuration file.
+ */
+ public static final String KEY_WIFI_SSID = "wifi_ssid";
+
+ /**
+ * Property key of the wifi password, used in the test configuration file.
+ */
+ public static final String KEY_WIFI_PWD = "wifi_password";
+
+ /**
+ * Property key of the wifi security type, used in the test configuration file.
+ */
+ public static final String KEY_WIFI_SECURITY_TYPE = "wifi_security_type";
+
+ /**
+ * Property key of the username of the work account, used in the test configuration file.
+ */
+ public static final String KEY_WORK_ACCOUNT_USERNAME = "work_account_username";
+
+ /**
+ * Property key of the password of the work account, used in the test configuration file.
+ */
+ public static final String KEY_WORK_ACCOUNT_PASSWORD = "work_account_password";
+
+ /**
+ * Property key for the list of OEM customized widget Ids.
+ *
+ * <p>The properties of each OEM widget are specified by separate keys in the format of
+ * widget_id.property. "property" can be any of {@link #KEY_OEM_WIDGET_TEXT},
+ * {@link #KEY_OEM_WIDGET_DESCRIPTION}, {@link #KEY_OEM_WIDGET_RESOURCE_ID},
+ * {@link #KEY_OEM_WIDGET_PACKAGE}, {@link #KEY_OEM_WIDGET_CLASS} or
+ * {@link #KEY_OEM_WIDGET_ACTION}.</p>
+ *
+ * <p>For example, OEM can define a widget like:
+ * <ul>
+ * <li>oem_widgets=custom_widget</li>
+ * <li>custom_widget.text=Get started</li>
+ * <li>custom_widget.class=android.widget.Button</li>
+ * <li>custom_widget.action=click</li>
+ * </ul>
+ * This tells the test to find a widget with text "Get started" and with class name
+ * "android.widget.Button" to click.</p>
+ */
+ public static final String KEY_OEM_WIDGETS = "oem_widgets";
+
+ /**
+ * Property key for the text of an OEM widget.
+ */
+ public static final String KEY_OEM_WIDGET_TEXT = "text";
+
+ /**
+ * Property key for the content description of an OEM widget.
+ */
+ public static final String KEY_OEM_WIDGET_DESCRIPTION = "description";
+
+ /**
+ * Property key for the resource ID of an OEM widget.
+ */
+ public static final String KEY_OEM_WIDGET_RESOURCE_ID = "resource_id";
+
+ /**
+ * Property key for the package name of an OEM widget.
+ */
+ public static final String KEY_OEM_WIDGET_PACKAGE = "package";
+
+ /**
+ * Property key for the class name of an OEM widget.
+ */
+ public static final String KEY_OEM_WIDGET_CLASS = "class";
+
+ /**
+ * Property key for the action on an OEM widget. It can any of {@link #ACTION_CLICK},
+ * {@link #ACTION_CHECK} or {@link #ACTION_SCROLL}.
+ */
+ public static final String KEY_OEM_WIDGET_ACTION = "action";
+
+ /**
+ * Property key for the scrolling direction of an OEM widget. It can be any of {@code UP},
+ * {@code DOWN}, {@code LEFT} or {@code RIGHT}.
+ */
+ public static final String KEY_OEM_WIDGET_SCROLL_DIRECTION = "scroll_direction";
+
+ /**
+ * Property key for the app crash dialog auto close strategy: whether to auto close
+ * all non-fatal crashes.
+ */
+ public static final String KEY_MUTE_APP_CRASH_DIALOGS = "mute_app_crash_dialogs";
+
+ /**
+ * Property key for app names whose app crash dialog should be auto closed, separated by ",".
+ */
+ public static final String KEY_APP_CRASH_WHITELIST = "app_crash_whitelist";
+
+ /**
+ * Click action on a widget.
+ */
+ public static final String ACTION_CLICK = "click";
+
+ /**
+ * Scroll action on a widget.
+ */
+ public static final String ACTION_SCROLL = "scroll";
+
+ /**
+ * Check action on a widget, either a checkbox or radio button.
+ */
+ public static final String ACTION_CHECK = "check";
+
+ /**
+ * Constant string for the user to specify the path of the file
+ * that contains NFC provisioning configurations.
+ */
+ public static final String NFC_BUMP_FILE = "NFCBumpFile";
+
+ /**
+ * Package name of system util app.
+ */
+ public static final String SYSTEM_UTIL_PKG_NAME = "com.android.afwtest.systemutil";
+
+ /**
+ * Send NFC bump action.
+ */
+ public static final String ACTION_SEND_NFC_BUMP
+ = "com.android.afwtest.systemutil.action.SEND_NFC_BUMP";
+
+ /**
+ * Start QR code provisioning action.
+ */
+ public static final String ACTION_START_QR_CODE_PROVISIONING
+ = "com.android.afwtest.systemutil.action.START_QR_CODE_PROVISIONING";
+
+
+ /**
+ * Constant string for the user to specify the path of the file
+ * that contains QR code provisioning configurations.
+ */
+ public static final String QR_CODE_FILE = "QRCodeFile";
+
+ /**
+ * Property key for leaving system apps enabled during provisioning.
+ */
+ public static final String LEAVE_ALL_SYSTEM_APPS_ENABLED = "leave_all_system_apps_enabled";
+
+ /**
+ * Property key for the admin extras bundle used during provisioning.
+ */
+ public static final String ADMIN_EXTRAS_BUNDLE = "admin_extras_bundle";
+
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private Constants() {
+ }
+}
diff --git a/libs/CommonLib/src/com/android/afwtest/common/FileUtils.java b/libs/CommonLib/src/com/android/afwtest/common/FileUtils.java
new file mode 100644
index 0000000..dcf962f
--- /dev/null
+++ b/libs/CommonLib/src/com/android/afwtest/common/FileUtils.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 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.afwtest.common;
+
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Properties;
+
+/**
+ * File Utils.
+ */
+public final class FileUtils {
+
+ private static final String TAG = "afwtest.FileUtils";
+
+ /**
+ * Reads {@link Properties} object from file.
+ *
+ * @param path File path
+ * @return Constructed {@link Properties} object
+ */
+ public static Properties readPropertiesFromFile(String path) throws IOException {
+ InputStream input = null;
+
+ try {
+ input = new FileInputStream(path);
+ Properties props = new Properties();
+ props.load(input);
+ return props;
+ } finally {
+ quietClose(input);
+ }
+ }
+
+ /**
+ * Writes {@link Properties} object into a file.
+ *
+ * @param props {@link Properties} object to write
+ * @param path file path
+ */
+ public static void writePropertiesToFile(Properties props, String path) throws IOException {
+ OutputStream output = null;
+
+ try {
+ output = new FileOutputStream(path);
+ props.store(output, null);
+ } finally {
+ quietClose(output);
+ }
+ }
+
+ /**
+ * Closes a closeable object quietly.
+ *
+ * @param handler The handler to close.
+ */
+ public static void quietClose(Closeable handler) {
+ if (handler != null) {
+ try {
+ handler.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Closing stream failed", e);
+ }
+ }
+ }
+}
diff --git a/libs/CommonLib/src/com/android/afwtest/common/NetworkUtils.java b/libs/CommonLib/src/com/android/afwtest/common/NetworkUtils.java
new file mode 100644
index 0000000..ef7e00c
--- /dev/null
+++ b/libs/CommonLib/src/com/android/afwtest/common/NetworkUtils.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2016 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.afwtest.common;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiManager;
+import android.util.Log;
+
+/**
+ * Network utilities.
+ */
+public class NetworkUtils {
+ private static final String TAG = "afwtest.NetworkUtils";
+
+ /**
+ * Enables Wifi.
+ *
+ * @param context {@link Context} object
+ * @return {@code true} if Wifi is enabled successfully; {@code false} otherwise
+ */
+ public static boolean enableWifi(Context context) {
+ WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ return wifiManager != null
+ && (wifiManager.isWifiEnabled() || wifiManager.setWifiEnabled(true));
+ }
+
+ /**
+ * Returns whether the device is currently connected to a wifi.
+ *
+ * @param context {@link Context} object
+ * @return {@code true} if connected to Wifi; {@code false} otherwise
+ */
+ public static boolean isConnectedToWifi(Context context) {
+ NetworkInfo info = getActiveNetworkInfo(context);
+ return info != null
+ && info.isConnected()
+ && info.getType() == ConnectivityManager.TYPE_WIFI;
+ }
+
+ /**
+ * Checks if connected with expected wifi.
+ *
+ * @param context {@link Context} object
+ * @param ssid Wifi SSID
+ * @return {@code true} if the expected wifi
+ */
+ public static boolean isConnectedToSpecifiedWifi(Context context, String ssid) {
+ WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ return ssid != null
+ && wifiManager != null
+ && isConnectedToWifi(context)
+ && wifiManager.getConnectionInfo() != null
+ && ssid.equals(wifiManager.getConnectionInfo().getSSID());
+ }
+
+ /**
+ * Gets the active network.
+ *
+ * @param context {@link Context} object
+ * @return active {@link NetworkInfo}
+ */
+ private static NetworkInfo getActiveNetworkInfo(Context context) {
+ ConnectivityManager cm =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (cm != null) {
+ return cm.getActiveNetworkInfo();
+ }
+ return null;
+ }
+
+ /**
+ * Disconnects a device from the connected Wi-Fi network by removing the network configuration.
+ *
+ * @param context {@link Context} object
+ */
+ public static boolean disconnectFromWifi(Context context) {
+ WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ if (wifiManager.isWifiEnabled() && isConnectedToWifi(context)) {
+ String wifiSsid = wifiManager.getConnectionInfo().getSSID();
+ if (!wifiManager.removeNetwork(wifiManager.getConnectionInfo().getNetworkId())) {
+ Log.e(TAG, String.format("Failed to remove Wifi %s", wifiSsid));
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/libs/CommonLib/src/com/android/afwtest/common/PkgMgrUtils.java b/libs/CommonLib/src/com/android/afwtest/common/PkgMgrUtils.java
new file mode 100644
index 0000000..fac3624
--- /dev/null
+++ b/libs/CommonLib/src/com/android/afwtest/common/PkgMgrUtils.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 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.afwtest.common;
+
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
+
+/**
+ * Utils for {@link PackageManager}.
+ */
+public final class PkgMgrUtils {
+ private static final String TAG = "afwtest.PkgMgrUtil";
+
+ /**
+ * Checks if a package is installed.
+ *
+ * @param context {@link Context} object
+ * @param pkgName name of the package to check
+ * @return {@code true} if target package is installed, {@code false} otherwise
+ */
+ public static boolean isPkgInstalled(Context context, String pkgName) {
+ try {
+ return context.getPackageManager().getPackageInfo(pkgName, 0) != null;
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Pkg not found", e);
+ }
+
+ // Not installed
+ return false;
+ }
+}
diff --git a/libs/CommonLib/src/com/android/afwtest/common/Preconditions.java b/libs/CommonLib/src/com/android/afwtest/common/Preconditions.java
new file mode 100644
index 0000000..bcaf2fc
--- /dev/null
+++ b/libs/CommonLib/src/com/android/afwtest/common/Preconditions.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 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.afwtest.common;
+
+import android.text.TextUtils;
+
+/**
+ * Precondition check utility.
+ */
+public final class Preconditions {
+
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private Preconditions() {
+ }
+
+ /**
+ * Ensures that a string passed is not null or empty.
+ *
+ * @param string a string
+ * @return the non-null, non-empty string that was validated
+ * @throws IllegalArgumentException if {@code String} is null or empty
+ */
+ public static String checkNotEmpty(String string) {
+ if (TextUtils.isEmpty(string)) {
+ throw new IllegalArgumentException();
+ }
+ return string;
+ }
+}
diff --git a/libs/CommonLib/src/com/android/afwtest/common/Timer.java b/libs/CommonLib/src/com/android/afwtest/common/Timer.java
new file mode 100644
index 0000000..b33fcf1
--- /dev/null
+++ b/libs/CommonLib/src/com/android/afwtest/common/Timer.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 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.afwtest.common;
+
+import android.util.Log;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A count-down timer.
+ */
+public final class Timer {
+
+ private static final String TAG = "afwtest.Timer";
+
+ // A year in milliseconds
+ private static final long ONE_YEAR_IN_MS = TimeUnit.DAYS.toMillis(365);
+
+ // Timeout of this timer in milliseconds
+ private final long mTimeoutMs;
+
+ // Start time in milliseconds since Jan 1, 1970
+ private long mStartTime;
+
+ // Elapsed time so far, in milliseconds
+ private long mElapsedTime;
+
+ /**
+ * Constructor.
+ *
+ * @param timeout timeout in milliseconds
+ */
+ public Timer(long timeout) {
+ mTimeoutMs = timeout;
+ }
+
+ /**
+ * Starts this timer.
+ */
+ public void start() {
+ mStartTime = System.currentTimeMillis();
+ mElapsedTime = 0;
+ }
+
+ /**
+ * Checks if time is up by checking if elapsed time is greater than timeout.
+ *
+ * @return {@code true} if time is up, {@code false} otherwise
+ */
+ public boolean isTimeUp() {
+ elapse();
+ return mElapsedTime > mTimeoutMs;
+ }
+
+ /**
+ * Gets elapsed time since start().
+ *
+ * @return long, elapsed time in milliseconds.
+ */
+ public long elapsedTime(){
+ elapse();
+ return mElapsedTime;
+ }
+
+ /**
+ * Re-calculate elapsed time.
+ */
+ private void elapse() {
+ long currentTime = System.currentTimeMillis();
+ long elapsedTime = currentTime - mStartTime;
+ // The system time might change suddenly from 1970 to 21 century, e.g. after factory reset.
+ // Reset mStartTime without updating mElapsedTime.
+ if (elapsedTime > ONE_YEAR_IN_MS) {
+ // Reset time
+ mStartTime = currentTime - mElapsedTime;
+ Log.w(TAG, String.format("System time changed: delta=%d", elapsedTime));
+ } else {
+ mElapsedTime = elapsedTime;
+ }
+ }
+}
diff --git a/libs/CommonLib/src/com/android/afwtest/common/nfcprovisioning/NfcBumpSimulator.java b/libs/CommonLib/src/com/android/afwtest/common/nfcprovisioning/NfcBumpSimulator.java
new file mode 100644
index 0000000..d38a997
--- /dev/null
+++ b/libs/CommonLib/src/com/android/afwtest/common/nfcprovisioning/NfcBumpSimulator.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016 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.afwtest.common.nfcprovisioning;
+
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID;
+import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC;
+import static android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED;
+import static com.android.afwtest.common.Constants.KEY_DEVICE_ADMIN_PKG_CHECKSUM;
+import static com.android.afwtest.common.Constants.KEY_DEVICE_ADMIN_PKG_SIGNATURE_HASH;
+import static com.android.afwtest.common.Preconditions.checkNotEmpty;
+
+import android.content.Context;
+import android.content.Intent;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.NfcAdapter;
+
+import com.android.afwtest.common.test.TestConfig;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Simulates Nfc Bump to start device owner provisioning.
+ */
+public final class NfcBumpSimulator {
+
+ private static final String TAG = "afwtest.NfcBumpSimulator";
+
+ /**
+ * Sends NFC bump to initiate device owner provisioning.
+ *
+ * @param context {@link Context} object
+ * @param propsConfigFile {@link Properties} configuration file containing NFC bump properties.
+ * @return Device admin package name after provisioning completes successfully
+ */
+ public static String sendNfcBump(Context context, String propsConfigFile) throws IOException {
+
+ TestConfig testConfig = TestConfig.get(propsConfigFile);
+
+ Properties bumpProps = new Properties();
+ String deviceAdminPkgName = checkNotEmpty(testConfig.getDeviceAdminPkgName());
+ bumpProps.put(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, deviceAdminPkgName);
+ bumpProps.put(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION,
+ checkNotEmpty(testConfig.getDeviceAdminPkgLocation()));
+
+ String signatureHash = testConfig.getDeviceAdminPkgSignatureHash("");
+ String checksum = testConfig.getDeviceAdminPkgChecksum("");
+ if (signatureHash.isEmpty() && checksum.isEmpty()) {
+ throw new RuntimeException(String.format("Neither %s nor %s is specified in file %s",
+ KEY_DEVICE_ADMIN_PKG_SIGNATURE_HASH,
+ KEY_DEVICE_ADMIN_PKG_CHECKSUM,
+ propsConfigFile));
+ }
+
+ if (!signatureHash.isEmpty()) {
+ bumpProps.put(EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM, signatureHash);
+ }
+
+ // Add checksum if it's not empty because OEM L devices use SHA1 checksum only.
+ if (!checksum.isEmpty()) {
+ bumpProps.put(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM, checksum);
+ }
+
+ String wifiSsid = checkNotEmpty(testConfig.getWifiSsid());
+ // Make sure to surround SSID with double quotes.
+ if (!wifiSsid.startsWith("\"") || !wifiSsid.endsWith("\"")) {
+ wifiSsid = "\"" + wifiSsid + "\"";
+ }
+ bumpProps.put(EXTRA_PROVISIONING_WIFI_SSID, wifiSsid);
+
+ bumpProps.put(EXTRA_PROVISIONING_WIFI_SECURITY_TYPE, testConfig.getWifiSecurityType(""));
+ bumpProps.put(EXTRA_PROVISIONING_WIFI_PASSWORD, testConfig.getWifiPassword(""));
+
+ // Skip encryption.
+ bumpProps.put(EXTRA_PROVISIONING_SKIP_ENCRYPTION, "true");
+
+ sendNfcBump(context, bumpProps);
+
+ // Return expected device admin package name
+ return deviceAdminPkgName;
+ }
+
+ /**
+ * Sends NFC bump to initiate device owner provisioning.
+ *
+ * @param context {@link Context} object.
+ * @param props {@link Properties} object contains NFC bump properties.
+ */
+ public static void sendNfcBump(Context context, Properties props) throws IOException {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+ props.store(stream, "AFW NFC provisioning");
+ NdefRecord record = NdefRecord
+ .createMime(MIME_TYPE_PROVISIONING_NFC, stream.toByteArray());
+ NdefMessage ndfMsg = new NdefMessage(new NdefRecord[]{record});
+
+ Intent intent = new Intent(ACTION_NDEF_DISCOVERED);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.setType(MIME_TYPE_PROVISIONING_NFC);
+ intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[]{ndfMsg});
+
+ context.startActivity(intent);
+ }
+}
diff --git a/libs/CommonLib/src/com/android/afwtest/common/nfcprovisioning/Utils.java b/libs/CommonLib/src/com/android/afwtest/common/nfcprovisioning/Utils.java
new file mode 100644
index 0000000..810846a
--- /dev/null
+++ b/libs/CommonLib/src/com/android/afwtest/common/nfcprovisioning/Utils.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 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.afwtest.common.nfcprovisioning;
+
+import static com.android.afwtest.common.Constants.ACTION_SEND_NFC_BUMP;
+import static com.android.afwtest.common.Constants.KEY_DEVICE_ADMIN_PKG_NAME;
+import static com.android.afwtest.common.Constants.NFC_BUMP_FILE;
+import static com.android.afwtest.common.Constants.SYSTEM_UTIL_PKG_NAME;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+
+import com.android.afwtest.common.FileUtils;
+import com.android.afwtest.common.PkgMgrUtils;
+
+import java.util.Properties;
+
+/**
+ * NFC provisioning utils.
+ */
+public final class Utils {
+
+ /**
+ * Start provisioning by sending an intent to AfwThSystemUtil app.
+ *
+ * @param context {@link Context} object
+ * @param nfcBumpFilePath path of the file containing the parameters to be sent in the Nfc bump
+ * @return expected device admin package name
+ */
+ public static String startProvisioning(Context context, String nfcBumpFilePath)
+ throws Exception {
+ // Find Device Admin Package name
+ Properties props = FileUtils.readPropertiesFromFile(nfcBumpFilePath);
+ if (!props.containsKey(KEY_DEVICE_ADMIN_PKG_NAME)) {
+ throw new RuntimeException(
+ KEY_DEVICE_ADMIN_PKG_NAME + " not specified in " + nfcBumpFilePath);
+ }
+
+ // Check if system util app is installed.
+ String deviceAdminPkgName = props.getProperty(KEY_DEVICE_ADMIN_PKG_NAME);
+ PackageManager pkgManager = context.getPackageManager();
+ if (!PkgMgrUtils.isPkgInstalled(context, SYSTEM_UTIL_PKG_NAME)) {
+ throw new RuntimeException(String.format("%s is not installed", SYSTEM_UTIL_PKG_NAME));
+ }
+
+ // Fire an intent to start nfc provisioning
+ Intent intent = new Intent(ACTION_SEND_NFC_BUMP);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(NFC_BUMP_FILE, nfcBumpFilePath);
+
+ context.startActivity(intent);
+
+ // Return the expected device admin package name
+ return deviceAdminPkgName;
+ }
+}
diff --git a/libs/CommonLib/src/com/android/afwtest/common/qrcodeprovisioning/QRCodeSimulator.java b/libs/CommonLib/src/com/android/afwtest/common/qrcodeprovisioning/QRCodeSimulator.java
new file mode 100644
index 0000000..2c76716
--- /dev/null
+++ b/libs/CommonLib/src/com/android/afwtest/common/qrcodeprovisioning/QRCodeSimulator.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 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.afwtest.common.qrcodeprovisioning;
+
+import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.PersistableBundle;
+import android.text.TextUtils;
+
+import com.android.afwtest.common.test.TestConfig;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Simulates QR code to start device owner provisioning.
+ */
+public final class QRCodeSimulator {
+
+ /**
+ * Sends QR code to initiate device owner provisioning.
+ *
+ * @param context {@link Context} object
+ * @param propsConfigFile {@link Properties} configuration file containing QR code properties.
+ */
+ public static void sendQRCode(Context context, String propsConfigFile)
+ throws IOException {
+ TestConfig testConfig = TestConfig.get(propsConfigFile);
+ // Fire an intent to start qrcode provisioning
+ Intent provisioningIntent =
+ new Intent(ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE);
+
+ String deviceAdminPkgName = testConfig.getDeviceAdminPkgName();
+ String deviceAdminReceiver = testConfig.getDeviceAdminReceiver();
+ String deviceAdminPkgLocation = testConfig.getDeviceAdminPkgLocation();
+ String deviceAdminPkgSignatureHash = testConfig.getDeviceAdminPkgSignatureHash();
+ PersistableBundle adminExtras = testConfig.getAdminExtrasBundle();
+
+ if (!TextUtils.isEmpty(deviceAdminPkgName) && !TextUtils.isEmpty(deviceAdminReceiver)) {
+ provisioningIntent
+ .putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
+ new ComponentName(deviceAdminPkgName, deviceAdminReceiver));
+ }
+ if (!TextUtils.isEmpty(deviceAdminPkgLocation)) {
+ provisioningIntent
+ .putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION,
+ deviceAdminPkgLocation);
+ }
+ if (!TextUtils.isEmpty(deviceAdminPkgSignatureHash)) {
+ provisioningIntent
+ .putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM,
+ deviceAdminPkgSignatureHash);
+ }
+ provisioningIntent
+ .putExtra(EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED,
+ testConfig.getLeaveAllSystemAppsEnabled());
+ if (adminExtras != null) {
+ provisioningIntent.putExtra(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE, adminExtras);
+ }
+
+ context.startActivity(provisioningIntent);
+ }
+}
diff --git a/libs/CommonLib/src/com/android/afwtest/common/qrcodeprovisioning/Utils.java b/libs/CommonLib/src/com/android/afwtest/common/qrcodeprovisioning/Utils.java
new file mode 100644
index 0000000..b6b9e88
--- /dev/null
+++ b/libs/CommonLib/src/com/android/afwtest/common/qrcodeprovisioning/Utils.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 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.afwtest.common.qrcodeprovisioning;
+
+import static com.android.afwtest.common.Constants.ACTION_START_QR_CODE_PROVISIONING;
+import static com.android.afwtest.common.Constants.KEY_DEVICE_ADMIN_PKG_NAME;
+import static com.android.afwtest.common.Constants.QR_CODE_FILE;
+import static com.android.afwtest.common.Constants.SYSTEM_UTIL_PKG_NAME;
+
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.afwtest.common.FileUtils;
+import com.android.afwtest.common.PkgMgrUtils;
+
+import java.util.Properties;
+
+/**
+ * QR Code provisioning utils.
+ */
+public final class Utils {
+
+ /**
+ * Start provisioning by sending an intent to AfwThSystemUtil app.
+ *
+ * @param context {@link Context} object
+ * @param qrcodeFilePath path of the file containing the parameters to be sent in the QR Code
+ * @return expected device admin package name
+ */
+ public static String startProvisioning(Context context, String qrcodeFilePath)
+ throws Exception {
+ // Find Device Admin Package name
+ Properties props = FileUtils.readPropertiesFromFile(qrcodeFilePath);
+ if (!props.containsKey(KEY_DEVICE_ADMIN_PKG_NAME)) {
+ throw new RuntimeException(String.format("%s not specified in %s.",
+ KEY_DEVICE_ADMIN_PKG_NAME, qrcodeFilePath));
+ }
+
+ // Check if system util app is installed.
+ String deviceAdminPkgName = props.getProperty(KEY_DEVICE_ADMIN_PKG_NAME);
+ if (!PkgMgrUtils.isPkgInstalled(context, SYSTEM_UTIL_PKG_NAME)) {
+ throw new RuntimeException(String.format("%s is not installed", SYSTEM_UTIL_PKG_NAME));
+ }
+
+ // Fire an intent to start qrcode provisioning
+ Intent intent = new Intent(ACTION_START_QR_CODE_PROVISIONING);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(QR_CODE_FILE, qrcodeFilePath);
+
+ context.startActivity(intent);
+
+ // Return the expected device admin package name
+ return deviceAdminPkgName;
+ }
+}
diff --git a/libs/CommonLib/src/com/android/afwtest/common/test/OemWidget.java b/libs/CommonLib/src/com/android/afwtest/common/test/OemWidget.java
new file mode 100644
index 0000000..3f82d10
--- /dev/null
+++ b/libs/CommonLib/src/com/android/afwtest/common/test/OemWidget.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2016 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.afwtest.common.test;
+
+import static com.android.afwtest.common.Constants.ACTION_CLICK;
+import static com.android.afwtest.common.Constants.KEY_OEM_WIDGET_ACTION;
+import static com.android.afwtest.common.Constants.KEY_OEM_WIDGET_CLASS;
+import static com.android.afwtest.common.Constants.KEY_OEM_WIDGET_DESCRIPTION;
+import static com.android.afwtest.common.Constants.KEY_OEM_WIDGET_PACKAGE;
+import static com.android.afwtest.common.Constants.KEY_OEM_WIDGET_RESOURCE_ID;
+import static com.android.afwtest.common.Constants.KEY_OEM_WIDGET_SCROLL_DIRECTION;
+import static com.android.afwtest.common.Constants.KEY_OEM_WIDGET_TEXT;
+
+import android.util.Log;
+
+import com.android.afwtest.common.Preconditions;
+
+/**
+ * A data class to store properties of an OEM widget.
+ */
+public final class OemWidget {
+
+ private static final String TAG = "afwtest.OemWidget";
+
+ // Id of the OEM widget.
+ private final String mId;
+
+ // Text property of this widget.
+ private final String mText;
+
+ // Description property of this widget.
+ private final String mDescription;
+
+ // Resource id of this widget.
+ private final String mResourceId;
+
+ // Package name of this widget.
+ private final String mPackage;
+
+ // Class name of this widget.
+ private final String mClass;
+
+ // Action of this widget, any of {@link com.android.afwtest.common.Constants#ACTION_CLICK},
+ // {@link com.android.afwtest.common.Constants#ACTION_CHECK} or
+ // {@link com.android.afwtest.common.Constants#ACTION_SCROLL}.
+ private final String mAction;
+
+ // If {@link #mAction} equals {@link com.android.afwtest.common.Constants#ACTION_SCROLL}
+ // this field specifies direction of scrolling.
+ private final String mScrollDirection;
+
+ /**
+ * Constructs a {@link OemWidget} from configuration file.
+ *
+ * @param testConfig Path of the test configuration file
+ * @param widgetId id of the widget to construct.
+ */
+ public OemWidget(TestConfig testConfig, String widgetId) {
+ // Check Id.
+ Preconditions.checkNotEmpty(widgetId);
+
+ Log.d(TAG, "Creating OemWidget " + widgetId);
+
+ mId = widgetId;
+
+ mText = testConfig.getProperty(widgetId + "." + KEY_OEM_WIDGET_TEXT, "");
+ mDescription = testConfig.getProperty(widgetId + "." + KEY_OEM_WIDGET_DESCRIPTION, "");
+ mResourceId = testConfig.getProperty(widgetId + "." + KEY_OEM_WIDGET_RESOURCE_ID, "");
+ mPackage = testConfig.getProperty(widgetId + "." + KEY_OEM_WIDGET_PACKAGE, "");
+ mClass = testConfig.getProperty(widgetId + "." + KEY_OEM_WIDGET_CLASS, "");
+ mAction = testConfig.getProperty(widgetId + "." + KEY_OEM_WIDGET_ACTION, ACTION_CLICK);
+ mScrollDirection =
+ testConfig.getProperty(widgetId + "." + KEY_OEM_WIDGET_SCROLL_DIRECTION, "UP");
+
+ // At least one property is specified.
+ Preconditions.checkNotEmpty(mText + mDescription + mResourceId + mPackage + mClass);
+
+ Log.d(TAG, "OemWidget: " + toString());
+ }
+
+ /**
+ * Gets id of this widget.
+ *
+ * @return id of this widget
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Gets text property of this widget.
+ *
+ * @return text property of this widget
+ */
+ public String getText() {
+ return mText;
+ }
+
+ /**
+ * Gets description property of this widget.
+ *
+ * @return description property of this widget
+ */
+ public String getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Gets resource id of this widget.
+ *
+ * @return resournce id of this widget
+ */
+ public String getResourceId() {
+ return mResourceId;
+ }
+
+ /**
+ * Gets package name of this widget.
+ *
+ * @return package name of this widget
+ */
+ public String getPackage() {
+ return mPackage;
+ }
+
+ /**
+ * Gets class name of this widget.
+ *
+ * @return class name of this widget
+ */
+ public String getClassName() {
+ return mClass;
+ }
+
+ /**
+ * Gets action of this widget.
+ *
+ * @return action of this widget
+ */
+ public String getAction() {
+ return mAction;
+ }
+
+ /**
+ * Gets scrolling direction of this widget.
+ *
+ * @return scrolling direction of this widget.
+ */
+ public String getScrollDirection() {
+ return mScrollDirection;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return String.format(
+ "OemWidget[Id=%s,text=[%s],desc=[%s],res=[%s],pkg=[%s],class=[%s],action=[%s],"
+ + "scroll_direction=[%s]]",
+ mId, mText, mDescription, mResourceId, mPackage, mClass, mAction, mScrollDirection);
+ }
+}
diff --git a/libs/CommonLib/src/com/android/afwtest/common/test/StatsLogger.java b/libs/CommonLib/src/com/android/afwtest/common/test/StatsLogger.java
new file mode 100644
index 0000000..6131f9d
--- /dev/null
+++ b/libs/CommonLib/src/com/android/afwtest/common/test/StatsLogger.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 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.afwtest.common.test;
+
+import static android.os.Environment.getExternalStorageDirectory;
+
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Range;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Singleton class to measure and save time stats in a local file in External Storage Directory.
+ *
+ * <p>writeStatsToFile() should be called at the end of the test.</p>
+ */
+public final class StatsLogger {
+
+ private static final String TAG = "afwtest.StatsLogger";
+
+ // Map stats file with corresponding {@link StatsLogger} object.
+ private static Map<String, StatsLogger> sStasLogger = new HashMap<String, StatsLogger>();
+
+ // File to save stats.
+ private File mAfwStatsFile;
+
+ // Time stats being logged by this StatsLogger.
+ // The key of the map is the name of a metric.
+ private Map<String, Range<Long>> mTimeStats = new HashMap<String, Range<Long>>();
+
+ /**
+ * Constructor.
+ *
+ * @param fileName Path of the file to save stats
+ */
+ private StatsLogger(String fileName) throws IOException {
+ mAfwStatsFile = new File(getExternalStorageDirectory().getAbsolutePath(), fileName);
+
+ if (!mAfwStatsFile.exists()){
+ Log.d(TAG, String.format("%s does not exist, creating... ", fileName));
+ mAfwStatsFile.createNewFile();
+ }
+ }
+
+ /**
+ * Gets {@link StatsLogger} object for given file, or create a new one if does not exist.
+ *
+ * @param fileName file relative to external storage.
+ */
+ public static StatsLogger getInstance(String fileName) throws IOException {
+ if (sStasLogger.containsKey(fileName)){
+ return sStasLogger.get(fileName);
+ }
+
+ StatsLogger statsLogger = new StatsLogger(fileName);
+ sStasLogger.put(fileName, statsLogger);
+ return statsLogger;
+ }
+
+ /**
+ * Saves the stats to file.
+ */
+ public void writeStatsToFile() throws IOException {
+ FileOutputStream outputStream = new FileOutputStream(mAfwStatsFile);
+ String stats = toCsvFormatString(mTimeStats);
+
+ try {
+ outputStream.write(stats.getBytes());
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to write stats to file", e);
+ } finally {
+ outputStream.close();
+ }
+ }
+
+ /**
+ * Converts set of time stats to cvs formatted string.
+ *
+ * @param timeStats time stats to convert, the key of the map is time stats name
+ * @return String format:
+ * Key1,Key2,...\n
+ * Time1,Time2,...\n
+ */
+ private static String toCsvFormatString(Map<String, Range<Long>> timeStats) {
+ List<String> keyList = new ArrayList();
+ List<String> valueList = new ArrayList();
+ for (Map.Entry<String, Range<Long>> entry : timeStats.entrySet()) {
+ keyList.add(entry.getKey());
+ long interval = entry.getValue().getUpper() - entry.getValue().getLower();
+ valueList.add(String.format("%d", interval));
+ }
+ String keys = TextUtils.join(",", keyList);
+ String values = TextUtils.join(",", valueList);
+
+ return String.format("%s\n%s", keys, values);
+ }
+
+ /**
+ * Start counting time associated with the passed metric.
+ *
+ * @param keyName name of the metric to measure.
+ */
+ public void startTime(String keyName){
+ long currentTime = System.currentTimeMillis();
+ Range<Long> range = Range.create(currentTime, currentTime);
+ Log.d(TAG, String.format("%s start-time: %d", keyName, currentTime));
+
+ mTimeStats.put(keyName, range);
+ Log.d(TAG, String.format("HashMap for %s:%s", keyName, range.toString()));
+ }
+
+ /**
+ * Stops counting time associated with the given metric and return the period.
+ *
+ * @param keyName name of the metric to measure
+ * @return time between startTime and stopTime in milliseconds
+ */
+ public long stopTime(String keyName) throws Exception {
+ // Find the key
+ if (!mTimeStats.containsKey(keyName)) {
+ throw new RuntimeException(String.format("%s was never started", keyName));
+ }
+
+ // Set end time
+ Range<Long> range = mTimeStats.get(keyName).extend(System.currentTimeMillis());
+ mTimeStats.put(keyName, range);
+ Log.d(TAG, String.format("%s: %s", keyName, range.toString()));
+
+ return range.getUpper() - range.getLower();
+ }
+}
+
diff --git a/libs/CommonLib/src/com/android/afwtest/common/test/TestConfig.java b/libs/CommonLib/src/com/android/afwtest/common/test/TestConfig.java
new file mode 100644
index 0000000..5b88a05
--- /dev/null
+++ b/libs/CommonLib/src/com/android/afwtest/common/test/TestConfig.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2016 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.afwtest.common.test;
+
+import static com.android.afwtest.common.Constants.ADMIN_EXTRAS_BUNDLE;
+import static com.android.afwtest.common.Constants.KEY_APP_CRASH_WHITELIST;
+import static com.android.afwtest.common.Constants.KEY_DEVICE_ADMIN_PKG_CHECKSUM;
+import static com.android.afwtest.common.Constants.KEY_DEVICE_ADMIN_PKG_LOCATION;
+import static com.android.afwtest.common.Constants.KEY_DEVICE_ADMIN_PKG_NAME;
+import static com.android.afwtest.common.Constants.KEY_DEVICE_ADMIN_RECEIVER;
+import static com.android.afwtest.common.Constants.KEY_DEVICE_ADMIN_PKG_SIGNATURE_HASH;
+import static com.android.afwtest.common.Constants.KEY_MUTE_APP_CRASH_DIALOGS;
+import static com.android.afwtest.common.Constants.KEY_OEM_WIDGETS;
+import static com.android.afwtest.common.Constants.KEY_TEST_TIMEOUT_MIN;
+import static com.android.afwtest.common.Constants.KEY_TIMEOUT_SIZE;
+import static com.android.afwtest.common.Constants.KEY_WIFI_PWD;
+import static com.android.afwtest.common.Constants.KEY_WIFI_SECURITY_TYPE;
+import static com.android.afwtest.common.Constants.KEY_WIFI_SSID;
+import static com.android.afwtest.common.Constants.KEY_WORK_ACCOUNT_PASSWORD;
+import static com.android.afwtest.common.Constants.KEY_WORK_ACCOUNT_USERNAME;
+import static com.android.afwtest.common.Constants.LEAVE_ALL_SYSTEM_APPS_ENABLED;
+
+import android.os.PersistableBundle;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import com.android.afwtest.common.FileUtils;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * A singleton class that reads properties from afw-test.props and provides interfaces
+ * to get test configurations.
+ */
+public final class TestConfig {
+
+ private static final String TAG = "afwtest.TestConfig";
+
+ /**
+ * Mapping from timeout size to its integer value.
+ */
+ private static final Map<String, Integer> TIMEOUT_SIZE_MAPPING =
+ new HashMap<String, Integer>() {{
+ put("S", 2);
+ put("M", 3);
+ put("L", 5);
+ put("XL", 8);
+ put("XXL", 13);
+ }};
+
+ /**
+ * File path of the default test config file.
+ */
+ public static final String DEFAULT_TEST_CONFIG_FILE = "/data/local/tmp/afw-test.props";
+
+ // Mapping from test configuration file path and its loaded {@link TestConfig} object.
+ private static Map<String, TestConfig> sTestConfigs = new HashMap<String, TestConfig>();
+
+ // Store configurations loaded from a file.
+ private final Properties mProps;
+
+ /**
+ * Creates a new object by loading configurations from file.
+ *
+ * @param configFilePath path of the test configuration file
+ */
+ private TestConfig(String configFilePath) throws IOException {
+ mProps = FileUtils.readPropertiesFromFile(configFilePath);
+ }
+
+ /**
+ * Gets {@link TestConfig} object for given file path.
+ *
+ * @param configFilePath file path of the test configuration
+ * @return {@link TestConfig} object for given file path
+ */
+ public static TestConfig get(String configFilePath) throws IOException {
+ if (sTestConfigs.containsKey(configFilePath)) {
+ return sTestConfigs.get(configFilePath);
+ }
+
+ // Create new object.
+ TestConfig testConfig = new TestConfig(configFilePath);
+ sTestConfigs.put(configFilePath, testConfig);
+ return testConfig;
+ }
+
+ /**
+ * Gets {@link TestConfig} object for {@link #DEFAULT_TEST_CONFIG_FILE}.
+ *
+ * @return {@link TestConfig} object for {@link #DEFAULT_TEST_CONFIG_FILE}
+ */
+ public static TestConfig getDefault() throws IOException {
+ return get(DEFAULT_TEST_CONFIG_FILE);
+ }
+
+ /**
+ * Gets device admin package name.
+ *
+ * @return device admin package name
+ */
+ public String getDeviceAdminPkgName() {
+ return mProps.getProperty(KEY_DEVICE_ADMIN_PKG_NAME);
+ }
+
+ /**
+ * Gets device admin package name.
+ *
+ * @param defaultValue default value if device admin pkg name is not found
+ * @return device admin package name
+ */
+ public String getDeviceAdminPkgName(String defaultValue) {
+ return mProps.getProperty(KEY_DEVICE_ADMIN_PKG_NAME, defaultValue);
+ }
+
+ /**
+ * Gets device admin receiver.
+ *
+ * @return device admin receiver
+ */
+ public String getDeviceAdminReceiver() {
+ return mProps.getProperty(KEY_DEVICE_ADMIN_RECEIVER);
+ }
+
+ /**
+ * Gets device admin receiver.
+ *
+ * @param defaultValue default value if device admin receiver is not found
+ * @return device admin receiver
+ */
+ public String getDeviceAdminReceiver(String defaultValue) {
+ return mProps.getProperty(KEY_DEVICE_ADMIN_RECEIVER, defaultValue);
+ }
+
+ /**
+ * Gets device admin package location.
+ *
+ * @return device admin package location
+ */
+ public String getDeviceAdminPkgLocation() {
+ return mProps.getProperty(KEY_DEVICE_ADMIN_PKG_LOCATION);
+ }
+
+ /**
+ * Gets device admin package location.
+ *
+ * @param defaultValue default value if device admin package location is not found
+ * @return device admin package location
+ */
+ public String getDeviceAdminPkgLocation(String defaultValue) {
+ return mProps.getProperty(KEY_DEVICE_ADMIN_PKG_LOCATION, defaultValue);
+ }
+
+ /**
+ * Gets device admin package checksum.
+ *
+ * @return device admin package checksum
+ */
+ public String getDeviceAdminPkgChecksum() {
+ return mProps.getProperty(KEY_DEVICE_ADMIN_PKG_CHECKSUM);
+ }
+
+ /**
+ * Gets device admin package checksum.
+ *
+ * @param defaultValue default valud if device admin package checksum is not found
+ * @return device admin package checksum
+ */
+ public String getDeviceAdminPkgChecksum(String defaultValue) {
+ return mProps.getProperty(KEY_DEVICE_ADMIN_PKG_CHECKSUM, defaultValue);
+ }
+
+ /**
+ * Gets device admin package signature hash.
+ *
+ * @return device admin package signature hash
+ */
+ public String getDeviceAdminPkgSignatureHash() {
+ return mProps.getProperty(KEY_DEVICE_ADMIN_PKG_SIGNATURE_HASH);
+ }
+
+ /**
+ * Gets device admin package signature hash.
+ *
+ * @param defaultValue default value if device admin package signature hash is not found
+ * @return device admin package signature hash
+ */
+ public String getDeviceAdminPkgSignatureHash(String defaultValue) {
+ return mProps.getProperty(KEY_DEVICE_ADMIN_PKG_SIGNATURE_HASH, defaultValue);
+ }
+
+ /**
+ * Gets timeout size value from props file.
+ *
+ * <p>
+ * Possible size values are strings "S", "M", "L", "XL" or "XXL".
+ * </p>
+ *
+ * @return An integer corresponding to the timeout size configured in the config file.
+ */
+ public int getTimeoutSize() {
+ // Default to M
+ String timeoutSize = getProperty(KEY_TIMEOUT_SIZE, "M");
+ if (!TIMEOUT_SIZE_MAPPING.containsKey(timeoutSize)) {
+ Log.w(TAG, "Invalid timeout size, defaulting to M");
+ timeoutSize = "M";
+ }
+
+ return TIMEOUT_SIZE_MAPPING.get(timeoutSize);
+ }
+
+ /**
+ * Gets test timeout in minutes.
+ *
+ * @return test timeout in minutes or -1 if either timeout is not specified or invalid
+ */
+ public int getTestTimeoutMin() {
+ return getTestTimeoutMin(-1);
+ }
+
+ /**
+ * Gets test timeout in minutes.
+ *
+ * @param defaultValue default value if test timeout not specified
+ * @return test timeout in minutes
+ */
+ public int getTestTimeoutMin(int defaultValue) {
+ String value = mProps.getProperty(KEY_TEST_TIMEOUT_MIN);
+ if (value == null || value.isEmpty()) {
+ return defaultValue;
+ }
+
+ return Integer.valueOf(value);
+ }
+
+ /**
+ * Gets Wifi SSID.
+ *
+ * @return Wifi SSID
+ */
+ public String getWifiSsid() {
+ return mProps.getProperty(KEY_WIFI_SSID);
+ }
+
+ /**
+ * Gets Wifi SSID.
+ *
+ * @param defaultValue default value if Wifi SSID is not found
+ * @return Wifi SSID
+ */
+ public String getWifiSsid(String defaultValue) {
+ return mProps.getProperty(KEY_WIFI_SSID, defaultValue);
+ }
+
+ /**
+ * Gets Wifi password.
+ *
+ * @return Wifi password
+ */
+ public String getWifiPassword() {
+ return mProps.getProperty(KEY_WIFI_PWD);
+ }
+
+ /**
+ * Gets Wifi password.
+ *
+ * @param defaultValue default value if Wifi password is not found
+ * @return Wifi password
+ */
+ public String getWifiPassword(String defaultValue) {
+ return mProps.getProperty(KEY_WIFI_PWD, defaultValue);
+ }
+
+ /**
+ * Gets Wifi security type.
+ *
+ * @return Wifi security type
+ */
+ public String getWifiSecurityType() {
+ return mProps.getProperty(KEY_WIFI_SECURITY_TYPE);
+ }
+
+ /**
+ * Gets Wifi security type.
+ *
+ * @param defaultValue default value if Wifi security type is not found
+ * @return Wifi security type
+ */
+ public String getWifiSecurityType(String defaultValue) {
+ return mProps.getProperty(KEY_WIFI_SECURITY_TYPE, defaultValue);
+ }
+
+ /**
+ * Gets work account user name.
+ *
+ * @return work account user name
+ */
+ public String getWorkAccountUsername() {
+ return mProps.getProperty(KEY_WORK_ACCOUNT_USERNAME);
+ }
+
+ /**
+ * Gets work account user name.
+ *
+ * @param defaultValue default value if work account user name is not found
+ * @return work account user name
+ */
+ public String getWorkAccountUsername(String defaultValue) {
+ return mProps.getProperty(KEY_WORK_ACCOUNT_USERNAME, defaultValue);
+ }
+
+ /**
+ * Gets work account password.
+ *
+ * @return work account password
+ */
+ public String getWorkAccountPassword() {
+ return mProps.getProperty(KEY_WORK_ACCOUNT_PASSWORD);
+ }
+
+ /**
+ * Gets work account password.
+ *
+ * @param defaultValue default value if work account password is not found
+ * @return work account password
+ */
+ public String getWorkAccountPassword(String defaultValue) {
+ return mProps.getProperty(KEY_WORK_ACCOUNT_PASSWORD, defaultValue);
+ }
+
+ /**
+ * Gets the property of a specific key.
+ *
+ * @param key property key
+ * @return property value of the given key
+ * If the key doesn't exist in the configuration file, null will be returned;
+ * If the value of the key is empty, empty string will be returned
+ */
+ public String getProperty(String key) {
+ return mProps.getProperty(key);
+ }
+
+ /**
+ * Gets the property of a specific key.
+ *
+ * @param key property key
+ * @param defaultValue default value if given key is not found
+ * @return property value of the given key;
+ * if given key not found, {@link #defaultValue} is returned
+ */
+ public String getProperty(String key, String defaultValue) {
+ return mProps.getProperty(key, defaultValue);
+ }
+
+ /**
+ * Gets the OEM widgets as a list.
+ *
+ * @return {@link List} of {@link OemWidget}
+ */
+ public List<OemWidget> getOemWidgets() {
+ List<OemWidget> oemWidgets = new LinkedList<OemWidget>();
+
+ String idList = mProps.getProperty(KEY_OEM_WIDGETS, "");
+ if (!idList.isEmpty()) {
+ String[] ids = idList.split(",");
+ for (String id : ids) {
+ OemWidget oemWidget = new OemWidget(this, id);
+ oemWidgets.add(oemWidget);
+ }
+ }
+
+ return oemWidgets;
+ }
+
+ /**
+ * Whether to mute app crash dialogs.
+ *
+ * @return {@code true} if yes, {@code false} otherwise
+ */
+ public boolean muteAppCrashDialogs() {
+ return Boolean.parseBoolean(mProps.getProperty(KEY_MUTE_APP_CRASH_DIALOGS, "true"));
+ }
+
+ /**
+ * Gets list of app names that if they crash, just auto close the dialog.
+ *
+ * @return set of app names that in the app crash dialog message, all in lower case
+ */
+ public List<String> getAppCrashWhitelist() {
+ String[] apps = getProperty(KEY_APP_CRASH_WHITELIST, "").split(",");
+ List<String> results = new LinkedList(Arrays.asList(apps));
+ results.removeAll(Arrays.asList("", null));
+ return results;
+ }
+
+ /**
+ * Gets whether all system apps should be left enabled while provisioning
+ */
+ public boolean getLeaveAllSystemAppsEnabled() {
+ return Boolean.parseBoolean(mProps.getProperty(LEAVE_ALL_SYSTEM_APPS_ENABLED, "false"));
+ }
+
+ /**
+ * Gets the admin extras to be passed along during provisioning
+ */
+ @Nullable
+ public PersistableBundle getAdminExtrasBundle() {
+ try {
+ String rawExtrasString = getProperty(ADMIN_EXTRAS_BUNDLE, null);
+ if (rawExtrasString == null)
+ return null;
+
+ JSONObject adminExtras = new JSONObject(rawExtrasString);
+ PersistableBundle adminExtrasBundle = new PersistableBundle();
+ Iterator<String> keys = adminExtras.keys();
+
+ while (keys.hasNext()) {
+ String key = keys.next();
+ Object val = adminExtras.get(key);
+
+ if (val == null) {
+ // Assume any null value from the provisioning data json is of type string.
+ adminExtrasBundle.putString(key, null);
+ } else if (val instanceof Boolean) {
+ adminExtrasBundle.putBoolean(key, (Boolean) val);
+ } else if (val instanceof Double) {
+ adminExtrasBundle.putDouble(key, (Double) val);
+ } else if (val instanceof Integer) {
+ adminExtrasBundle.putInt(key, (Integer) val);
+ } else if (val instanceof Long) {
+ adminExtrasBundle.putLong(key, (Long) val);
+ } else if (val instanceof String) {
+ adminExtrasBundle.putString(key, (String) val);
+ } else {
+ throw new JSONException("Unsupported value for key: " + key);
+ }
+ }
+
+ return adminExtrasBundle;
+
+ } catch (JSONException e) {
+ Log.w(TAG, "Invalid JSON format for admin extras bundle, returning null");
+ return null;
+ }
+ }
+
+
+}
diff --git a/libs/UiAutomatorLib/Android.mk b/libs/UiAutomatorLib/Android.mk
new file mode 100644
index 0000000..5aade26
--- /dev/null
+++ b/libs/UiAutomatorLib/Android.mk
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2016 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator AfwThCommonLib android-support-test
+
+LOCAL_MODULE := AfwThUiAutomatorLib
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/Constants.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/Constants.java
new file mode 100644
index 0000000..bb9e3df
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/Constants.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator;
+
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.webkit.WebView;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.RadioButton;
+import android.widget.ScrollView;
+import android.widget.Switch;
+
+import java.util.regex.Pattern;
+
+/**
+ * Constants for this lib and test packages.
+ */
+public final class Constants {
+
+ /**
+ * Android package name.
+ */
+ public static final String ANDROID_PKG_NAME = "android";
+
+ /**
+ * System UI package name.
+ */
+ public static final String SYSTEM_UI_PKG_NAME = "com.android.systemui";
+
+ /**
+ * GMSCore package name.
+ */
+ public static final String GMS_PKG_NAME = "com.google.android.gms";
+
+ /**
+ * GMSCore package name regular expression string.
+ */
+ public static final String GMS_PKG_NAME_REGEX =
+ Pattern.quote(GMS_PKG_NAME) + "(\\.[^:]+)?";
+
+ /**
+ * Managed provisioning package name.
+ */
+ public static final String MANAGED_PROVISIONING_PKG_NAME = "com.android.managedprovisioning";
+
+ /**
+ * Android package installer package name.
+ */
+ public static final String PACKAGE_INSTALLER_PKG_NAME = "com.android.packageinstaller";
+
+ /**
+ * TestDpc package name.
+ */
+ public static final String TESTDPC_PKG_NAME = "com.afwsamples.testdpc";
+
+ /**
+ * Default file name for provisioning performance stats.
+ */
+ public static final String PROVISIONING_STATS_FILE = "Provisioning-Stats.csv";
+
+ /**
+ * Representation name of TestDpc work profile creation time.
+ */
+ public static final String STAT_TESTDPC_WORK_PROFILE_CREATION_TIME =
+ "testDPC Work Profile creation";
+
+ /**
+ * Representation name of Managed Provisioning work profile creation name.
+ */
+ public static final String STAT_MANAGED_PROVISIONING_WORK_PROFILE_CREATION_TIME =
+ "MP Work Profile creation";
+
+ /**
+ * GMS button with text ok.
+ */
+ public static final BySelector GMS_BTN_WITH_TEXT_OK =
+ By.pkg(GMS_PKG_NAME).text(Pattern.compile("ok", CASE_INSENSITIVE));
+
+ /**
+ * GMS button with content description ok.
+ */
+ public static final BySelector GMS_BTN_WITH_DESC_OK =
+ By.pkg(GMS_PKG_NAME).desc(Pattern.compile("ok", CASE_INSENSITIVE));
+
+ /**
+ * Regular expression string to match resource id of GMSCore NEXT button.
+ */
+ public static final String GMS_NEXT_BTN_RESOURCE_ID_REGEX =
+ GMS_PKG_NAME_REGEX
+ + ":id/(auth_setup_wizard_navbar_next"
+ + "|suw_navbar_next"
+ + "|google_services_next_button_item"
+ + "|auth_device_management_download_next_button"
+ + "|next_button)";
+ /**
+ * {@link BySelector} for {@link Button} in GMS core with description "ACCEPT".
+ */
+ public static final BySelector GMS_ACCEPT_BUTTON_SELECTOR =
+ By.pkg(GMS_PKG_NAME)
+ .desc(Pattern.compile("ACCEPT", CASE_INSENSITIVE));
+
+ /**
+ * {@link BySelector} for {@link Button} in GMS core with text "NEXT".
+ */
+ public static final BySelector GMS_BUTTON_WITH_TEXT_NEXT =
+ By.pkg(GMS_PKG_NAME)
+ .text(Pattern.compile("NEXT", CASE_INSENSITIVE));
+
+
+ /**
+ * {@link BySelector} for {@link Button} in GMS core with description "NEXT".
+ */
+ public static final BySelector GMS_NEXT_BUTTON_SELECTOR =
+ By.pkg(GMS_PKG_NAME)
+ .desc(Pattern.compile("NEXT", CASE_INSENSITIVE));
+
+ /**
+ * Resource Id {@link BySelector} for GmsCore NEXT button.
+ */
+ public static final BySelector GMS_NEXT_BUTTON_RES_SELECTOR =
+ By.res(Pattern.compile(GMS_NEXT_BTN_RESOURCE_ID_REGEX))
+ .enabled(true)
+ .clickable(true);
+
+ /**
+ * {@link BySelector} for {@link EditText} in GMS core.
+ */
+ public static final BySelector GMS_TEXT_FIELD_SELECTOR = By.pkg(GMS_PKG_NAME).clazz(
+ EditText.class.getName());
+
+ /**
+ * {@link BySelector} for {@link CheckBox} in GMS core with resource-id "agree_backup".
+ */
+ public static final BySelector GMS_AGREE_BACKUP_SELECTOR =
+ By.res(Pattern.compile(GMS_PKG_NAME_REGEX + ":id/agree_backup"));
+
+ /**
+ * {@link BySelector} for {@link Switch} in GMS core with resource-id
+ * "suw_items_switch".
+ */
+ public static final BySelector GMS_SWITCH_SELECTOR =
+ By.res(Pattern.compile(GMS_PKG_NAME_REGEX + ":id/suw_items_switch"));
+
+ /**
+ * {@link BySelector} for {@link Button} in GMS core with text "CLOSE".
+ */
+ public static final BySelector GMS_CLOSE_BACKUP_MESSAGE_SELECTOR =
+ By.pkg(GMS_PKG_NAME).text("CLOSE");
+
+ /**
+ * @{@link BySelector} for downloading MDMs in GmsCore.
+ */
+ public static final BySelector GMS_DOWNLOADING_DIALOG_SELECTOR =
+ By.text(Pattern.compile("Downloading.*"))
+ .res(ANDROID_PKG_NAME, "message")
+ .pkg(GMS_PKG_NAME);
+
+ /**
+ * {@link BySelector} for GMSCore web view UI.
+ */
+ public static final BySelector GMS_WEBVIEW_SELECTOR =
+ By.pkg(Constants.GMS_PKG_NAME).clazz(WebView.class.getName());
+
+ /**
+ * @{@link BySelector} unique to the add account page of GmsCore.
+ */
+ public static final BySelector GMS_ADD_ACCOUNT_PAGE_SELECTOR =
+ By.pkg(GMS_PKG_NAME)
+ .desc(Pattern.compile("Add your account|Sign in", CASE_INSENSITIVE));
+
+ /**
+ * {@link BySelector} for {@link EditText} with resource-name "password" in GMSCore.
+ */
+ public static final BySelector GMS_PASSWORD_FIELD_SELECTOR =
+ By.pkg(GMS_PKG_NAME).res("password");
+
+ /**
+ * {@link BySelector} for {@link Button} in ManagedProvisioning to set up the profile/device.
+ */
+ public static final BySelector MANAGED_PROVISIONING_SETUP_BUTTON_SELECTOR = By.res(
+ Pattern.compile(MANAGED_PROVISIONING_PKG_NAME + ":id/next_button"));
+
+ /**
+ * {@link BySelector} in ManagedProvisioning.
+ */
+ public static final BySelector MANAGED_PROVISIONING_PKG_SELECTOR = By.pkg(
+ MANAGED_PROVISIONING_PKG_NAME);
+
+ /**
+ * {@link BySelector} for {@link Button} in ManagedProvisioning with resource-id
+ * positive_button.
+ */
+ public static final BySelector MANAGED_PROVISIONING_OK_BUTTON_SELECTOR = By.res(
+ MANAGED_PROVISIONING_PKG_NAME, "positive_button");
+
+ public static final BySelector MANAGED_PROVISIONING_SCROLL_VIEW_SELECTOR =
+ By.pkg(MANAGED_PROVISIONING_PKG_NAME).clazz(ScrollView.class.getName());
+
+ /**
+ * {@link BySelector} for {@link Button} in Package Installer with resource-id ok_button.
+ */
+ public static final BySelector PACKAGE_INSTALLER_INSTALL_BUTTON_SELECTOR = By.res(
+ PACKAGE_INSTALLER_PKG_NAME, "ok_button");
+
+ public static final BySelector TESTDPC_SETUP_MANAGEMENT_PAGE_TITLE_SELECTOR =
+ By.pkg(TESTDPC_PKG_NAME).text(Pattern.compile("Set.*management.*", CASE_INSENSITIVE));
+
+ public static final BySelector TESTDPC_SCROLL_VIEW_SELECTOR =
+ By.pkg(TESTDPC_PKG_NAME).clazz(ScrollView.class.getName());
+
+ /**
+ * {@link BySelector} for {@link RadioButton} with resource-id setup_device_owner on
+ * TestDpc Setup Management page.
+ */
+ public static final BySelector TESTDPC_SETUP_DEVICE_OWNER_RADIO_BUTTON_SELECTOR =
+ By.res(TESTDPC_PKG_NAME, "setup_device_owner")
+ .clazz(RadioButton.class.getName())
+ .clickable(true)
+ .checkable(true);
+
+ /**
+ * {@link BySelector} for {@link RadioButton} with resource-id setup_managed_profile on
+ * TestDpc Setup Management page.
+ */
+ public static final BySelector TESTDPC_SETUP_MANAGED_PROFILE_RADIO_BUTTON_SELECTOR =
+ By.res(TESTDPC_PKG_NAME, "setup_managed_profile")
+ .clazz(RadioButton.class.getName())
+ .clickable(true)
+ .checkable(true);
+
+ /**
+ * {@link BySelector} for the 'NEXT' navigate button on TestDpc pages.
+ */
+ public static final BySelector TESTDPC_NEXT_BUTTON_SELECTOR =
+ By.res(TESTDPC_PKG_NAME, "suw_navbar_next")
+ .clazz(Button.class.getName())
+ .clickable(true);
+
+ /**
+ * {@link BySelector} for Managed Provisioning error message.
+ */
+ public static final BySelector MANAGED_PROVISIONING_ERROR_MSG_SELECTOR =
+ By.pkg(MANAGED_PROVISIONING_PKG_NAME).res(ANDROID_PKG_NAME, "message");
+
+ /**
+ * {@link BySelector} for Android platform alert message.
+ */
+ public static final BySelector ANDROID_ALERT_MSG_SELECTOR = By.res(ANDROID_PKG_NAME, "message");
+
+ /**
+ * {@link BySelector} for Android platform button.
+ */
+ public static final BySelector ANDROID_DIALOG_BTN_SELECTOR = By.clazz(Button.class.getName());
+
+ /**
+ * {@link BySelector} for {@link Button} in TestDpc with resource-id finish_setup.
+ */
+ public static final BySelector TESTDPC_FINISH_SKIP_BUTTON_SELECTOR =
+ By.res(TESTDPC_PKG_NAME, "btn_add_account_skip");
+
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private Constants() {
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/LandingPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/LandingPage.java
new file mode 100644
index 0000000..4b99dc6
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/LandingPage.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.pages;
+
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+
+/**
+ * A {@link UiPage} that won't navigate any further.
+ *
+ * <p>Such page is usually used as the last page of an automation flow.</p>
+ */
+public final class LandingPage extends UiPage {
+
+ /**
+ * Unique element that this page should wait for.
+ */
+ private final BySelector mUniqueElementSelector;
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ * @param uniqueElementSelector {@link BySelector} of the ui element to wait for
+ */
+ public LandingPage(UiDevice uiDevice,
+ TestConfig config,
+ BySelector uniqueElementSelector) {
+ super(uiDevice, config);
+ mUniqueElementSelector = uniqueElementSelector;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return mUniqueElementSelector;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ // Do nothing.
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/PageSkipper.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/PageSkipper.java
new file mode 100644
index 0000000..cc3c8c8
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/PageSkipper.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.pages;
+
+import static com.android.afwtest.common.Constants.ACTION_CHECK;
+import static com.android.afwtest.common.Constants.ACTION_CLICK;
+import static com.android.afwtest.common.Constants.ACTION_SCROLL;
+import static com.android.afwtest.uiautomator.utils.WidgetUtils.safeClick;
+import static com.android.afwtest.uiautomator.utils.WidgetUtils.safeFling;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.afwtest.common.Timer;
+import com.android.afwtest.common.test.OemWidget;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.utils.BySelectorHelper;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+/**
+ * A help class to skip pages until the expected page appears.
+ */
+public final class PageSkipper extends UiPage {
+
+ private static final String TAG = "afwtest.PageSkipper";
+
+ /**
+ * Thread sleep time.
+ */
+ private static final long THREAD_SLEEP_TIME_MS = TimeUnit.SECONDS.toMillis(3);
+
+ /**
+ * Words to match when finding available "NEXT" navigation buttons.
+ *
+ * The order of the list is preferring skip over continue or next.
+ */
+ private static final String[] NEXT_WORDS = {
+ "[sS]kip", "SKIP",
+ "[fF]inish", "FINISH",
+ "[dD]one", "DONE",
+ "[nN]ext", "NEXT",
+ "[cC]ontinue", "CONTINUE",
+ "[pP]roceed", "PROCEED",
+ "[yY][eE][sS]", "[oO][kK]"};
+
+ /**
+ * {@link Pattern} to match for NEXT buttons.
+ */
+ private static final Pattern NEXT_BTN_PATTERN =
+ Pattern.compile(TextUtils.join("|", NEXT_WORDS));
+
+ /**
+ * Buttons with text matching {@link #NEXT_BTN_PATTERN}.
+ */
+ private static final BySelector NAVIGATION_BTN_TEXT_SELECTOR =
+ By.enabled(true)
+ .checkable(false)
+ .clickable(true)
+ .text(NEXT_BTN_PATTERN);
+
+ /**
+ * Buttons with content description matching {@link #NEXT_BTN_PATTERN}.
+ */
+ private static final BySelector NAVIGATION_BTN_DESC_SELECTOR =
+ By.enabled(true)
+ .checkable(false)
+ .clickable(true)
+ .desc(NEXT_BTN_PATTERN);
+
+ /**
+ * List of package names whose buttons should not be clicked.
+ */
+ private Set<String> mPackageNameBlacklist = null;
+
+ /**
+ * Stop skipping when any object with this {@link BySelector} is found.
+ */
+ private final BySelector mSkipEndSelector;
+
+ /**
+ * Creates a new page skipper.
+ *
+ * @param uiDevice {@link UiDevice} object to access UIAutomator API
+ * @param skipEndSelector stop skipping pages if this any object matching this
+ * {@link BySelector} is found
+ * @param testConfig {@link testConfig} to get test configurations
+ */
+ public PageSkipper(UiDevice uiDevice, BySelector skipEndSelector, TestConfig testConfig) {
+ super(uiDevice, testConfig);
+
+ mSkipEndSelector = skipEndSelector;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean waitForLoading() throws Exception {
+ // Do nothing.
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ // No unique element.
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ long timeout = TimeUnit.MINUTES.toMillis(1) * getTestConfig().getTimeoutSize();
+ Timer timer = new Timer(timeout);
+ List<OemWidget> oemWidgets = getTestConfig().getOemWidgets();
+ timer.start();
+ while (!timer.isTimeUp() && !getUiDevice().hasObject(mSkipEndSelector)) {
+
+ if (!clickAvailableNextButton()) {
+ iterateOemWidgets(oemWidgets);
+ }
+
+ // Assert on fatal app crash
+ assertOnFatalAppCrash();
+
+ SystemClock.sleep(THREAD_SLEEP_TIME_MS);
+ }
+
+ if (timer.isTimeUp()) {
+ Log.e(TAG, "Timeout");
+ }
+ }
+
+ /**
+ * Finds buttons with text or content description that match pattern {@link NEXT_BTN_PATTERN}
+ * and click on them.
+ *
+ * @return {@code true} if any button found, {@code false} otherwise
+ */
+ private boolean clickAvailableNextButton() {
+ return clickAnyButton(getUiDevice().findObjects(NAVIGATION_BTN_TEXT_SELECTOR))
+ || clickAnyButton(getUiDevice().findObjects(NAVIGATION_BTN_DESC_SELECTOR));
+ }
+
+ /**
+ * Clicks any button from given list.
+ *
+ * @param buttons list of buttons to click
+ * @return {@code true} if there is any button clicked successfully, {@code false} otherwise
+ */
+ private boolean clickAnyButton(List<UiObject2> navigationBtns) {
+ for (UiObject2 obj : navigationBtns) {
+ // Skip widget with package name in mPackageNameBlacklist.
+ String pkgName = WidgetUtils.getPackageName(obj);
+ if (mPackageNameBlacklist != null && mPackageNameBlacklist.contains(pkgName)) {
+ continue;
+ }
+
+ if (safeClick(obj)) {
+ // Returns immediately after the first 'Next' button is found and clicked; because
+ // after clicking the current view hierarchy will change; the found navigation
+ // buttons will be stale.
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Iterates OEM widgets and act on found ones if necessary.
+ *
+ * @param oemWidgets {@link List} of {@link OemWidget} to operate
+ */
+ private void iterateOemWidgets(List<OemWidget> oemWidgets) {
+
+ for (OemWidget widget : oemWidgets) {
+ BySelector selector = BySelectorHelper.getSelector(widget);
+
+ if (getUiDevice().hasObject(selector)) {
+ Log.d(TAG, "Found OEM widget: " + widget.toString());
+
+ if (widget.getAction().equals(ACTION_CHECK)) {
+ safeClick(getUiDevice().findObject(selector));
+ } else if (widget.getAction().equals(ACTION_SCROLL)){
+ safeFling(
+ getUiDevice().findObject(selector),
+ Direction.valueOf(widget.getScrollDirection()));
+ } else if (widget.getAction().equals(ACTION_CLICK)) {
+ safeClick(getUiDevice().findObject(selector));
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets list of package names which should not be skipped.
+ *
+ * @param blacklist list of package names
+ */
+ public void setPackageNameBlacklist(Set<String> blacklist) {
+ mPackageNameBlacklist = blacklist;
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/UiPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/UiPage.java
new file mode 100644
index 0000000..f6b2532
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/UiPage.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.pages;
+
+import static com.android.afwtest.uiautomator.Constants.PROVISIONING_STATS_FILE;
+
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import com.android.afwtest.common.Timer;
+import com.android.afwtest.common.test.StatsLogger;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.test.AfwTestUiWatcher;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Abstract class that represents the screen view of the device.
+ */
+public abstract class UiPage {
+
+ private static final String TAG = "afwtest.UiPage";
+
+ /**
+ * Default waiting for a {@link UiPage}.
+ */
+ private static final long DEFAULT_LOAD_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(1);
+
+ /**
+ * Reference to {@link UiDevice} object.
+ */
+ private final UiDevice mUiDevice;
+
+ /**
+ * Test configuration.
+ */
+ private final TestConfig mTestConfig;
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public UiPage(UiDevice uiDevice, TestConfig config) {
+ mUiDevice = uiDevice;
+ mTestConfig = config;
+ }
+
+ /**
+ * Gets {@link UiDevice}.
+ *
+ * @return {@link UiDevice} object
+ */
+ protected UiDevice getUiDevice() {
+ return mUiDevice;
+ }
+
+ /**
+ * Gets test configuration.
+ *
+ * @return {@link TestConfig} object
+ */
+ protected TestConfig getTestConfig() {
+ return mTestConfig;
+ }
+
+ /**
+ * Gets the page loading timeout.
+ *
+ * @return page loading timeout in millisecond
+ */
+ protected long getLoadingTimeoutInMs() throws IOException {
+ return DEFAULT_LOAD_TIMEOUT_MS * mTestConfig.getTimeoutSize();
+ }
+
+ /**
+ * Gets if this page optional, e.g. might not appear in some flow.
+ *
+ * @return {@code true} if this page can be optional, {@code} false otherwise
+ */
+ public boolean isOptional() {
+ return false;
+ }
+
+ /**
+ * Waits for this page to load.
+ *
+ * @return {@code true} if this page is loaded successful, {@code false} otherwise
+ */
+ public boolean waitForLoading() throws Exception {
+ Timer timer = new Timer(getLoadingTimeoutInMs());
+ timer.start();
+ do {
+ if (WidgetUtils.safeWait(getUiDevice(), uniqueElement()) != null) {
+ return true;
+ }
+ assertOnFatalAppCrash();
+ } while (!timer.isTimeUp());
+
+ Log.e(TAG, String.format("UiPage.waitForLoading timeout(%sms).", getLoadingTimeoutInMs()));
+
+ return false;
+ }
+
+ /**
+ * Asserts if fatal app crash is found.
+ */
+ protected void assertOnFatalAppCrash() {
+ String appCrashMsg = AfwTestUiWatcher.getFatalAppCrashMsg();
+ if (appCrashMsg != null) {
+ throw new RuntimeException(appCrashMsg);
+ }
+ }
+
+ /**
+ * Gets provisioning stats logger.
+ *
+ * @return {@link StatsLogger} object
+ */
+ protected StatsLogger getProvisioningStatsLogger() throws IOException {
+ return StatsLogger.getInstance(PROVISIONING_STATS_FILE);
+ }
+
+ /**
+ * Gets unique element of this page described by {@link BySelector}.
+ *
+ * <p>The returned element is used to identify this page, such as to determine if this page
+ * is visible. For example, {@link waitForLoading} function is using the returned element
+ * of this function to determine if this page has successfully loaded.
+ * </p>
+ *
+ * @return unique element of this page described by {@link BySelector}
+ */
+ public abstract BySelector uniqueElement();
+
+ /**
+ * Navigates this page properly to next page.
+ *
+ * @throws Exception if navigation failed
+ */
+ public abstract void navigate() throws Exception;
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/AddAccountPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/AddAccountPage.java
new file mode 100644
index 0000000..427d435
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/AddAccountPage.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.pages.gms;
+
+import static com.android.afwtest.uiautomator.Constants.GMS_NEXT_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.GMS_PKG_NAME;
+import static com.android.afwtest.uiautomator.Constants.GMS_TEXT_FIELD_SELECTOR;
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.TextField;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+/**
+ * GMS Core add account page.
+ */
+public final class AddAccountPage extends UiPage {
+
+ /**
+ * @{@link BySelector} unique to the add or verify account page.
+ */
+ public static final BySelector GMS_ADD_OR_VERIFY_ACCOUNT_PAGE_SELECTOR =
+ By.pkg(GMS_PKG_NAME)
+ .desc(Pattern.compile("(Add|Verify) your account|Sign in", CASE_INSENSITIVE));
+
+ /**
+ * Default waiting time for widget, in milliseconds.
+ */
+ private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(20);
+
+ /**
+ * Username of the account to add.
+ */
+ private final String mUsername;
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public AddAccountPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ mUsername = config.getWorkAccountUsername();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ * @param customAccountKey property key of the account in {@link TestConfig}
+ */
+ public AddAccountPage(UiDevice uiDevice, TestConfig config, String customAccountKey) {
+ super(uiDevice, config);
+ mUsername = config.getProperty(customAccountKey);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected long getLoadingTimeoutInMs() {
+ // "Checking..." and "Loading" may take longer time than normal pages
+ return TimeUnit.MINUTES.toMillis(5);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return GMS_ADD_OR_VERIFY_ACCOUNT_PAGE_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ TextField.enterTextAndActivateNavigationBtn(
+ getUiDevice(),
+ GMS_TEXT_FIELD_SELECTOR,
+ mUsername,
+ GMS_NEXT_BUTTON_SELECTOR);
+
+ WidgetUtils.safeWaitAndClick(getUiDevice(), GMS_NEXT_BUTTON_SELECTOR, DEFAULT_TIMEOUT_MS);
+ WidgetUtils.waitToBeGone(getUiDevice(), GMS_ADD_OR_VERIFY_ACCOUNT_PAGE_SELECTOR);
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/BackUpToDrivePage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/BackUpToDrivePage.java
new file mode 100644
index 0000000..0ae2025
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/BackUpToDrivePage.java
@@ -0,0 +1,72 @@
+package com.android.afwtest.uiautomator.pages.gms;
+
+import static com.android.afwtest.uiautomator.Constants.GMS_BTN_WITH_TEXT_OK;
+import static com.android.afwtest.uiautomator.Constants.GMS_PKG_NAME_REGEX;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+
+/**
+ * Back up to Google Drive Page, optional.
+ */
+public final class BackUpToDrivePage extends UiPage {
+
+ /**
+ * Turn off back up to Google drive button.
+ */
+ private static final BySelector TURN_OFF_BACK_UP_BTN =
+ By.res(Pattern.compile(GMS_PKG_NAME_REGEX + ":id/backup_opt_in_disable_backup",
+ Pattern.CASE_INSENSITIVE));
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public BackUpToDrivePage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return TURN_OFF_BACK_UP_BTN;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected long getLoadingTimeoutInMs() {
+ return TimeUnit.MINUTES.toMillis(1);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isOptional() {
+ // This page is optional.
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ WidgetUtils.waitAndClick(getUiDevice(), TURN_OFF_BACK_UP_BTN);
+ WidgetUtils.waitToBeGone(getUiDevice(), TURN_OFF_BACK_UP_BTN);
+ WidgetUtils.waitAndClick(getUiDevice(), GMS_BTN_WITH_TEXT_OK);
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/EnterPasswordPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/EnterPasswordPage.java
new file mode 100644
index 0000000..96e7dcd
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/EnterPasswordPage.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.pages.gms;
+
+import static com.android.afwtest.uiautomator.Constants.GMS_NEXT_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.GMS_PKG_NAME;
+import static com.android.afwtest.uiautomator.Constants.GMS_TEXT_FIELD_SELECTOR;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * GMS Core auth enter password page.
+ */
+public final class EnterPasswordPage extends UiPage {
+
+ /**
+ * {@link BySelector} unique to this page.
+ */
+ private static final BySelector ENTER_PASSWORD_PAGE_SELECTOR =
+ By.pkg(GMS_PKG_NAME).desc("Forgot password?");
+
+ /**
+ * Password of the account to add.
+ */
+ private final String mPassword;
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public EnterPasswordPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ mPassword = config.getWorkAccountPassword();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ * @param customPasswordKey property key of the password in {@link TestConfig}
+ */
+ public EnterPasswordPage(UiDevice uiDevice, TestConfig config, String customPasswordKey) {
+ super(uiDevice, config);
+ mPassword = config.getProperty(customPasswordKey);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected long getLoadingTimeoutInMs() {
+ // Accessing Google server takes long time
+ return TimeUnit.MINUTES.toMillis(5);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return ENTER_PASSWORD_PAGE_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ WidgetUtils.waitAndClick(getUiDevice(),
+ GMS_TEXT_FIELD_SELECTOR,
+ TimeUnit.SECONDS.toMillis(5));
+ // Clicking the text field will bring up the keyboard and change the view hierachy of
+ // current screen; textField may become stale. Try to find it again before simulating
+ // the keyboard events.
+ SystemClock.sleep(TimeUnit.SECONDS.toMillis(5));
+
+ //Enter password by key event
+ KeyCharacterMap map = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ KeyEvent[] events = map.getEvents(mPassword.toCharArray());
+ for (KeyEvent event : events) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ getUiDevice().pressKeyCode(event.getKeyCode(), event.getMetaState());
+ }
+ }
+
+ WidgetUtils.waitAndClick(getUiDevice(), GMS_NEXT_BUTTON_SELECTOR);
+ // Wait until the next button is gone.
+ WidgetUtils.waitToBeGone(getUiDevice(), GMS_NEXT_BUTTON_SELECTOR);
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleAppsDevicePolicyPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleAppsDevicePolicyPage.java
new file mode 100644
index 0000000..b5f572c
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleAppsDevicePolicyPage.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.pages.gms;
+
+import static com.android.afwtest.uiautomator.Constants.GMS_DOWNLOADING_DIALOG_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.GMS_NEXT_BUTTON_RES_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.GMS_PKG_NAME_REGEX;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import com.android.afwtest.common.Timer;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+/**
+ * GMS Google Apps device policy page.
+ */
+public final class GoogleAppsDevicePolicyPage extends UiPage {
+
+ /**
+ * Timeout for starting downloading mdm.
+ */
+ private static final long START_DOWNLOAD_MDM_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
+
+ /**
+ * Timeout for downloading the dmd.
+ */
+ private static final long DOWNLOAD_MDM_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(1);
+
+ /**
+ * Number of attempts to start downloading MDM.
+ *
+ * <p>
+ * It's found on some devices it takes some time for the MDM app icon to load;
+ * and before the icon is loaded, clicking the Next button doesn't work.
+ * Add retry logic to keep clicking until the downloading starts.
+ * </p>
+ */
+ private static final int CLICK_NEXT_BTN_ATTEMPTS = 5;
+
+ /**
+ * {@link BySelector} for the MDM app icon.
+ */
+ private static final BySelector MDM_ICON_SELECTOR =
+ By.res(Pattern.compile(GMS_PKG_NAME_REGEX +
+ ":id/auth_device_management_download_app_icon"));
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public GoogleAppsDevicePolicyPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return MDM_ICON_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected long getLoadingTimeoutInMs() {
+ // Accessing Google server takes long time
+ return TimeUnit.MINUTES.toMillis(5);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ // Clicks the Next button until downloading starts.
+ int attempts = CLICK_NEXT_BTN_ATTEMPTS;
+ while (getUiDevice().hasObject(GMS_NEXT_BUTTON_RES_SELECTOR) && attempts > 0) {
+
+ --attempts;
+
+ // Clicks the next button to start downloading mdm
+ WidgetUtils.safeClick(getUiDevice().findObject(GMS_NEXT_BUTTON_RES_SELECTOR));
+
+ // Wait for downloading mdm to appear
+ if (WidgetUtils.safeWait(getUiDevice(),
+ GMS_DOWNLOADING_DIALOG_SELECTOR, START_DOWNLOAD_MDM_TIMEOUT_MS) != null) {
+ break;
+ }
+ }
+
+ // Waits until the download finishes
+ Timer timer = new Timer(DOWNLOAD_MDM_TIMEOUT_MS);
+ timer.start();
+ while (!timer.isTimeUp() && getUiDevice().hasObject(GMS_DOWNLOADING_DIALOG_SELECTOR)) {
+ SystemClock.sleep(TimeUnit.SECONDS.toMillis(2));
+ }
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleServicesPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleServicesPage.java
new file mode 100644
index 0000000..f3cb453
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleServicesPage.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.pages.gms;
+
+import static com.android.afwtest.uiautomator.Constants.GMS_PKG_NAME_REGEX;
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.test.AfwTestUiWatcher;
+import com.android.afwtest.uiautomator.utils.Device;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+/**
+ * GMS Core Google Services page.
+ */
+public final class GoogleServicesPage extends UiPage {
+
+ /**
+ * {@link BySelector} unique to this page.
+ */
+ private static final BySelector GOOGLE_SERVICES_PAGE_SELECTOR =
+ By.pkg(Pattern.compile(GMS_PKG_NAME_REGEX)).text("Google services");
+
+ private static final BySelector GMS_BTN_WITH_TEXT_AGREE =
+ By.pkg(Pattern.compile(GMS_PKG_NAME_REGEX))
+ .text(Pattern.compile("agree", CASE_INSENSITIVE));
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public GoogleServicesPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected long getLoadingTimeoutInMs() {
+ // Accessing Google server takes long time
+ return TimeUnit.MINUTES.toMillis(5);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isOptional() {
+ // This page is optional.
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return GOOGLE_SERVICES_PAGE_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ // Otherwise "Agree" will be clicked automatically.
+ AfwTestUiWatcher.disallowPageSkipping();
+
+ // Scroll up until "more" button becomes "agree".
+ for (int i = 0; i < 5; ++i) {
+ Device.swipeUp(getUiDevice());
+ if (WidgetUtils.safeWait(getUiDevice(), GMS_BTN_WITH_TEXT_AGREE,
+ TimeUnit.SECONDS.toMillis(3)) != null) {
+ break;
+ }
+ }
+
+ if (!WidgetUtils.safeWaitAndClick(getUiDevice(), GMS_BTN_WITH_TEXT_AGREE)) {
+ throw new Exception("Failed to click Agree button");
+ }
+
+ AfwTestUiWatcher.allowPageSkipping();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean waitForLoading() throws Exception {
+ AfwTestUiWatcher.disallowPageSkipping();
+ boolean result = super.waitForLoading();
+ AfwTestUiWatcher.allowPageSkipping();
+ return result;
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/BasePage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/BasePage.java
new file mode 100644
index 0000000..1e249ce
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/BasePage.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.pages.managedprovisioning;
+
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_ERROR_MSG_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_OK_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_NAME;
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_SELECTOR;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.Timer;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Base class for all Managed Provisioning pages to keep common functionality in one place.
+ */
+public abstract class BasePage extends UiPage {
+
+ /**
+ * Thread sleep time, in milliseconds.
+ */
+ protected static final long THREAD_SLEEP_TIME_MS = TimeUnit.SECONDS.toMillis(1);
+
+ private static final BySelector LEARN_MORE_TEXT_1 =
+ By.res(MANAGED_PROVISIONING_PKG_NAME, "learn_more_text1");
+
+ /**
+ * Default provisioning timeout in milliseconds.
+ */
+ private static final long DEFAULT_PROVISIONING_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(1);
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public BasePage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * Gets provisioning error message.
+ *
+ * @return Error message if provisioning is showing error dialog, {@code null} otherwise
+ */
+ protected String getProvisioningError() {
+ // Get any managed provisioning error dialog
+ if (getUiDevice().hasObject(MANAGED_PROVISIONING_ERROR_MSG_SELECTOR)) {
+ return getUiDevice().findObject(MANAGED_PROVISIONING_ERROR_MSG_SELECTOR).getText();
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the provisioning timeout in millisecond.
+ *
+ * @return provisioning timeout in millisecond
+ */
+ protected long getProvisioningTimeoutInMs() throws IOException {
+ return DEFAULT_PROVISIONING_TIMEOUT_MS * getTestConfig().getTimeoutSize();
+ }
+
+ /**
+ * Waits for the provisioning to finish.
+ *
+ * @param timeout Waiting timeout in milliseconds
+ * @return {@code true} if the provisioning is finished, {@code false} otherwise
+ */
+ protected boolean waitForProvisioningToFinish() throws Exception {
+ Timer timer = new Timer(getProvisioningTimeoutInMs());
+ timer.start();
+ while (!timer.isTimeUp()) {
+ if (WidgetUtils.safeWait(getUiDevice(), MANAGED_PROVISIONING_PKG_SELECTOR) == null) {
+ assertOnFatalAppCrash();
+ return true;
+ }
+
+ // Dismiss the dialog with: "Your administrator has the ability to monitor...".
+ if (getUiDevice().hasObject(LEARN_MORE_TEXT_1)) {
+ getUiDevice().findObject(MANAGED_PROVISIONING_OK_BUTTON_SELECTOR).click();
+ SystemClock.sleep(THREAD_SLEEP_TIME_MS);
+ continue;
+ }
+
+ String errorMsg = getProvisioningError();
+ if (errorMsg != null) {
+ throw new RuntimeException("Provisioning error: " + errorMsg);
+ }
+
+ assertOnFatalAppCrash();
+
+ SystemClock.sleep(THREAD_SLEEP_TIME_MS);
+ }
+
+ return false;
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/ErrorPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/ErrorPage.java
new file mode 100644
index 0000000..7fc55f7
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/ErrorPage.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.pages.managedprovisioning;
+
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_NAME;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+/**
+ * A {@link UiPage} representing error dialog popped up from Managed Provisioning.
+ */
+public class ErrorPage extends BasePage {
+
+ /**
+ * Default UI waiting time, in milliseconds.
+ */
+ private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
+
+ private static final BySelector ERROR_PAGE_SELECTOR =
+ By.pkg(MANAGED_PROVISIONING_PKG_NAME).text("Can't set up device");
+
+ private static final BySelector OK_BUTTON_SELECTOR =
+ By.pkg(MANAGED_PROVISIONING_PKG_NAME)
+ .text(Pattern.compile("[Oo][Kk]"))
+ .clickable(true);
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public ErrorPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return ERROR_PAGE_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ WidgetUtils.waitAndClick(getUiDevice(), OK_BUTTON_SELECTOR, DEFAULT_TIMEOUT_MS);
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourDevicePage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourDevicePage.java
new file mode 100644
index 0000000..d10e281
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourDevicePage.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.pages.managedprovisioning;
+
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_NAME;
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_SCROLL_VIEW_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_SETUP_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.STAT_TESTDPC_WORK_PROFILE_CREATION_TIME;
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.widget.CheckBox;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+/**
+ * Managed Provisioning setup your device page.
+ */
+public class SetupYourDevicePage extends BasePage {
+
+ /**
+ * Default UI waiting time, in milliseconds.
+ */
+ private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(20);
+
+ /**
+ * {@link BySelector} unique to this page.
+ */
+ private static final BySelector SET_UP_YOUR_DEVICE_PAGE_SELECTOR =
+ By.pkg(MANAGED_PROVISIONING_PKG_NAME)
+ .text(Pattern.compile("Set up .* device", CASE_INSENSITIVE));
+
+ private static final BySelector LEARN_MORE_TEXT_1 =
+ By.res(MANAGED_PROVISIONING_PKG_NAME, "learn_more_text1");
+
+ /**
+ * {@link BySelector} for {@link CheckBox} with resource-id user_consent_checkbox on
+ * base ManagedProvisioning dialog.
+ */
+ private static final BySelector MANAGED_PROVISIONING_CONSENT_CHECKBOX_SELECTOR =
+ By.res(MANAGED_PROVISIONING_PKG_NAME, "user_consent_checkbox")
+ .clazz(CheckBox.class.getName())
+ .checkable(true);
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public SetupYourDevicePage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return SET_UP_YOUR_DEVICE_PAGE_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ WidgetUtils.waitAndClick(getUiDevice(), MANAGED_PROVISIONING_SETUP_BUTTON_SELECTOR,
+ DEFAULT_TIMEOUT_MS);
+ if (null != WidgetUtils.safeWait(getUiDevice(), LEARN_MORE_TEXT_1)) {
+ WidgetUtils.scrollToItem(getUiDevice(),
+ MANAGED_PROVISIONING_SCROLL_VIEW_SELECTOR,
+ MANAGED_PROVISIONING_CONSENT_CHECKBOX_SELECTOR);
+ WidgetUtils.waitAndClick(getUiDevice(),
+ MANAGED_PROVISIONING_CONSENT_CHECKBOX_SELECTOR,
+ DEFAULT_TIMEOUT_MS);
+ }
+
+ onProvisioningStarted();
+
+ // Wait for the provisioning to finish.
+ if (!waitForProvisioningToFinish()) {
+ throw new RuntimeException("DO Provisioning timeout");
+ }
+ }
+
+ /**
+ * Handles provisioning started event.
+ */
+ protected void onProvisioningStarted() throws Exception {
+ // Default to be TestDpc provisioning
+ getProvisioningStatsLogger().startTime(STAT_TESTDPC_WORK_PROFILE_CREATION_TIME);
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/TermsPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/TermsPage.java
new file mode 100644
index 0000000..d595499
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/TermsPage.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.pages.managedprovisioning;
+
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_NAME;
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_SETUP_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.STAT_TESTDPC_WORK_PROFILE_CREATION_TIME;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+/**
+ * Managed Provisioning terms and conditions page.
+ */
+public class TermsPage extends BasePage {
+
+ private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(20);
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public TermsPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return MANAGED_PROVISIONING_SETUP_BUTTON_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ WidgetUtils.waitAndClick(getUiDevice(), MANAGED_PROVISIONING_SETUP_BUTTON_SELECTOR,
+ DEFAULT_TIMEOUT_MS);
+
+ onProvisioningStarted();
+
+ // Wait for the provisioning to finish.
+ if (!waitForProvisioningToFinish()) {
+ throw new RuntimeException("PO Provisioning timeout");
+ }
+ }
+
+ /**
+ * Handles provisioning started event.
+ */
+ protected void onProvisioningStarted() throws Exception {
+ // Default to be TestDpc provisioning
+ getProvisioningStatsLogger().startTime(STAT_TESTDPC_WORK_PROFILE_CREATION_TIME);
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/packageinstaller/DeviceAccessPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/packageinstaller/DeviceAccessPage.java
new file mode 100644
index 0000000..3c1f930
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/packageinstaller/DeviceAccessPage.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.pages.packageinstaller;
+
+import static com.android.afwtest.uiautomator.Constants.PACKAGE_INSTALLER_INSTALL_BUTTON_SELECTOR;
+
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+/**
+ * Package Installer device access page.
+ */
+public final class DeviceAccessPage extends UiPage {
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public DeviceAccessPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return PACKAGE_INSTALLER_INSTALL_BUTTON_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ WidgetUtils.waitAndClick(getUiDevice(), PACKAGE_INSTALLER_INSTALL_BUTTON_SELECTOR);
+ }
+}
+
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/FinishSetupPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/FinishSetupPage.java
new file mode 100644
index 0000000..4b266a1
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/FinishSetupPage.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.pages.testdpc;
+
+import static com.android.afwtest.uiautomator.Constants.STAT_TESTDPC_WORK_PROFILE_CREATION_TIME;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_NEXT_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_PKG_NAME;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.widget.Button;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.util.regex.Pattern;
+
+/**
+ * Finish Setup page, which should be launched after DO or PO provisioning is successful.
+ */
+public final class FinishSetupPage extends UiPage {
+
+ /**
+ * Flag indicating if account was migrated.
+ */
+ private final boolean mIsAccountMigrated;
+
+ /**
+ * {@link BySelector} for TestDpc SuW layout title "Finish setup".
+ */
+ private static final BySelector TESTDPC_FINISH_SETUP_PAGE_FINISH_BUTTON_SELECTOR =
+ By.pkg(TESTDPC_PKG_NAME)
+ .clazz(Pattern.compile(Button.class.getName() + "|" + View.class.getName()))
+ .text("FINISH")
+ .enabled(true);
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ * @param isAccountMigrated whether account was migrated
+ */
+ public FinishSetupPage(UiDevice uiDevice, TestConfig config, boolean isAccountMigrated) {
+ super(uiDevice, config);
+ mIsAccountMigrated = isAccountMigrated;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public FinishSetupPage(UiDevice uiDevice, TestConfig config) {
+ this(uiDevice, config, true);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return TESTDPC_FINISH_SETUP_PAGE_FINISH_BUTTON_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ if (mIsAccountMigrated) {
+ // Find message that account migration was successful.
+ BySelector succeedResult =
+ By.pkg(TESTDPC_PKG_NAME).text("Added account that is now managed:");
+
+ // Find managed work account name to verify required account is migrated.
+ BySelector managedAccount =
+ By.res(TESTDPC_PKG_NAME, "managed_account_name")
+ .text(getTestConfig().getWorkAccountUsername().toLowerCase());
+
+ if (WidgetUtils.safeWait(getUiDevice(), succeedResult) == null ||
+ WidgetUtils.safeWait(getUiDevice(), managedAccount) == null) {
+ throw new RuntimeException(String.format(
+ "Provisioning failed: %s is not setup to be managed by TestDpc!",
+ getTestConfig().getWorkAccountUsername()));
+ }
+
+ // Find page title to verify its text displays successful setup
+ BySelector titleSelector = By.res(TESTDPC_PKG_NAME, "suw_layout_title");
+ UiObject2 titleWidget = WidgetUtils.safeWait(getUiDevice(), titleSelector);
+ if (titleWidget == null) {
+ throw new RuntimeException("Provisioning failed: Could not find page title!");
+ }
+ if (!titleWidget.getText().equals("Finish setup")) {
+ throw new RuntimeException(
+ String.format(
+ "Provisioning failed: Unexpected page title: %s",
+ titleWidget.getText()
+ ));
+ }
+
+ // Save Time metric
+ getProvisioningStatsLogger().stopTime(STAT_TESTDPC_WORK_PROFILE_CREATION_TIME);
+ getProvisioningStatsLogger().writeStatsToFile();
+
+ } else {
+ BySelector succeedResult = By.pkg(TESTDPC_PKG_NAME)
+ .text("To manage the new managed profile, visit the badged version of this");
+ WidgetUtils.safeWait(getUiDevice(), succeedResult);
+ }
+
+ WidgetUtils.waitAndClick(getUiDevice(), TESTDPC_NEXT_BUTTON_SELECTOR);
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishedPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishedPage.java
new file mode 100644
index 0000000..e347dfa
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishedPage.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017 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.afwtest.uiautomator.pages.testdpc;
+
+import static com.android.afwtest.uiautomator.Constants.STAT_TESTDPC_WORK_PROFILE_CREATION_TIME;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_NEXT_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_PKG_NAME;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.widget.RadioButton;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Setup finished page, with 3 options: add account, add account with name, and Skip.
+ */
+public final class SetupFinishedPage extends UiPage {
+ /** Options for next step.*/
+ public enum OPTION {
+ DO_NOTHING,
+ ADD_ACCOUNT,
+ ADD_ACCOUNT_WITH_NAME,
+ SKIP,
+ };
+
+ /**
+ * {@link BySelector} unique to this page.
+ */
+ private static final BySelector TESTDPC_SETUP_FINISHED_PAGE_SELECTOR =
+ By.res(TESTDPC_PKG_NAME, "suw_layout_title")
+ .text("Setup finished");
+
+ /**
+ * {@link BySelector} for {@link RadioButton} with resource-id add_account on
+ * TestDpc Setup finished page.
+ */
+ private static final BySelector ADD_ACCOUNT_RADIO_BUTTON =
+ By.res(TESTDPC_PKG_NAME, "add_account")
+ .clazz(RadioButton.class)
+ .clickable(true)
+ .checkable(true);
+
+ /**
+ * {@link BySelector} for {@link RadioButton} with resource-id add_account_with_name on
+ * TestDpc Setup finished page.
+ */
+ private static final BySelector ADD_ACCOUNT_WITH_NAME_RADIO_BUTTON =
+ By.res(TESTDPC_PKG_NAME, "add_account_with_name")
+ .clazz(RadioButton.class)
+ .clickable(true)
+ .checkable(true);
+
+ /**
+ * {@link BySelector} for {@link RadioButton} with resource-id add_account_with_name on
+ * TestDpc Setup finished page.
+ */
+ private static final BySelector ADD_ACCOUNT_SKIP_RADIO_BUTTON =
+ By.res(TESTDPC_PKG_NAME, "add_account_skip")
+ .clazz(RadioButton.class)
+ .clickable(true)
+ .checkable(true);
+
+ /** Option for next step. */
+ private final OPTION mNextStep;
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public SetupFinishedPage(UiDevice uiDevice, TestConfig config, OPTION nextStep) {
+ super(uiDevice, config);
+ mNextStep = nextStep;
+ }
+
+ @Override
+ public BySelector uniqueElement() {
+ return TESTDPC_SETUP_FINISHED_PAGE_SELECTOR;
+ }
+
+ @Override
+ public void navigate() throws Exception {
+ getProvisioningStatsLogger().stopTime(STAT_TESTDPC_WORK_PROFILE_CREATION_TIME);
+ getProvisioningStatsLogger().writeStatsToFile();
+
+ switch(mNextStep) {
+ case ADD_ACCOUNT:
+ WidgetUtils.waitAndClick(getUiDevice(), ADD_ACCOUNT_RADIO_BUTTON);
+ break;
+ case ADD_ACCOUNT_WITH_NAME:
+ WidgetUtils.waitAndClick(getUiDevice(), ADD_ACCOUNT_WITH_NAME_RADIO_BUTTON);
+ break;
+ case SKIP:
+ WidgetUtils.waitAndClick(getUiDevice(), ADD_ACCOUNT_SKIP_RADIO_BUTTON);
+ break;
+ }
+ if (!mNextStep.equals(OPTION.DO_NOTHING)) {
+ WidgetUtils.waitAndClick(getUiDevice(), TESTDPC_NEXT_BUTTON_SELECTOR);
+ }
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementDoPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementDoPage.java
new file mode 100644
index 0000000..6aeb2e1
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementDoPage.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.pages.testdpc;
+
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_NEXT_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_SCROLL_VIEW_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_SETUP_DEVICE_OWNER_RADIO_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_SETUP_MANAGEMENT_PAGE_TITLE_SELECTOR;
+
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+/**
+ * TestDpc Setup Management page during DO provisioning.
+ */
+public final class SetupManagementDoPage extends UiPage {
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public SetupManagementDoPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return TESTDPC_SETUP_MANAGEMENT_PAGE_TITLE_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ WidgetUtils.scrollToItem(getUiDevice(),
+ TESTDPC_SCROLL_VIEW_SELECTOR,
+ TESTDPC_SETUP_DEVICE_OWNER_RADIO_BUTTON_SELECTOR);
+ WidgetUtils.waitAndClick(getUiDevice(),
+ TESTDPC_SETUP_DEVICE_OWNER_RADIO_BUTTON_SELECTOR);
+ WidgetUtils.waitAndClick(getUiDevice(), TESTDPC_NEXT_BUTTON_SELECTOR);
+ }
+} \ No newline at end of file
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementPoPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementPoPage.java
new file mode 100644
index 0000000..468016f
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementPoPage.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.pages.testdpc;
+
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_NEXT_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_SCROLL_VIEW_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_SETUP_DEVICE_OWNER_RADIO_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_SETUP_MANAGED_PROFILE_RADIO_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_SETUP_MANAGEMENT_PAGE_TITLE_SELECTOR;
+
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+/**
+ * TestDpc Setup Management page during PO provisioning.
+ */
+public final class SetupManagementPoPage extends UiPage {
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public SetupManagementPoPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return TESTDPC_SETUP_MANAGEMENT_PAGE_TITLE_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ WidgetUtils.scrollToItem(getUiDevice(),
+ TESTDPC_SCROLL_VIEW_SELECTOR,
+ TESTDPC_SETUP_DEVICE_OWNER_RADIO_BUTTON_SELECTOR);
+ WidgetUtils.waitAndClick(getUiDevice(),
+ TESTDPC_SETUP_MANAGED_PROFILE_RADIO_BUTTON_SELECTOR);
+ WidgetUtils.waitAndClick(getUiDevice(), TESTDPC_NEXT_BUTTON_SELECTOR);
+ }
+} \ No newline at end of file
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/provisioning/AutomationDriver.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/provisioning/AutomationDriver.java
new file mode 100644
index 0000000..629c262
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/provisioning/AutomationDriver.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.provisioning;
+
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_NAME;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_PKG_NAME;
+import static com.android.afwtest.uiautomator.pages.gms.AddAccountPage.GMS_ADD_OR_VERIFY_ACCOUNT_PAGE_SELECTOR;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.LandingPage;
+import com.android.afwtest.uiautomator.pages.PageSkipper;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.pages.gms.AddAccountPage;
+import com.android.afwtest.uiautomator.pages.gms.EnterPasswordPage;
+import com.android.afwtest.uiautomator.pages.gms.GoogleAppsDevicePolicyPage;
+import com.android.afwtest.uiautomator.pages.gms.GoogleServicesPage;
+import com.android.afwtest.uiautomator.pages.managedprovisioning.ErrorPage;
+import com.android.afwtest.uiautomator.pages.managedprovisioning.SetupYourDevicePage;
+import com.android.afwtest.uiautomator.pages.managedprovisioning.TermsPage;
+import com.android.afwtest.uiautomator.pages.packageinstaller.DeviceAccessPage;
+import com.android.afwtest.uiautomator.pages.testdpc.FinishSetupPage;
+import com.android.afwtest.uiautomator.pages.testdpc.SetupManagementDoPage;
+import com.android.afwtest.uiautomator.pages.testdpc.SetupManagementPoPage;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A help class that automates provisioning flows.
+ */
+public class AutomationDriver {
+
+ private static final String TAG = "afwtest.AutomationDriver";
+
+ /**
+ * {@link UiDevice} object.
+ */
+ private final UiDevice mUiDevice;
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ */
+ public AutomationDriver(UiDevice uiDevice) {
+ mUiDevice = uiDevice;
+ }
+
+ /**
+ * Gets {@link UiDevice} instance.
+ *
+ * @return {@link UiDevice} instances
+ */
+ protected UiDevice getUiDevice() {
+ return mUiDevice;
+ }
+
+ /**
+ * Runs the NFC provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return {@code true} if provisioning completes, {@code false} otherwise
+ */
+ public boolean runNfcProvisioning(TestConfig testConfig) throws Exception {
+ return navigate(getNfcProvisioningPages(testConfig));
+ }
+
+ /**
+ * Runs the QR Code provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return {@code true} if provisioning completes, {@code false} otherwise
+ */
+ public boolean runQRCodeProvisioning(TestConfig testConfig) throws Exception {
+ return navigate(getQRCodeProvisioningPages(testConfig));
+ }
+
+ /**
+ * Runs the setup wizard device owner provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return {@code true} if provisioning completes, {@code false} otherwise
+ */
+ public boolean runSuwDoProvisioning(TestConfig testConfig) throws Exception {
+ return navigate(getSuwDoProvisioningPages(testConfig));
+ }
+
+ /**
+ * Runs the setup wizard profile owner provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return {@code true} if provisioning completes, {@code false} otherwise
+ */
+ public boolean runSuwPoProvisioning(TestConfig testConfig) throws Exception {
+ return navigate(getSuwPoProvisioningPages(testConfig));
+ }
+
+ /**
+ * Runs the non setup wizard profile owner provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return {@code true} if provisioning completes, {@code false} otherwise
+ */
+ public boolean runNonSuwPoProvisioning(TestConfig testConfig) throws Exception {
+ return navigate(getNonSuwPoProvisioningPages(testConfig));
+ }
+
+ /**
+ * Runs the non setup wizard profile owner provisioning flow when it is not allowed.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return {@code true} if provisioning completes, {@code false} otherwise
+ */
+ public boolean runNonSuwPoProvisioningDisallowed(TestConfig testConfig) throws Exception {
+ return navigate(getNonSuwPoProvisioningDisallowedPages(testConfig));
+ }
+
+ /**
+ * Navigates a list of {@link UiPage} one by one. Optional pages will be skipped if not found.
+ *
+ * @param pages list of {@link UiPage} to navigate
+ *
+ * @return {@code true} if all pages are navigated successfully, {@code false} otherwise
+ */
+ protected boolean navigate(List<UiPage> pages) throws Exception {
+ for (UiPage page : pages) {
+ if (page.waitForLoading()) {
+ Log.i(TAG, String.format("Navigating: %s", page.getClass().getName()));
+ page.navigate();
+ Log.i(TAG, String.format("Navigating: %s done", page.getClass().getName()));
+ } else if (page.isOptional()) {
+ Log.i(TAG, String.format("Failed to load page: %s; but it's optional, skipping",
+ page.getClass().getName()));
+ } else {
+ throw new Exception(
+ String.format("Failed to load page: %s", page.getClass().getName()));
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets the list of {@link UiPage} for NFC provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return list of {@link UiPage} representing the setup wizard DO provisioning flow
+ */
+ private List<UiPage> getNfcProvisioningPages(TestConfig testConfig) {
+ List<UiPage> pages = new LinkedList<>();
+ pages.add(new SetupYourDevicePage(mUiDevice, testConfig));
+
+ // Land on the mdm page.
+ UiPage mdmPage =
+ new LandingPage(mUiDevice, testConfig, By.pkg(testConfig.getDeviceAdminPkgName()));
+ PageSkipper pageSkipper = new PageSkipper(mUiDevice, mdmPage.uniqueElement(), testConfig);
+
+ pages.add(pageSkipper);
+ pages.add(mdmPage);
+
+ // Setup page skipper.
+ Set<String> packageNameBlacklist = new HashSet<String>();
+ packageNameBlacklist.add(MANAGED_PROVISIONING_PKG_NAME);
+ packageNameBlacklist.add(testConfig.getDeviceAdminPkgName());
+ pageSkipper.setPackageNameBlacklist(packageNameBlacklist);
+
+ return pages;
+ }
+
+ /**
+ * Gets the list of {@link UiPage} for QR code provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return list of {@link UiPage} representing the setup wizard DO provisioning flow
+ */
+ private List<UiPage> getQRCodeProvisioningPages(TestConfig testConfig) {
+ List<UiPage> pages = new LinkedList<>();
+ pages.add(new SetupYourDevicePage(mUiDevice, testConfig));
+
+ // Land on the mdm page.
+ UiPage mdmPage =
+ new LandingPage(mUiDevice, testConfig, By.pkg(testConfig.getDeviceAdminPkgName()));
+ PageSkipper pageSkipper = new PageSkipper(mUiDevice, mdmPage.uniqueElement(), testConfig);
+
+ pages.add(pageSkipper);
+ pages.add(mdmPage);
+
+ // Setup page skipper.
+ Set<String> packageNameBlacklist = new HashSet<String>();
+ packageNameBlacklist.add(MANAGED_PROVISIONING_PKG_NAME);
+ packageNameBlacklist.add(testConfig.getDeviceAdminPkgName());
+ pageSkipper.setPackageNameBlacklist(packageNameBlacklist);
+
+ return pages;
+ }
+
+ /**
+ * Gets the list of {@link UiPage} for setup wizard DO provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return list of {@link UiPage} representing the setup wizard DO provisioning flow
+ */
+ private List<UiPage> getSuwDoProvisioningPages(TestConfig testConfig) {
+ List<UiPage> pages = new LinkedList<>();
+ pages.add(new AddAccountPage(mUiDevice, testConfig));
+ pages.add(new EnterPasswordPage(mUiDevice, testConfig));
+ pages.add(new GoogleServicesPage(mUiDevice, testConfig));
+ pages.add(new GoogleAppsDevicePolicyPage(mUiDevice, testConfig));
+ pages.add(new DeviceAccessPage(mUiDevice, testConfig));
+ pages.add(new SetupManagementDoPage(mUiDevice, testConfig));
+ pages.add(new SetupYourDevicePage(mUiDevice, testConfig));
+
+ UiPage landingPage = new FinishSetupPage(mUiDevice, testConfig);
+ PageSkipper pageSkipper =
+ new PageSkipper(mUiDevice, landingPage.uniqueElement(), testConfig);
+ pages.add(pageSkipper);
+ pages.add(landingPage);
+
+ // Setup page skipper.
+ Set<String> packageNameBlacklist = new HashSet<String>();
+ packageNameBlacklist.add(MANAGED_PROVISIONING_PKG_NAME);
+ packageNameBlacklist.add(TESTDPC_PKG_NAME);
+ pageSkipper.setPackageNameBlacklist(packageNameBlacklist);
+
+ return pages;
+ }
+
+ /**
+ * Gets the list of {@link UiPage} for setup wizard PO provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return list of {@link UiPage} representing the setup wizard PO provisioning flow
+ */
+ private List<UiPage> getSuwPoProvisioningPages(TestConfig testConfig) {
+ List<UiPage> pages = new LinkedList<>();
+ pages.add(getToAddAccountSkipper(testConfig));
+ pages.add(new AddAccountPage(mUiDevice, testConfig));
+ pages.add(new EnterPasswordPage(mUiDevice, testConfig));
+ pages.add(new GoogleServicesPage(mUiDevice, testConfig));
+ pages.add(new GoogleAppsDevicePolicyPage(mUiDevice, testConfig));
+ pages.add(new DeviceAccessPage(mUiDevice, testConfig));
+ pages.add(new SetupManagementPoPage(mUiDevice, testConfig));
+ pages.add(new TermsPage(mUiDevice, testConfig));
+
+ UiPage landingPage = new FinishSetupPage(mUiDevice, testConfig);
+ PageSkipper pageSkipper =
+ new PageSkipper(mUiDevice, landingPage.uniqueElement(), testConfig);
+ pages.add(pageSkipper);
+ pages.add(landingPage);
+
+ // Setup page skipper.
+ Set<String> packageNameBlacklist = new HashSet<String>();
+ packageNameBlacklist.add(MANAGED_PROVISIONING_PKG_NAME);
+ packageNameBlacklist.add(TESTDPC_PKG_NAME);
+ pageSkipper.setPackageNameBlacklist(packageNameBlacklist);
+
+ return pages;
+ }
+
+ /**
+ * Gets {@link PageSkipper} to go through Setup Wizard to reach {@link AddAccountPage}.
+ *
+ * @param testConfig test configurations required for provisioning automation.
+ * @return {@link PageSkipper} to go through Setup Wizard to reach {@link AddAccountPage}.
+ */
+ private UiPage getToAddAccountSkipper(TestConfig testConfig) {
+ return new PageSkipper(mUiDevice, GMS_ADD_OR_VERIFY_ACCOUNT_PAGE_SELECTOR, testConfig);
+ }
+
+ /**
+ * Gets the list of {@link UiPage} for non setup wizard PO provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return list of {@link UiPage} representing the non setup wizard PO provisioning flow
+ */
+ private List<UiPage> getNonSuwPoProvisioningPages(TestConfig testConfig) {
+ List<UiPage> pages = new LinkedList<>();
+ pages.add(new AddAccountPage(mUiDevice, testConfig));
+ pages.add(new EnterPasswordPage(mUiDevice, testConfig));
+ pages.add(new GoogleServicesPage(mUiDevice, testConfig));
+ pages.add(new GoogleAppsDevicePolicyPage(mUiDevice, testConfig));
+ pages.add(new DeviceAccessPage(mUiDevice, testConfig));
+ pages.add(new SetupManagementPoPage(mUiDevice, testConfig));
+ pages.add(new TermsPage(mUiDevice, testConfig));
+ pages.add(new FinishSetupPage(mUiDevice, testConfig));
+ return pages;
+ }
+
+ /**
+ * Gets the list of {@link UiPage} for testing disallowed provisioning test.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return list of {@link UiPage} for testing disallowed provisioning test.
+ */
+ private List<UiPage> getNonSuwPoProvisioningDisallowedPages(TestConfig testConfig) {
+ List<UiPage> pages = new LinkedList<>();
+ pages.add(new ErrorPage(mUiDevice, testConfig));
+ return pages;
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/test/AbstractTestCase.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/test/AbstractTestCase.java
new file mode 100644
index 0000000..2b4a58f
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/test/AbstractTestCase.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.test;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+
+/**
+ * Abstract class for test cases using android-support-test.
+ *
+ * Provides common methods that are likely to be needed in test cases.
+ */
+public abstract class AbstractTestCase {
+
+ /**
+ * Gets application context.
+ *
+ * @return application context
+ */
+ protected Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ /**
+ * Get current instance of {@link UiDevice}.
+ *
+ * @return current {@link UiDevice}
+ */
+ protected UiDevice getUiDevice() {
+ return UiDevice.getInstance(getInstrumentation());
+ }
+
+ /**
+ * Gets current instance of {@link Instrumentation}.
+ *
+ * @return current {@link Instrumentation}
+ */
+ public Instrumentation getInstrumentation() {
+ return InstrumentationRegistry.getInstrumentation();
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/test/AfwTestUiWatcher.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/test/AfwTestUiWatcher.java
new file mode 100644
index 0000000..4e3cbe3
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/test/AfwTestUiWatcher.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.test;
+
+import static com.android.afwtest.common.Constants.KEY_MUTE_APP_CRASH_DIALOGS;
+import static com.android.afwtest.uiautomator.Constants.ANDROID_PKG_NAME;
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_NAME;
+import static com.android.afwtest.uiautomator.utils.WidgetUtils.safeClick;
+import static com.android.afwtest.uiautomator.utils.WidgetUtils.safeClickAny;
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiWatcher;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.afwtest.common.test.TestConfig;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * {@link UiWatcher} to handle common unexpected scenarios.
+ *
+ * <p>
+ * This is a singleton class. There can be only one
+ * </p>
+ */
+public final class AfwTestUiWatcher implements UiWatcher {
+
+ private static final String TAG = "afwtest.AfwTestUiWatcher";
+
+ /**
+ * Unique name of this ui watcher.
+ */
+ private static final String UI_WATCHER_NAME = "afw-test-uiwatcher";
+
+ /**
+ * Regex string for app crashed message.
+ */
+ private static final String APP_STOPPED_MSG_REGEX = ".*has stopped.*";
+
+ /**
+ * {@link BySelector} for app stopped dialog title.
+ */
+ private static final BySelector APP_STOPPED_MSG_SELECTOR =
+ By.res(ANDROID_PKG_NAME, "alertTitle");
+
+ /**
+ * {@link BySelector} for mute option on app stop dialog.
+ */
+ private static final BySelector APP_STOPPED_DIALOG_MUTE_SELECTOR =
+ By.res(Pattern.compile(ANDROID_PKG_NAME + ":id/(aerr_mute|aerr_close)"));
+
+ /**
+ * Words to match for "Accept" button's text or description.
+ */
+ private static final String[] ACCEPT_WORDS = {
+ "accept", "agree", "allow", "I agree", "yes"};
+
+ /**
+ * {@link Pattern} to match any "Accept" word in {@link #ACCEPT_WORDS}.
+ */
+ private static final Pattern ACCEPT_BTN_PATTERN =
+ Pattern.compile(TextUtils.join("|", ACCEPT_WORDS), CASE_INSENSITIVE);
+
+ /**
+ * Buttons with text matching {@link #ACCEPT_BTN_PATTERN}.
+ */
+ private static final BySelector ACCEPT_BTN_TEXT_SELECTOR =
+ By.enabled(true)
+ .checkable(false)
+ .clickable(true)
+ .text(ACCEPT_BTN_PATTERN);
+
+ /**
+ * Buttons with content description matching {@link #ACCEPT_BTN_PATTERN}.
+ */
+ private static final BySelector ACCEPT_BTN_DESC_SELECTOR =
+ By.enabled(true)
+ .checkable(false)
+ .clickable(true)
+ .desc(ACCEPT_BTN_PATTERN);
+
+ /**
+ * Non-null string indicates fatal app crashed.
+ */
+ private static String sFatalAppCrashMsg;
+
+ /**
+ * Assert if any of these app crashes.
+ */
+ private static final List<String> FATAL_APP_CRASHES =
+ Arrays.asList(MANAGED_PROVISIONING_PKG_NAME,
+ "Setup Wizard");
+
+
+ /**
+ * Whether it is allowed to use {@link #ACCEPT_WORDS} to skip pages.
+ */
+ private static boolean sAllowPageSkipping = true;
+
+ /**
+ * List of packages whose crash should be ignored.
+ */
+ private final List<String> mAppCrashWhitelist;
+
+ /**
+ * {@link UiDevice} object.
+ */
+ private final UiDevice mUiDevice;
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ */
+ private AfwTestUiWatcher(UiDevice uiDevice) throws Exception {
+ mUiDevice = uiDevice;
+ mAppCrashWhitelist = new ArrayList<String>();
+ mAppCrashWhitelist.addAll(TestConfig.getDefault().getAppCrashWhitelist());
+ }
+
+ /**
+ * Registers this ui watcher.
+ *
+ * @param uiDevice {@link UiDevice} object
+ */
+ public static void register(UiDevice uiDevice) throws Exception {
+ uiDevice.registerWatcher(UI_WATCHER_NAME, new AfwTestUiWatcher(uiDevice));
+ }
+
+ /**
+ * Unregisters this ui watcher.
+ *
+ * @param uiDevice {@link UiDevice} object
+ */
+ public static void unregister(UiDevice uiDevice) {
+ uiDevice.removeWatcher(UI_WATCHER_NAME);
+ }
+
+ public static String getFatalAppCrashMsg() {
+ return sFatalAppCrashMsg;
+ }
+
+ /**
+ * Allows skipping pages using {@link #ACCEPT_WORDS}.
+ */
+ public static void allowPageSkipping() {
+ sAllowPageSkipping = true;
+ }
+
+ /**
+ * Disallows skipping pages using {@link #ACCEPT_WORDS}.
+ */
+ public static void disallowPageSkipping() {
+ sAllowPageSkipping = false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean checkForCondition() {
+
+ try {
+ // Use temp variables to avoid short-circuit boolean evaluation.
+ boolean isAppsStopped = clearAppStoppedDialog();
+ boolean isPagesSkipped = (sAllowPageSkipping && checkAcceptButtons());
+ return isAppsStopped || isPagesSkipped;
+ } catch (Exception e) {
+ // Don't throw exception as it will crashes the test runner
+ Log.e(TAG, UI_WATCHER_NAME, e);
+ return false;
+ }
+ }
+
+ /**
+ * Clears dialogs: "Unfortunately, {app name} has stopped."
+ *
+ * @return {@code true} if any dialog is dismissed, {@code false} otherwise
+ */
+ private boolean clearAppStoppedDialog() throws IOException {
+
+ if (mUiDevice.hasObject(APP_STOPPED_MSG_SELECTOR)
+ && mUiDevice.hasObject(APP_STOPPED_DIALOG_MUTE_SELECTOR)) {
+
+ UiObject2 msgWidget = mUiDevice.findObject(APP_STOPPED_MSG_SELECTOR);
+ if (msgWidget != null) {
+ String msg = msgWidget.getText();
+ Log.w(TAG, String.format("Found app crash dialog: %s", msg));
+ if (isFatalAppCrash(msg)) {
+ Log.w(TAG, String.format("Fatal app crash. Test should abort.", msg));
+ sFatalAppCrashMsg = msg;
+ return true;
+ }
+ Log.w(TAG, String.format("Auto closing: %s", msg));
+ sFatalAppCrashMsg = null;
+ return safeClick(mUiDevice.findObject(APP_STOPPED_DIALOG_MUTE_SELECTOR));
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if app crash is fatal.
+ *
+ * @param appCrashMsg App crash message
+ * @return {@code true} if app crash is fatal, {@code false} otherwise.
+ */
+ private boolean isFatalAppCrash(String appCrashMsg) throws IOException {
+ for (String app : FATAL_APP_CRASHES) {
+ if (appCrashMsg.contains(app)) {
+ return true;
+ }
+ }
+
+ // if muting all app crash dialog is enabled, return
+ if (TestConfig.getDefault().muteAppCrashDialogs()) {
+ Log.i(TAG, String.format("%s=true", KEY_MUTE_APP_CRASH_DIALOGS));
+ return false;
+ }
+
+ // otherwise, auto close whitelisted app crashes, assert on others.
+ for (String app : mAppCrashWhitelist) {
+ if (appCrashMsg.contains(app)) {
+ Log.w(TAG, String.format("Whitelisted app crash: %s", app));
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Clicks any visible accept button.
+ *
+ * @return {@code true} if any button found and clicked successfully;
+ * {@code false} otherwise
+ */
+ private boolean checkAcceptButtons() {
+ return safeClickAny(mUiDevice.findObjects(ACCEPT_BTN_TEXT_SELECTOR)) ||
+ safeClickAny(mUiDevice.findObjects(ACCEPT_BTN_DESC_SELECTOR));
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/BySelectorHelper.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/BySelectorHelper.java
new file mode 100644
index 0000000..3763bac
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/BySelectorHelper.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.utils;
+
+import static com.android.afwtest.common.Constants.ACTION_CHECK;
+import static com.android.afwtest.common.Constants.ACTION_SCROLL;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+
+import com.android.afwtest.common.test.OemWidget;
+
+/**
+ * Helper class for {@link BySelector}.
+ */
+public final class BySelectorHelper {
+
+ /**
+ * Gets the corresponding {@link BySelector} for {@link OemWidget}
+ *
+ * @param widget {@link OemWidget} object
+ * @return {@link BySelector} for the given {@link OemWidget}
+ */
+ public static BySelector getSelector(OemWidget widget) {
+ BySelector selector = By.enabled(true);
+
+ if (!widget.getText().isEmpty()) {
+ selector.text(widget.getText());
+ }
+
+ if (!widget.getDescription().isEmpty()) {
+ selector.desc(widget.getDescription());
+ }
+
+ if (!widget.getResourceId().isEmpty()) {
+ selector.res(widget.getResourceId());
+ }
+
+ if (!widget.getPackage().isEmpty()) {
+ selector.pkg(widget.getPackage());
+ }
+
+ if (!widget.getClassName().isEmpty()) {
+ selector.clazz(widget.getClassName());
+ }
+
+ if (widget.getAction().equals(ACTION_SCROLL)) {
+ selector.scrollable(true);
+ } else if (widget.getAction().equals(ACTION_CHECK)) {
+ selector.checkable(true).checked(false);
+ } else {
+ selector.clickable(true);
+ }
+
+ return selector;
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/Device.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/Device.java
new file mode 100644
index 0000000..f462718
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/Device.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.utils;
+
+import android.support.test.uiautomator.UiDevice;
+
+/**
+ * Device related util functions.
+ */
+public final class Device {
+ /**
+ * Swipes screen down for half page.
+ *
+ * @param uiDevice {@link UiDevice} object
+ */
+ public static void swipeDown(UiDevice device) {
+ int width = device.getDisplayWidth();
+ int height = device.getDisplayHeight();
+ device.swipe(width / 2, height / 4, width / 2, height * 3 / 4, 10);
+ }
+
+ /**
+ * Swipes screen up for half page.
+ *
+ * @param uiDevice {@link UiDevice} object
+ */
+ public static void swipeUp(UiDevice device) {
+ int width = device.getDisplayWidth();
+ int height = device.getDisplayHeight();
+ device.swipe(width / 2, height * 3 / 4, width / 2, height / 4, 10);
+ }
+} \ No newline at end of file
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/TextField.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/TextField.java
new file mode 100644
index 0000000..b82fc57
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/TextField.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.utils;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Text field related util functions.
+ */
+public final class TextField {
+
+ private static final String TAG = "afwtest.TextField";
+
+ /**
+ * Default UI waiting time, in milliseconds.
+ */
+ private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
+
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private TextField() {
+ }
+
+ /**
+ * Inputs text into a text field and activate the navigation button.
+ *
+ * <p>Normally {@link UiObject2}.setText() will input text into a text field.
+ * But on some activities, such as Add Google Account page, the navigation button (e.g. NEXT)
+ * will not be activated until there is some non-space characters entered into the text
+ * field. Calling {@link UiObject2}.setText() will set the text but not activate the
+ * navigate button.
+ * This util function solves this problem by simulating key pressing event to enter '0'
+ * into the text filed to activate the navigation button before calling {@link UiObject2}.
+ * setText() to set the text.
+ * </p>
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param textFieldSelector {@link BySelector} for the text field
+ * @param text text to set
+ * @param navigationBtnSelector navigation button to activate
+ */
+ public static void enterTextAndActivateNavigationBtn(
+ UiDevice uiDevice,
+ BySelector textFieldSelector,
+ String text,
+ BySelector navigationBtnSelector) throws Exception {
+
+ // Try 3 times
+ int maxAttempts = 3;
+ while (maxAttempts > 0) {
+
+ Log.i(TAG, String.format("Activating navigation button: %s.",
+ navigationBtnSelector.toString()));
+
+ try {
+ activateNavigationBtn(uiDevice, textFieldSelector, navigationBtnSelector);
+ // Navigation button activated, exit loop
+ break;
+ } catch (Exception e) {
+
+ --maxAttempts;
+
+ // Don't throw in the retry loop
+ Log.e(TAG, String.format("Failed to activate navigation button, attempts left: %d.",
+ maxAttempts), e);
+ }
+ }
+
+ // Throw exception if activation button is not activated.
+ if (maxAttempts <= 0) {
+ throw new RuntimeException(String.format("Failed to activate navigation button",
+ navigationBtnSelector.toString()));
+ }
+
+ // Hide keyboard.
+ uiDevice.pressBack();
+
+ // Set the text now.
+ UiObject2 textField = WidgetUtils.safeWait(uiDevice,
+ textFieldSelector,
+ DEFAULT_TIMEOUT_MS,
+ 3);
+ if (textField == null) {
+ throw new RuntimeException(
+ "Failed to find text field: " + textFieldSelector.toString());
+ }
+
+
+ textField.setText(text);
+ }
+
+ /**
+ * Activates the navigation button by simulating a key pressing event to enter '0'.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param textFieldSelector {@link BySelector} for the text field
+ * @param navigationBtnSelector navigation button to activate
+ */
+ private static void activateNavigationBtn(UiDevice uiDevice,
+ BySelector textFieldSelector,
+ BySelector navigationBtnSelector) throws Exception {
+ WidgetUtils.waitAndClick(uiDevice, textFieldSelector, DEFAULT_TIMEOUT_MS, 3);
+
+ // Clicking the text field will bring up the keyboard and change the view hierachy of
+ // current screen; textField may become stale. Try to find it again before simulating
+ // the keyboard events.
+ if (!WidgetUtils.waitForKeyboard(uiDevice, 3)) {
+ throw new RuntimeException("Could not find keyboard.");
+ }
+ if (WidgetUtils.safeWait(uiDevice, textFieldSelector, DEFAULT_TIMEOUT_MS, 3) == null) {
+ throw new RuntimeException(
+ "Failed to find text field: " + textFieldSelector.toString());
+ }
+
+ // Simulate an event to enter '0' to the text field, this will activate the navigate button.
+ if (!uiDevice.pressKeyCode(KeyEvent.KEYCODE_0)) {
+ throw new RuntimeException(String.format("Failed to enter 0 into the text field %s",
+ textFieldSelector.toString()));
+ }
+
+ // Wait for the navigation button to be activated.
+ BySelector newNavigationBtnSelector = By.copy(navigationBtnSelector).enabled(true);
+ if (WidgetUtils.safeWait(uiDevice,
+ newNavigationBtnSelector,
+ DEFAULT_TIMEOUT_MS,
+ 3) == null) {
+ throw new RuntimeException(String.format("Failed to activate navigation button: %s",
+ navigationBtnSelector.toString()));
+ }
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/WidgetUtils.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/WidgetUtils.java
new file mode 100644
index 0000000..4a3d692
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/WidgetUtils.java
@@ -0,0 +1,657 @@
+/*
+ * Copyright (C) 2016 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.afwtest.uiautomator.utils;
+
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.StaleObjectException;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+import android.widget.TextView;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+/**
+ * Widget utils.
+ */
+public class WidgetUtils {
+
+ private static final String TAG = "afwtest.WidgetUtils";
+
+ /**
+ * Waiting time for each call to UiDevice.wait().
+ */
+ private static final long DEFAULT_UI_WAIT_TIME_MS = TimeUnit.SECONDS.toMillis(5);
+
+ /**
+ * Number of attempts to call {@link UiDevice#wait()} to avoid {@link StaleObjectException}.
+ */
+ private static final int DEFAULT_UI_ATTEMPTS_COUNT = 3;
+
+ /**
+ * Margin of swipe's starting and ending points inside the target.
+ */
+ private static final float DEFAULT_SWIPE_DEADZONE_PCT = 0.1f;
+
+ /**
+ * Swipe's length as object's dimension percentage.
+ */
+ private static final float DEFAULT_SWIPE_PCT = 0.7f;
+
+ /**
+ * Keyboard pop up waiting time, in milliseconds.
+ */
+ private static final long KEYBOARD_START_TIME_MS = TimeUnit.SECONDS.toMillis(2);
+
+ /**
+ * Shell command to execute in order to determine if keyboard is visible.
+ */
+ private static final String FIND_KEYBOARD_COMMAND = "dumpsys input_method";
+
+ /**
+ * String present in dumpsys command's output when keyboard is visible.
+ */
+ private static final String KEYBOARD_IS_SHOWN_STRING = "mInputShown=true";
+
+ /**
+ * Clicks on given {@link UiObject2} without throwing any exception.
+ *
+ * @param obj {@link UiObject2} to click
+ * @return {@code true} if clicked, {@code false} otherwise
+ */
+ public static boolean safeClick(UiObject2 obj) {
+ String widgetProps = getWidgetPropertiesAsString(obj);
+ try {
+ obj.click();
+ Log.d(TAG, String.format("Clicked: %s", widgetProps));
+ return true;
+ } catch(Exception e) {
+ Log.e(TAG, String.format("Failed to click: %s", widgetProps) , e);
+ return false;
+ }
+ }
+
+ /**
+ * Tries to click any of the given list of buttons; return if any button
+ * clicked successfully.
+ *
+ * @param btns list of buttons to click
+ * @return {@code true} if any button clicked successfully; {@code false} otherwise
+ */
+ public static boolean safeClickAny(List<UiObject2> btns) {
+ for (UiObject2 obj : btns) {
+ if (safeClick(obj)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Perform fling gesture on given {@link UiObject2} until it cannot scroll any more without
+ * throwing any exception.
+ *
+ * @param obj {@link UiObject2} to scroll
+ * @param direction The direction in which to fling
+ * @return {@code true} if fling performed, {@code false} otherwise
+ */
+ public static boolean safeFling(UiObject2 obj, Direction direction) {
+ String widgetProps = getWidgetPropertiesAsString(obj);
+
+ try {
+ // Set limit to 100 times.
+ for (int i = 0; i < 100; ++i) {
+ if (!obj.fling(direction)) {
+ break;
+ }
+ }
+ Log.d(TAG, String.format("Scrolled: %s", widgetProps));
+ return true;
+ } catch(Exception e) {
+ Log.e(TAG, String.format("Failed to scroll: %s", widgetProps), e);
+ return false;
+ }
+ }
+
+ /**
+ * Waits for a widget without throwing any exception (N attempts).
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the widget to wait
+ * @param timeoutMS timeout in milliseconds
+ * @param attempts number of attempts.
+ * @return {@link UiObject2} if expected widget appears within timeout, {@code null} otherwise
+ */
+ public static UiObject2 safeWait(UiDevice uiDevice, BySelector selector, long timeoutMS,
+ int attempts) {
+ for (int i = 0; i < attempts; ++i) {
+ UiObject2 widget = safeWait(uiDevice, selector, timeoutMS);
+ if (widget != null) {
+ return widget;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Waits for a widget without throwing any exception.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the widget to wait
+ * @param timeoutMS timeout in milliseconds
+ * @return {@link UiObject2} if expected widget appears within timeout, {@code null} otherwise
+ */
+ public static UiObject2 safeWait(UiDevice uiDevice, BySelector selector, long timeoutMS) {
+ try {
+ return uiDevice.wait(Until.findObject(selector), timeoutMS);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to wait for widget ", e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Waits for a widget without throwing any exception.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the widget to wait
+ * @return {@link UiObject2} if expected widget appears within default timeout,
+ * {@code null} otherwise
+ */
+ public static UiObject2 safeWait(UiDevice uiDevice, BySelector selector) {
+ return safeWait(uiDevice, selector, DEFAULT_UI_WAIT_TIME_MS, DEFAULT_UI_ATTEMPTS_COUNT);
+ }
+
+ /**
+ * Waits for a widget and click on it without throwing any exception.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the widget to wait
+ * @return {@code true} if click action was performed, {@code false} otherwise.
+ */
+ public static boolean safeWaitAndClick(UiDevice uiDevice, BySelector selector) {
+ try {
+ waitAndClick(uiDevice, selector);
+ return true;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to wait and click for widget ", e);
+ }
+
+ return false;
+ }
+
+ /**
+ * Waits for a widget and click on it.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the widget to wait
+ */
+ public static void waitAndClick(UiDevice uiDevice, BySelector selector)
+ throws Exception {
+ waitAndClick(uiDevice, selector, DEFAULT_UI_WAIT_TIME_MS, DEFAULT_UI_ATTEMPTS_COUNT);
+ }
+
+ /**
+ * Waits and click on a child widget without throwing any exception.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param parent {@link BySelector} of the parent widget to wait
+ * @param child {@link BySelector} of the child widget to wait
+ * @param timeoutMS timeout in milliseconds
+ * @return {@code true} if click action was performed, {@code false} otherwise
+ */
+ public static boolean safeWaitAndClick(UiDevice uiDevice, BySelector parent, BySelector child,
+ long timeoutMS) {
+ try {
+ waitAndClick(uiDevice, parent, child, timeoutMS);
+ return true;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to wait and click on child widget ", e);
+ }
+
+ return false;
+ }
+
+ /**
+ * Waits and click on a child widget.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param parent {@link BySelector} of the parent widget to wait
+ * @param child {@link BySelector} of the child widget to wait
+ * @param timeoutMS timeout in milliseconds
+ */
+ public static void waitAndClick(UiDevice uiDevice, BySelector parent, BySelector child,
+ long timeoutMS) throws Exception {
+ UiObject2 object = wait(uiDevice, parent, child, timeoutMS);
+ if (object == null) {
+ throw new Exception(String.format("Child widget(%s) not found", child.toString()));
+ }
+ object.click();
+ }
+
+ /**
+ * Waits for a child object into a parent widget.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param parent {@link BySelector} of the parent widget to wait
+ * @param child {@link BySelector} of the child widget to wait
+ * @param timeoutMS timeout in milliseconds
+ * @return {@link UiObject2} if expected widget appears within timeout, {@code null} otherwise
+ */
+ public static UiObject2 wait(UiDevice uiDevice, BySelector parent, BySelector child,
+ long timeoutMS) throws Exception {
+
+ UiObject2 parentWidget = WidgetUtils.safeWait(uiDevice, parent, timeoutMS);
+ if (parentWidget == null) {
+ throw new Exception(String.format("Parent(%s) widget not found", parent.toString()));
+ }
+
+ return parentWidget.findObject(child);
+ }
+
+ /**
+ * Waits for a widget and click on it without throwing any exception.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the widget to wait
+ * @param timeoutMS timeout in milliseconds
+ * @return {@code true} if click action was performed, {@code false} otherwise.
+ */
+ public static boolean safeWaitAndClick(UiDevice uiDevice, BySelector selector, long timeoutMS) {
+ try {
+ waitAndClick(uiDevice, selector, timeoutMS);
+ return true;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to wait and click for widget ", e);
+ }
+
+ return false;
+ }
+
+ /**
+ * Waits for a widget and click on it (N attempts).
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the widget to wait
+ * @param timeoutMS timeout in milliseconds
+ * @param attempts number of attempts
+ */
+ public static void waitAndClick(UiDevice uiDevice, BySelector selector, long timeoutMS,
+ int attempts) throws Exception {
+ for (int i = 0; i < attempts; ++i) {
+ if(safeWaitAndClick(uiDevice, selector, timeoutMS)) {
+ return;
+ }
+ }
+
+ throw new Exception(String.format("UI object not found: %s after %d attempts",
+ selector.toString(),
+ attempts));
+ }
+
+ /**
+ * Waits for a widget and click on it.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the widget to wait
+ * @param timeoutMS timeout in milliseconds
+ */
+ public static void waitAndClick(UiDevice uiDevice, BySelector selector, long timeoutMS)
+ throws Exception {
+ UiObject2 object = uiDevice.wait(Until.findObject(selector), timeoutMS);
+ if (object == null) {
+ throw new Exception(String.format("UI object not found: %s", selector.toString()));
+ }
+ object.click();
+ }
+
+ /**
+ * Waits for all elements with given {@link BySelector} to be gone.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the UI elements to wait
+ * @param timeoutMs timeout in mmilliseconds
+ */
+ public static void waitToBeGone(UiDevice uiDevice, BySelector selector, long timeoutMs)
+ throws Exception {
+ uiDevice.wait(Until.gone(selector), timeoutMs);
+ }
+
+ /**
+ * Waits for all elements with given {@link BySelector} to be gone.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the UI elements to wait
+ */
+ public static void waitToBeGone(UiDevice uiDevice, BySelector selector) throws Exception {
+ waitToBeGone(uiDevice, selector, DEFAULT_UI_WAIT_TIME_MS);
+ }
+
+ /**
+ * Scrolls a scrollable UI widget so that an item with certain {@link TextView} text is in view.
+ *
+ * @param container Selector for the scrollable widget.
+ * @param item Text that appears in the target item.
+ * @return The target item as a {@link UiObject} when it first appears, or {@code null} if
+ * not found.
+ * @throws UiObjectNotFoundException If the scrollable does not exist.
+ */
+ public static UiObject scrollToItem(UiSelector container, String item)
+ throws UiObjectNotFoundException {
+ UiSelector itemSelector = new UiSelector().className(TextView.class).text(item);
+ return scrollToItem(container, itemSelector);
+ }
+
+ /**
+ * Scrolls a scrollable UI widget so that a certain item is in view.
+ *
+ * @param container Selector for the scrollable widget.
+ * @param item Selector that specifies the target item.
+ * @return The target item as a {@link UiObject} when it first appears, or {@code null} if
+ * not found.
+ * @throws UiObjectNotFoundException If the scrollable does not exist.
+ */
+ public static UiObject scrollToItem(UiSelector container, UiSelector item)
+ throws UiObjectNotFoundException {
+ UiScrollable scrollable = new UiScrollable(container);
+ if (!scrollable.waitForExists(DEFAULT_UI_WAIT_TIME_MS)) {
+ throw new UiObjectNotFoundException("Cannot find scrollable " + scrollable);
+ }
+
+ if (scrollable.scrollIntoView(item)) {
+ return scrollable.getChild(item);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Scrolls vertically a scrollable UI widget so that a certain item is in view.
+ *
+ * @param uiDevice current {@link UiDevice}.
+ * @param container {@link BySelector} of a scrollable container.
+ * @param item {@link BySelector} of an item to scroll to.
+ * @return The target item as a {@link UiObject2} when it first appears, or {@code null} if
+ * not found.
+ * @throws UiObjectNotFoundException If the scrollable does not exist.
+ */
+ public static UiObject2 scrollToItem(UiDevice uiDevice,
+ BySelector container,
+ BySelector item) throws UiObjectNotFoundException {
+ return scrollToItem(uiDevice, container, item, false);
+ }
+
+ /**
+ * Scrolls a scrollable UI widget so that a certain item is in view.
+ *
+ * @param uiDevice current {@link UiDevice}.
+ * @param container {@link BySelector} of a scrollable container.
+ * @param item {@link BySelector} of an item to scroll to.
+ * @param isHorizontal {@code true} if scrollable is horizontal, {@code false} otherwise.
+ * @return The target item as a {@link UiObject2} when it first appears, or {@code null} if
+ * not found.
+ * @throws UiObjectNotFoundException If the scrollable does not exist.
+ */
+ public static UiObject2 scrollToItem(UiDevice uiDevice,
+ BySelector container,
+ BySelector item,
+ boolean isHorizontal) throws UiObjectNotFoundException {
+
+ // Find scrollable object
+ final UiObject2 scrollable = safeWait(uiDevice, container);
+ if (scrollable == null) {
+ throw new UiObjectNotFoundException("Cannot find scrollable " + container);
+ }
+
+ setGestureMargins(scrollable, DEFAULT_SWIPE_DEADZONE_PCT);
+
+ // Determine scrolling direction
+ final Direction scrollDirection = isHorizontal ? Direction.RIGHT : Direction.DOWN;
+ final Direction oppositeDirection = Direction.reverse(scrollDirection);
+
+ // Scroll to the beginning of the scrollable
+ while (scrollable.scroll(oppositeDirection, DEFAULT_SWIPE_PCT));
+
+ // Scroll while target item is not visible and scrolling is still possible
+ UiObject2 foundItem = scrollable.findObject(item);
+ boolean isScrollingPossible = true;
+ while (foundItem == null && isScrollingPossible) {
+ isScrollingPossible = scrollable.scroll(scrollDirection, DEFAULT_SWIPE_PCT);
+ foundItem = scrollable.findObject(item);
+ }
+ return foundItem;
+ }
+
+ /**
+ * Scrolls a scrollable UI widget so that a certain item is in view and clicks this item.
+ *
+ * @param uiDevice current {@link UiDevice}.
+ * @param container {@link BySelector} of a scrollable container.
+ * @param item {@link BySelector} of an item to scroll to.
+ * @throws UiObjectNotFoundException if either scrollable or item could not be found.
+ */
+ public static void scrollToItemAndClick(UiDevice uiDevice,
+ BySelector container,
+ BySelector item) throws UiObjectNotFoundException {
+
+ scrollToItemAndClick(uiDevice, container, item, false);
+ }
+
+ /**
+ * Scrolls a scrollable UI widget so that a certain item is in view and clicks this item.
+ *
+ * @param uiDevice current {@link UiDevice}.
+ * @param container {@link BySelector} of a scrollable container.
+ * @param item {@link BySelector} of an item to scroll to.
+ * @param isHorizontal {@code true} if scrollable is horizontal, {@code false} otherwise.
+ * @throws UiObjectNotFoundException if either scrollable or item could not be found.
+ */
+ public static void scrollToItemAndClick(UiDevice uiDevice,
+ BySelector container,
+ BySelector item,
+ boolean isHorizontal) throws UiObjectNotFoundException {
+
+ UiObject2 foundItem = scrollToItem(uiDevice, container, item, isHorizontal);
+ if (foundItem == null) {
+ throw new UiObjectNotFoundException(item.toString() + " could not be found.");
+ }
+ foundItem.click();
+ }
+
+ /**
+ * Set target object's gesture margins.
+ * @param target target object.
+ * @param marginPct gesture margin as target's dimension percentage.
+ */
+ private static void setGestureMargins(UiObject2 target, float marginPct) {
+ final Rect bounds = callWithRetry(target::getVisibleBounds, DEFAULT_UI_ATTEMPTS_COUNT);
+ final int horizontalMargin = (int)(bounds.width() * marginPct);
+ final int verticalMargin = (int)(bounds.height() * marginPct);
+ target.setGestureMargins(horizontalMargin,
+ verticalMargin,
+ horizontalMargin,
+ verticalMargin);
+ }
+
+ /**
+ * Gets properties of a {@link UiObject2} as a String, for debugging purpose.
+ *
+ * @param widget {@link UiObject2} to get properties from
+ * @return properties of given {@link UiObject2} as String
+ */
+ public static String getWidgetPropertiesAsString(UiObject2 widget) {
+ try {
+ return String.format("text=[%s],desc=[%s],res=[%s],pkg=[%s],class=[%s]",
+ widget.getText(),
+ widget.getContentDescription(),
+ widget.getResourceName(),
+ widget.getApplicationPackage(),
+ widget.getClassName());
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get properties from a widget", e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the package name of a {@link UiObject2} safely.
+ *
+ * @param widget {@link UiObject2} to get property from
+ * @return package name of given widget or null if there is any error
+ */
+ public static String getPackageName(UiObject2 widget) {
+ try {
+ return widget.getApplicationPackage();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get package name from a widget", e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the text of a {@link UiObject2} safely.
+ *
+ * @param widget {@link UiObject2} to get property from
+ * @return text of given widget or null if there is any error
+ */
+ public static String getText(UiObject2 widget) {
+ try {
+ return widget.getText();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get text from a widget", e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the content description of a {@link UiObject2} safely.
+ *
+ * @param widget {@link UiObject2} to get property from
+ * @return content description of given widget or null if there is any error
+ */
+ public static String getContentDescription(UiObject2 widget) {
+ try {
+ return widget.getContentDescription();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get text from a widget", e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Presses back until element specified by either {@code targetSelector}
+ * or {@code limiterSelector} is found. If element specified by {@code targetSelector} is found,
+ * it is returned. {@code null} is returned otherwise.
+ *
+ * @param uiDevice current {@link UiDevice}.
+ * @param targetSelector {@link BySelector} of element to found.
+ * @param limiterSelector {@link BySelector} of boundary element to represent failed search.
+ * @param maxAttempts Max number of attempts
+ * @return element specified by {@code targetSelector} if found, {@code null} otherwise.
+ */
+ public static UiObject2 safeGoBackUntilFound(UiDevice uiDevice, BySelector targetSelector,
+ BySelector limiterSelector, int maxAttempts) {
+ UiObject2 foundObject = null;
+ while (maxAttempts-- > 0
+ && (foundObject = WidgetUtils.safeWait(uiDevice, targetSelector)) == null
+ && WidgetUtils.safeWait(uiDevice, limiterSelector) == null) {
+ uiDevice.pressBack();
+ }
+ return foundObject;
+ }
+
+ /**
+ * Presses back until element specified by either {@code targetSelector}
+ * or {@code limiterSelector} is found. If element specified by {@code targetSelector} is found,
+ * it is returned. {@code null} is returned otherwise.
+ *
+ * @param uiDevice current {@link UiDevice}.
+ * @param targetSelector {@link BySelector} of element to found.
+ * @param limiterSelector {@link BySelector} of boundary element to represent failed search.
+ * @return element specified by {@code targetSelector} if found, {@code null} otherwise.
+ */
+ public static UiObject2 safeGoBackUntilFound(UiDevice uiDevice, BySelector targetSelector,
+ BySelector limiterSelector) {
+ return safeGoBackUntilFound(uiDevice, targetSelector, limiterSelector, 10);
+ }
+
+ /**
+ * Waits until keyboard is visible.
+ *
+ * @param uiDevice current {@link UiDevice}.
+ * @param maxAttempts maximum attempts to find keyboard before reporting failure.
+ * @return {@code true} if keyboard was found and is visible, {@code false} otherwise.
+ * @throws IOException if could not run shell command to get dumpsys info.
+ */
+ public static boolean waitForKeyboard(UiDevice uiDevice, int maxAttempts) throws IOException {
+ while ((maxAttempts--) > 0) {
+ SystemClock.sleep(KEYBOARD_START_TIME_MS);
+ final String output = uiDevice.executeShellCommand(FIND_KEYBOARD_COMMAND);
+ if (output.contains(KEYBOARD_IS_SHOWN_STRING)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Executes provided action. In case the call throws
+ * {@link StaleObjectException} it is retried at most {@code maxAttempts} times.
+ *
+ * @param action action to execute.
+ * @param maxAttempts maximum attempt to perform.
+ * @param <T> return type of provided action.
+ * @return value returned by provided action.
+ */
+ public static <T> T callWithRetry(Supplier<T> action, int maxAttempts) {
+ if (maxAttempts <= 0) {
+ throw new IllegalArgumentException("maxAttempts must be positive integer value.");
+ }
+
+ T result = null;
+ while ((maxAttempts--) > 0) {
+ try {
+ result = action.get();
+ break;
+ } catch (StaleObjectException e) {
+ if (maxAttempts == 0) {
+ throw e;
+ }
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/licenses.html b/licenses.html
new file mode 100644
index 0000000..197722a
--- /dev/null
+++ b/licenses.html
@@ -0,0 +1,311 @@
+<html>
+<head>
+ <style>
+ body { margin: 0; font-family: sans-serif; }
+ span { background-color: #eeeeee; display:block; padding: 1em; white-space: pre-wrap; font-family: monospace; font-size: 8pt; }
+ li { list-style: none; }
+ h3 { margin: 0.5em; }
+ p { margin: 1em; white-space: nowrap; }
+ </style>
+</head>
+<body>
+<p>
+ Notice for BouncyCastle<br />
+</p>
+<span>
+Copyright (c) 2000-2015 The Legion of the Bouncy Castle Inc.<br />
+Website: http://www.bouncycastle.org<br />
+
+The MIT License (MIT)
+http://opensource.org/licenses/mit-license.php<br />
+
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+</span>
+<p>
+ Notice for Conscrypt:<br />
+</p>
+<span>
+Website: https://conscrypt.org/<br />
+
+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.</span>
+<p>
+ Notice for JUnit<br />
+</p>
+<span>
+URL: http://junit.org<br />
+
+LICENSE:
+JUNIT
+
+Common Public License - v 1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON
+PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF
+THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and
+ documentation distributed under this Agreement, and
+b) in the case of each subsequent Contributor:
+ i) changes to the Program, and
+ ii) additions to the Program;
+where such changes and/or additions to the Program originate from
+and are distributed by that particular Contributor. A Contribution
+'originates' from a Contributor if it was added to the Program by
+such Contributor itself or anyone acting on such Contributor's behalf.
+Contributions do not include additions to the Program which: (i) are
+separate modules of software distributed in conjunction with the
+Program under their own license agreement, and (ii) are not derivative
+works of the Program.
+
+
+"Contributor" means any person or entity that distributes the Program.
+
+
+"Licensed Patents " mean patent claims licensable by a Contributor which
+are necessarily infringed by the use or sale of its Contribution alone
+or when combined with the Program.
+
+
+"Program" means the Contributions distributed in accordance with this
+ Agreement.
+
+
+"Recipient" means anyone who receives the Program under this Agreement,
+including all Contributors.
+
+
+2. GRANT OF RIGHTS
+
+a) Subject to the terms of this Agreement, each Contributor hereby grants
+Recipient a non-exclusive, worldwide, royalty-free copyright license to
+reproduce, prepare derivative works of, publicly display, publicly perform,
+distribute and sublicense the Contribution of such Contributor, if any,
+and such derivative works, in source code and object code form.
+b) Subject to the terms of this Agreement, each Contributor hereby grants
+Recipient a non-exclusive, worldwide, royalty-free patent license under
+Licensed Patents to make, use, sell, offer to sell, import and otherwise
+transfer the Contribution of such Contributor, if any, in source code and
+object code form. This patent license shall apply to the combination of
+the Contribution and the Program if, at the time the Contribution is added
+by the Contributor, such addition of the Contribution causes such combination
+to be covered by the Licensed Patents. The patent license shall not apply to
+any other combinations which include the Contribution. No hardware per se is
+licensed hereunder.
+c) Recipient understands that although each Contributor grants the
+licenses to its Contributions set forth herein, no assurances are provided
+by any Contributor that the Program does not infringe the patent or other
+intellectual property rights of any other entity. Each Contributor disclaims
+any liability to Recipient for claims brought by any other entity based on
+infringement of intellectual property rights or otherwise. As a condition to
+exercising the rights and licenses granted hereunder, each Recipient hereby
+assumes sole responsibility to secure any other intellectual property rights
+needed, if any. For example, if a third party patent license is required to
+allow Recipient to distribute the Program, it is Recipient's responsibility
+to acquire that license before distributing the Program.
+d) Each Contributor represents that to its knowledge it has sufficient
+copyright rights in its Contribution, if any, to grant the copyright license
+set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code form under
+its own license agreement, provided that:
+
+a) it complies with the terms and conditions of this Agreement; and
+b) its license agreement:
+i) effectively disclaims on behalf of all Contributors all warranties and
+conditions, express and implied, including warranties or conditions of title
+and non-infringement, and implied warranties or conditions of merchantability
+and fitness for a particular purpose;
+ii) effectively excludes on behalf of all Contributors all liability for
+damages, including direct, indirect, special, incidental and consequential
+damages, such as lost profits;
+iii) states that any provisions which differ from this Agreement are offered
+by that Contributor alone and not by any other party; and
+iv) states that source code for the Program is available from such Contributor,
+and informs licensees how to obtain it in a reasonable manner on or through a
+medium customarily used for software exchange.
+When the Program is made available in source code form:
+
+a) it must be made available under this Agreement; and
+b) a copy of this Agreement must be included with each copy of the Program.
+
+Contributors may not remove or alter any copyright notices contained within the Program.
+
+
+Each Contributor must identify itself as the originator of its Contribution,
+if any, in a manner that reasonably allows subsequent Recipients to identify
+the originator of the Contribution.
+
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities with
+respect to end users, business partners and the like. While this license is
+intended to facilitate the commercial use of the Program, the Contributor
+who includes the Program in a commercial product offering should do so in a
+manner which does not create potential liability for other Contributors.
+Therefore, if a Contributor includes the Program in a commercial product
+offering, such Contributor ("Commercial Contributor") hereby agrees to defend
+and indemnify every other Contributor ("Indemnified Contributor") against any
+losses, damages and costs (collectively "Losses") arising from claims, lawsuits
+and other legal actions brought by a third party against the Indemnified
+Contributor to the extent caused by the acts or omissions of such Commercial
+Contributor in connection with its distribution of the Program in a commercial
+product offering. The obligations in this section do not apply to any claims
+or Losses relating to any actual or alleged intellectual property infringement.
+In order to qualify, an Indemnified Contributor must: a) promptly notify the
+Commercial Contributor in writing of such claim, and b) allow the Commercial
+Contributor to control, and cooperate with the Commercial Contributor in, the
+defense and any related settlement negotiations. The Indemnified Contributor
+may participate in any such claim at its own expense.
+
+
+For example, a Contributor might include the Program in a commercial product
+offering, Product X. That Contributor is then a Commercial Contributor. If
+that Commercial Contributor then makes performance claims, or offers warranties
+related to Product X, those performance claims and warranties are such Commercial
+Contributor's responsibility alone. Under this section, the Commercial Contributor
+would have to defend claims against the other Contributors related to those
+performance claims and warranties, and if a court requires any other Contributor
+to pay any damages as a result, the Commercial Contributor must pay those damages.
+
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
+IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
+Recipient is solely responsible for determining the appropriateness of using
+and distributing the Program and assumes all risks associated with its exercise
+of rights under this Agreement, including but not limited to the risks and costs
+of program errors, compliance with applicable laws, damage to or loss of data,
+programs or equipment, and unavailability or interruption of operations.
+
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
+CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
+LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
+EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGES.
+
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under applicable
+law, it shall not affect the validity or enforceability of the remainder of
+the terms of this Agreement, and without further action by the parties hereto,
+such provision shall be reformed to the minimum extent necessary to make such
+provision valid and enforceable.
+
+If Recipient institutes patent litigation against a Contributor with respect
+to a patent applicable to software (including a cross-claim or counterclaim
+in a lawsuit), then any patent licenses granted by that Contributor to such
+Recipient under this Agreement shall terminate as of the date such litigation
+is filed. In addition, if Recipient institutes patent litigation against any
+entity (including a cross-claim or counterclaim in a lawsuit) alleging that
+the Program itself (excluding combinations of the Program with other software
+or hardware) infringes such Recipient's patent(s), then such Recipient's rights
+granted under Section 2(b) shall terminate as of the date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails to comply
+with any of the material terms or conditions of this Agreement and does not cure
+such failure in a reasonable period of time after becoming aware of such
+noncompliance. If all Recipient's rights under this Agreement terminate, Recipient
+agrees to cease use and distribution of the Program as soon as reasonably
+practicable. However, Recipient's obligations under this Agreement and any
+licenses granted by Recipient relating to the Program shall continue and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement, but in
+order to avoid inconsistency the Agreement is copyrighted and may only be
+modified in the following manner. The Agreement Steward reserves the right to
+publish new versions (including revisions) of this Agreement from time to time.
+No one other than the Agreement Steward has the right to modify this Agreement.
+IBM is the initial Agreement Steward. IBM may assign the responsibility to serve
+as the Agreement Steward to a suitable separate entity. Each new version of the
+Agreement will be given a distinguishing version number. The Program (including
+Contributions) may always be distributed subject to the version of the Agreement
+under which it was received. In addition, after a new version of the Agreement is
+published, Contributor may elect to distribute the Program (including its Contributions)
+under the new version. Except as expressly stated in Sections 2(a) and 2(b) above,
+Recipient receives no rights or licenses to the intellectual property of any
+Contributor under this Agreement, whether expressly, by implication, estoppel or
+otherwise. All rights in the Program not expressly granted under this Agreement
+are reserved.
+
+This Agreement is governed by the laws of the State of New York and the intellectual
+property laws of the United States of America. No party to this Agreement will bring
+a legal action under this Agreement more than one year after the cause of action arose.
+Each party waives its rights to a jury trial in any resulting litigation.
+</span>
+<p>
+ Notice for libphonenumber<br />
+</p>
+<span>
+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.</span>
+<p>
+ Notice for OkHttp<br />
+</p>
+<span>
+Copyright 2014 Square, Inc.<br />
+
+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.</span>
+</body>
+</html>
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..41a41d0
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,17 @@
+#
+# Copyright (C) 2016 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.
+#
+
+include $(call all-subdir-makefiles)
diff --git a/tests/NfcProvisioning/Android.mk b/tests/NfcProvisioning/Android.mk
new file mode 100644
index 0000000..882e7d1
--- /dev/null
+++ b/tests/NfcProvisioning/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2016 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := AfwTestNfcProvisioningTestCases
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator AfwThCommonLib AfwThUiAutomatorLib android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_AFW_TEST_MODULE_CONFIG := $(LOCAL_PATH)/$(AFW_TEST_MODULE_TEST_CONFIG)
+
+LOCAL_SDK_VERSION := 22
+
+include $(BUILD_AFW_TEST_PACKAGE)
diff --git a/tests/NfcProvisioning/AndroidManifest.xml b/tests/NfcProvisioning/AndroidManifest.xml
new file mode 100644
index 0000000..5ba861d
--- /dev/null
+++ b/tests/NfcProvisioning/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.afwtest.nfcprovisioning">
+
+ <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="23"/>
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.afwtest.nfcprovisioning"
+ android:label="AfW NFC Provisioning Test Package">
+ </instrumentation>
+</manifest>
diff --git a/tests/NfcProvisioning/AndroidTest.xml b/tests/NfcProvisioning/AndroidTest.xml
new file mode 100644
index 0000000..7572cf2
--- /dev/null
+++ b/tests/NfcProvisioning/AndroidTest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<!-- This file contains configurations used to prepare a testing device for running the
+ test in in AfwTestNfcProvisioningTestCases.apk.
+-->
+<configuration description="Test configurations for AfwTestNfcProvisioningTestCases">
+
+ <include name="afw-test-factory-reset"/>
+
+ <!-- General device setup. -->
+ <include name="afw-test-common"/>
+ <!-- Turn on NFC -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="svc nfc enable" />
+ </target_preparer>
+
+ <!-- Dump testing environment info to android-cts/repository/logs/ -->
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestEnvDumper" >
+ <option name="file-name-prefix" value="AfwTestNfcProvisioningTestCases"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="AfwTestNfcProvisioningTestCases.apk"/>
+ </target_preparer>
+
+ <test class="com.android.afwtest.tradefed.testtype.AfwAndroidJUnitTest">
+ <option name="package" value="com.android.afwtest.nfcprovisioning"/>
+ </test>
+</configuration>
diff --git a/tests/NfcProvisioning/src/com/android/afwtest/nfcprovisioning/NfcProvisioningTest.java b/tests/NfcProvisioning/src/com/android/afwtest/nfcprovisioning/NfcProvisioningTest.java
new file mode 100644
index 0000000..9199d79
--- /dev/null
+++ b/tests/NfcProvisioning/src/com/android/afwtest/nfcprovisioning/NfcProvisioningTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016 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.afwtest.nfcprovisioning;
+
+import static com.android.afwtest.common.test.TestConfig.DEFAULT_TEST_CONFIG_FILE;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcManager;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import com.android.afwtest.common.nfcprovisioning.Utils;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.provisioning.AutomationDriver;
+import com.android.afwtest.uiautomator.test.AbstractTestCase;
+import com.android.afwtest.uiautomator.test.AfwTestUiWatcher;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * NFC provisioning test.
+ */
+@RunWith(AndroidJUnit4.class)
+public class NfcProvisioningTest extends AbstractTestCase {
+
+ private static final String TAG = "afwtest.NfcProvisioningTest";
+
+ /**
+ * {@inheritDoc}
+ */
+ @Before
+ public void setUp() throws Exception {
+ AfwTestUiWatcher.register(getUiDevice());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @After
+ public void tearDown() throws Exception {
+ AfwTestUiWatcher.unregister(getUiDevice());
+ }
+
+ @Test
+ public void testNfcProvisioning() throws Exception {
+
+ // Skip the test if no nfc
+ if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
+ Log.w(TAG, "Device doesn't support NFC, skipping Nfc provisioning test!");
+ return;
+ }
+
+ // Verify if NFC is available and enabled
+ NfcManager nfcManager = (NfcManager) getContext().getSystemService(Context.NFC_SERVICE);
+ assertNotNull("Failed to get NfcManager", nfcManager);
+ NfcAdapter nfcAdapter = nfcManager.getDefaultAdapter();
+ assertNotNull("No NFC adapter found!", nfcAdapter);
+ assertTrue("NFC is disabled.", nfcAdapter.isEnabled());
+
+ // Start provisioning
+ String deviceAdminPkgName = Utils.startProvisioning(getContext(), DEFAULT_TEST_CONFIG_FILE);
+ assertNotNull(deviceAdminPkgName);
+
+ // Navigate the pages.
+ AutomationDriver driver = new AutomationDriver(getUiDevice());
+ assertTrue("NFC provisioning didn't finish",
+ driver.runNfcProvisioning(TestConfig.getDefault()));
+
+ DevicePolicyManager devicePolicyManager =
+ (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ // Verify if the device is provisioned.
+ assertTrue("Provisioning failed", devicePolicyManager.isDeviceOwnerApp(deviceAdminPkgName));
+ }
+}
diff --git a/tests/NonSuwPoProvisioning/Android.mk b/tests/NonSuwPoProvisioning/Android.mk
new file mode 100644
index 0000000..69b4063
--- /dev/null
+++ b/tests/NonSuwPoProvisioning/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2016 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := AfwTestNonSuwPoProvisioningTestCases
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator AfwThCommonLib AfwThUiAutomatorLib android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := 22
+
+LOCAL_AFW_TEST_MODULE_CONFIG := $(LOCAL_PATH)/$(AFW_TEST_MODULE_TEST_CONFIG)
+
+include $(BUILD_AFW_TEST_PACKAGE)
diff --git a/tests/NonSuwPoProvisioning/AndroidManifest.xml b/tests/NonSuwPoProvisioning/AndroidManifest.xml
new file mode 100644
index 0000000..3db6052
--- /dev/null
+++ b/tests/NonSuwPoProvisioning/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.afwtest.nonsuwpoprovisioning">
+
+ <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="23"/>
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.afwtest.nonsuwpoprovisioning"
+ android:label="AfW Non-SuW PO Provisioning Test Package">
+ </instrumentation>
+</manifest>
diff --git a/tests/NonSuwPoProvisioning/AndroidTest.xml b/tests/NonSuwPoProvisioning/AndroidTest.xml
new file mode 100644
index 0000000..7e0aa4c
--- /dev/null
+++ b/tests/NonSuwPoProvisioning/AndroidTest.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<!-- This file contains configurations used to prepare a testing device for running the
+ test in in AfwTestNonSuwPoProvisioningTestCases.apk.
+-->
+<configuration description="Test configurations for AfwTestNonSuwPoProvisioningTestCases">
+
+ <!-- Disable adb root option as this test is intended to be running on production build. -->
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestAdbRootOptionPreparer">
+ <option name="enable-root-option" value="false"/>
+ </target_preparer>
+
+ <!-- General device setup -->
+ <include name="afw-test-common"/>
+
+ <!-- Connect wifi -->
+ <include name="afw-test-wifi"/>
+
+ <!-- Uninstall TestDpc and reset users. -->
+ <include name="afw-test-uninstall-testdpc-and-reset-users"/>
+
+ <!-- Force-stop Google Search App after the test as it often becomes not responding. -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="teardown-command" value="am force-stop com.google.android.googlequicksearchbox"/>
+ </target_preparer>
+
+ <!-- Dump testing environment info to android-cts/repository/logs/ -->
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestEnvDumper" >
+ <option name="file-name-prefix" value="AfwTestNonSuwPoProvisioningTestCases"/>
+ </target_preparer>
+
+ <!-- Copy Provisioning-Stats.csv to local file -->
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestPullExternalFile" >
+ <option name="remote-file" value="Provisioning-Stats.csv"/>
+ <option name="local-file" value="/stats/NonSuwPoProvisioning/Provisioning-Stats.csv"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="AfwTestNonSuwPoProvisioningTestCases.apk"/>
+ </target_preparer>
+
+ <test class="com.android.afwtest.tradefed.testtype.AfwAndroidJUnitTest">
+ <option name="package" value="com.android.afwtest.nonsuwpoprovisioning"/>
+ </test>
+</configuration>
diff --git a/tests/NonSuwPoProvisioning/src/com/android/afwtest/nonsuwpoprovisioning/NonSuwPoProvisioningTest.java b/tests/NonSuwPoProvisioning/src/com/android/afwtest/nonsuwpoprovisioning/NonSuwPoProvisioningTest.java
new file mode 100644
index 0000000..f2f505a
--- /dev/null
+++ b/tests/NonSuwPoProvisioning/src/com/android/afwtest/nonsuwpoprovisioning/NonSuwPoProvisioningTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2016 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.afwtest.nonsuwpoprovisioning;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Intent;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.afwtest.common.AccountManagerUtils;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.provisioning.AutomationDriver;
+import com.android.afwtest.uiautomator.test.AbstractTestCase;
+import com.android.afwtest.uiautomator.test.AfwTestUiWatcher;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test profile owner provisioning started from Settings.
+ */
+@RunWith(AndroidJUnit4.class)
+public class NonSuwPoProvisioningTest extends AbstractTestCase {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Before
+ public void setUp() throws Exception {
+ AfwTestUiWatcher.register(getUiDevice());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @After
+ public void tearDown() throws Exception {
+ AfwTestUiWatcher.unregister(getUiDevice());
+ }
+
+ /**
+ * Tests non-suw profile owner provisioning flow.
+ */
+ @Test
+ public void testNonSuwPoProvisioning() throws Exception {
+
+ // Start Add Account activity.
+ AccountManagerUtils.startAddGoogleAccountActivity(getContext(), false);
+ AutomationDriver automationDriver = new AutomationDriver(getUiDevice());
+
+ // Navigate provisioning.
+ assertTrue("Non-SuW PO provisioning didn't finish",
+ automationDriver.runNonSuwPoProvisioning(TestConfig.getDefault()));
+ }
+
+ /**
+ * Tests provisioning a non-existing package.
+ */
+ @Test
+ public void testDisallowedProvisioning() throws Exception {
+ Intent startProvisioning = new Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE);
+ // We're asking ManagedProvisioning to provision a non-existing package. It should not be
+ // allowed.
+ startProvisioning.putExtra(
+ DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
+ "package.that.does.not.exist");
+ startProvisioning.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ AutomationDriver automationDriver = new AutomationDriver(getUiDevice());
+ getContext().startActivity(startProvisioning);
+ assertTrue("Non-SuW PO provisioning disallowed didn't finish",
+ automationDriver.runNonSuwPoProvisioningDisallowed(TestConfig.getDefault()));
+ }
+}
diff --git a/tests/QRCodeProvisioning/Android.mk b/tests/QRCodeProvisioning/Android.mk
new file mode 100644
index 0000000..d75171c
--- /dev/null
+++ b/tests/QRCodeProvisioning/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2016 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := AfwTestQRCodeProvisioningTestCases
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator AfwThCommonLib AfwThUiAutomatorLib android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_AFW_TEST_MODULE_CONFIG := $(LOCAL_PATH)/$(AFW_TEST_MODULE_TEST_CONFIG)
+
+LOCAL_SDK_VERSION := 24
+
+include $(BUILD_AFW_TEST_PACKAGE)
diff --git a/tests/QRCodeProvisioning/AndroidManifest.xml b/tests/QRCodeProvisioning/AndroidManifest.xml
new file mode 100644
index 0000000..4f52706
--- /dev/null
+++ b/tests/QRCodeProvisioning/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.afwtest.qrcodeprovisioning">
+
+ <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="24"/>
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.afwtest.qrcodeprovisioning"
+ android:label="AfW QR Code Provisioning Test Package">
+ </instrumentation>
+</manifest>
diff --git a/tests/QRCodeProvisioning/AndroidTest.xml b/tests/QRCodeProvisioning/AndroidTest.xml
new file mode 100644
index 0000000..a90a003
--- /dev/null
+++ b/tests/QRCodeProvisioning/AndroidTest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<!-- This file contains configurations used to prepare a testing device for running the
+ test in AfwTestQRCodeProvisioningTestCases.apk.
+-->
+<configuration description="Test configurations for AfwTestQRCodeProvisioningTestCases">
+
+ <include name="afw-test-factory-reset"/>
+
+ <!-- General device setup. -->
+ <include name="afw-test-common"/>
+
+ <!-- Connect wifi -->
+ <include name="afw-test-wifi"/>
+
+ <!-- Dump testing environment info to android-cts/repository/logs/ -->
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestEnvDumper" >
+ <option name="file-name-prefix" value="AfwTestQRCodeProvisioningTestCases"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="AfwTestQRCodeProvisioningTestCases.apk"/>
+ </target_preparer>
+
+ <test class="com.android.afwtest.tradefed.testtype.AfwAndroidJUnitTest">
+ <option name="package" value="com.android.afwtest.qrcodeprovisioning"/>
+ </test>
+</configuration>
diff --git a/tests/QRCodeProvisioning/src/com/android/afwtest/qrcodeprovisioning/QRCodeProvisioningTest.java b/tests/QRCodeProvisioning/src/com/android/afwtest/qrcodeprovisioning/QRCodeProvisioningTest.java
new file mode 100644
index 0000000..2cbe953
--- /dev/null
+++ b/tests/QRCodeProvisioning/src/com/android/afwtest/qrcodeprovisioning/QRCodeProvisioningTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 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.afwtest.qrcodeprovisioning;
+
+import static com.android.afwtest.common.test.TestConfig.DEFAULT_TEST_CONFIG_FILE;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.SystemClock;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+
+import com.android.afwtest.common.qrcodeprovisioning.Utils;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.provisioning.AutomationDriver;
+import com.android.afwtest.uiautomator.test.AbstractTestCase;
+import com.android.afwtest.uiautomator.test.AfwTestUiWatcher;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.util.regex.Pattern;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * QR code provisioning test.
+ */
+@RunWith(AndroidJUnit4.class)
+public class QRCodeProvisioningTest extends AbstractTestCase {
+
+ private static final String SUW_PACKAGE_NAME = "com.google.android.setupwizard";
+ private static final int TAP_COUNT = 6;
+ private static final int TAP_INTERVAL = 300;
+
+ private static final int QR_READER_INSTALL_TIMEOUT = 30000;
+ private static final int QR_READER_INSTALL_ATTEMPTS = 6;
+
+ private static final BySelector NEXT_BUTTON_SELECTOR =
+ By.text(Pattern.compile("next", Pattern.CASE_INSENSITIVE)).clickable(true);
+ private static final BySelector SWITCH_CAMERA_BUTTON_SELECTOR =
+ By.res(SUW_PACKAGE_NAME, "switch_camera_button");
+ private static final BySelector WELCOME_TITLE_SELECTOR =
+ By.text(Pattern.compile("welcome|hi there", Pattern.CASE_INSENSITIVE));
+
+ /**
+ * {@inheritDoc}
+ */
+ @Before
+ public void setUp() throws Exception {
+ AfwTestUiWatcher.register(getUiDevice());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @After
+ public void tearDown() throws Exception {
+ AfwTestUiWatcher.unregister(getUiDevice());
+ }
+
+ /**
+ * Test QR code provisioning flow.
+ */
+ @Test
+ public void testQRCodeProvisioning() throws Exception {
+ UiObject2 welcomeTitle = WidgetUtils.safeWait(getUiDevice(), WELCOME_TITLE_SELECTOR);
+ for (int i = 0; i < TAP_COUNT; i++) {
+ welcomeTitle.click();
+ SystemClock.sleep(TAP_INTERVAL);
+ }
+ WidgetUtils.waitAndClick(getUiDevice(), NEXT_BUTTON_SELECTOR);
+ UiObject2 frontCameraButton =
+ WidgetUtils.safeWait(getUiDevice(), SWITCH_CAMERA_BUTTON_SELECTOR,
+ QR_READER_INSTALL_TIMEOUT, QR_READER_INSTALL_ATTEMPTS);
+ if (frontCameraButton == null) {
+ throw new UiObjectNotFoundException("Cannot identify QR reader");
+ }
+
+ // Start provisioning
+ String deviceAdminPkgName = Utils.startProvisioning(getContext(), DEFAULT_TEST_CONFIG_FILE);
+ assertNotNull(deviceAdminPkgName);
+ AutomationDriver driver = new AutomationDriver(getUiDevice());
+ assertTrue("QR Code provisioning didn't finish",
+ driver.runQRCodeProvisioning(TestConfig.getDefault()));
+ DevicePolicyManager devicePolicyManager =
+ (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
+ // Verify if the device is provisioned.
+ assertTrue("Provisioning failed", devicePolicyManager.isDeviceOwnerApp(deviceAdminPkgName));
+ }
+}
diff --git a/tests/SuwDoProvisioning/Android.mk b/tests/SuwDoProvisioning/Android.mk
new file mode 100644
index 0000000..308e9c1
--- /dev/null
+++ b/tests/SuwDoProvisioning/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2016 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := AfwTestSuwDoProvisioningTestCases
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator AfwThCommonLib AfwThUiAutomatorLib android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := 22
+
+LOCAL_AFW_TEST_MODULE_CONFIG := $(LOCAL_PATH)/$(AFW_TEST_MODULE_TEST_CONFIG)
+
+include $(BUILD_AFW_TEST_PACKAGE)
diff --git a/tests/SuwDoProvisioning/AndroidManifest.xml b/tests/SuwDoProvisioning/AndroidManifest.xml
new file mode 100644
index 0000000..9688e98
--- /dev/null
+++ b/tests/SuwDoProvisioning/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.afwtest.suwdoprovisioning">
+
+ <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="23"/>
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.afwtest.suwdoprovisioning"
+ android:label="AfW SUW DO Provisioning Test Package">
+ </instrumentation>
+</manifest>
diff --git a/tests/SuwDoProvisioning/AndroidTest.xml b/tests/SuwDoProvisioning/AndroidTest.xml
new file mode 100644
index 0000000..0e8b9e8
--- /dev/null
+++ b/tests/SuwDoProvisioning/AndroidTest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<!-- This file contains configurations used to prepare a testing device for running the
+ test in AfwTestSuwDoProvisioningTestCases.apk.
+-->
+<configuration description="Test configurations for AfwTestSuwDoProvisioningTestCases">
+
+ <!-- Factory Reset Device -->
+ <include name="afw-test-factory-reset"/>
+
+ <!-- General device setup -->
+ <include name="afw-test-common"/>
+
+ <!-- Encrypt Device -->
+ <include name="afw-test-encrypt-device"/>
+
+ <!-- Connect wifi -->
+ <include name="afw-test-wifi"/>
+
+ <!-- Dump testing environment info to android-cts/repository/logs/ -->
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestEnvDumper" >
+ <option name="file-name-prefix" value="AfwTestSuwDoProvisioningTestCases"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="AfwTestSuwDoProvisioningTestCases.apk"/>
+ </target_preparer>
+
+ <test class="com.android.afwtest.tradefed.testtype.AfwAndroidJUnitTest">
+ <option name="package" value="com.android.afwtest.suwdoprovisioning"/>
+ </test>
+</configuration>
diff --git a/tests/SuwDoProvisioning/src/com/android/afwtest/suwdoprovisioning/SuwDoProvisioningTest.java b/tests/SuwDoProvisioning/src/com/android/afwtest/suwdoprovisioning/SuwDoProvisioningTest.java
new file mode 100644
index 0000000..d81eff9
--- /dev/null
+++ b/tests/SuwDoProvisioning/src/com/android/afwtest/suwdoprovisioning/SuwDoProvisioningTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 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.afwtest.suwdoprovisioning;
+
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_PKG_NAME;
+import static org.junit.Assert.assertTrue;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.afwtest.common.AccountManagerUtils;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.provisioning.AutomationDriver;
+import com.android.afwtest.uiautomator.test.AbstractTestCase;
+import com.android.afwtest.uiautomator.test.AfwTestUiWatcher;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * SuW device owner provisioning test.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SuwDoProvisioningTest extends AbstractTestCase{
+
+ /**
+ * {@inheritDoc}
+ */
+ @Before
+ public void setUp() throws Exception {
+ AfwTestUiWatcher.register(getUiDevice());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @After
+ public void tearDown() throws Exception {
+ AfwTestUiWatcher.unregister(getUiDevice());
+ }
+
+ /**
+ * Tests Device Owner provisioning flow.
+ */
+ @Test
+ public void testDoProvisioning() throws Exception {
+
+ AccountManagerUtils.startAddGoogleAccountActivity(getContext(), true);
+
+ AutomationDriver runner = new AutomationDriver(getUiDevice());
+ assertTrue("SuW DO provisioning didn't finish",
+ runner.runSuwDoProvisioning(TestConfig.getDefault()));
+
+ DevicePolicyManager devicePolicyManager =
+ (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ // Verify if the device is provisioned.
+ assertTrue("Provisioning failed",
+ devicePolicyManager.isDeviceOwnerApp(TESTDPC_PKG_NAME));
+ }
+}
diff --git a/tests/SuwPoProvisioning/Android.mk b/tests/SuwPoProvisioning/Android.mk
new file mode 100644
index 0000000..49bf2f7
--- /dev/null
+++ b/tests/SuwPoProvisioning/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2016 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := AfwTestSuwPoProvisioningTestCases
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator AfwThCommonLib AfwThUiAutomatorLib android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := 22
+
+LOCAL_AFW_TEST_MODULE_CONFIG := $(LOCAL_PATH)/$(AFW_TEST_MODULE_TEST_CONFIG)
+
+include $(BUILD_AFW_TEST_PACKAGE)
diff --git a/tests/SuwPoProvisioning/AndroidManifest.xml b/tests/SuwPoProvisioning/AndroidManifest.xml
new file mode 100644
index 0000000..4e5172d
--- /dev/null
+++ b/tests/SuwPoProvisioning/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.afwtest.suwpoprovisioning">
+
+ <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="23"/>
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.afwtest.suwpoprovisioning"
+ android:label="AfW SUW PO Provisioning Test Package">
+ </instrumentation>
+</manifest>
diff --git a/tests/SuwPoProvisioning/AndroidTest.xml b/tests/SuwPoProvisioning/AndroidTest.xml
new file mode 100644
index 0000000..fe2ef05
--- /dev/null
+++ b/tests/SuwPoProvisioning/AndroidTest.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<!-- This file contains configurations used to prepare a testing device for running the
+ test in AfwTestSuwPoProvisioningTestCases.apk.
+-->
+<configuration description="Test configurations for AfwTestSuwPoProvisioningTestCases">
+
+ <!-- Factory Reset Device -->
+ <include name="afw-test-factory-reset"/>
+
+ <!-- General device setup -->
+ <include name="afw-test-common"/>
+
+ <!-- Encrypt Device -->
+ <include name="afw-test-encrypt-device"/>
+
+ <!-- Connect wifi -->
+ <include name="afw-test-wifi"/>
+
+ <!-- Remove non-primary user after the test. -->
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestUserRemover">
+ <option name="remove-users-before-test" value="false"/>
+ </target_preparer>
+
+ <!-- Force-stop Google Search App after the test as it often becomes not responding. -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="teardown-command" value="am force-stop com.google.android.googlequicksearchbox"/>
+ </target_preparer>
+
+ <!-- Dump testing environment info to android-cts/repository/logs/ -->
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestEnvDumper" >
+ <option name="file-name-prefix" value="AfwTestSuwPoProvisioningTestCases"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="AfwTestSuwPoProvisioningTestCases.apk"/>
+ </target_preparer>
+
+ <test class="com.android.afwtest.tradefed.testtype.AfwAndroidJUnitTest">
+ <option name="package" value="com.android.afwtest.suwpoprovisioning"/>
+ </test>
+</configuration>
diff --git a/tests/SuwPoProvisioning/src/com/android/afwtest/suwpoprovisioning/SuwPoProvisioningTest.java b/tests/SuwPoProvisioning/src/com/android/afwtest/suwpoprovisioning/SuwPoProvisioningTest.java
new file mode 100644
index 0000000..da3b6fe
--- /dev/null
+++ b/tests/SuwPoProvisioning/src/com/android/afwtest/suwpoprovisioning/SuwPoProvisioningTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 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.afwtest.suwpoprovisioning;
+
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.provisioning.AutomationDriver;
+import com.android.afwtest.uiautomator.test.AbstractTestCase;
+import com.android.afwtest.uiautomator.test.AfwTestUiWatcher;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test Profile Owner provisioning flow from Setup Wizard.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SuwPoProvisioningTest extends AbstractTestCase {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Before
+ public void setUp() throws Exception {
+ AfwTestUiWatcher.register(getUiDevice());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @After
+ public void tearDown() throws Exception {
+ AfwTestUiWatcher.unregister(getUiDevice());
+ }
+
+ @Test
+ public void testPoProvisioning() throws Exception {
+
+ AutomationDriver runner = new AutomationDriver(getUiDevice());
+ assertTrue("SuW PO provisioning didn't finish",
+ runner.runSuwPoProvisioning(TestConfig.getDefault()));
+ }
+}
diff --git a/tools/Android.mk b/tools/Android.mk
new file mode 100644
index 0000000..dad6c47
--- /dev/null
+++ b/tools/Android.mk
@@ -0,0 +1,31 @@
+#
+# Copyright (C) 2016 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.
+#
+
+AFW_TEST_TF_JAR := $(HOST_OUT_JAVA_LIBRARIES)/afw-test-tradefed.jar
+AFW_TEST_TF_EXEC_PATH ?= $(HOST_OUT_EXECUTABLES)/afw-test-tradefed
+
+afw_test_prebuilt_jar := $(HOST_OUT)/afw-th/android-cts/tools/afw-test-prebuilt.jar
+$(afw_test_prebuilt_jar): PRIVATE_TESTS_DIR := $(HOST_OUT)/afw-th/android-cts/testcases
+$(afw_test_prebuilt_jar): PRIVATE_TOOLS_DIR := $(HOST_OUT)/afw-th/android-cts/tools
+$(afw_test_prebuilt_jar): $(TF_JAR) $(AFW_TEST_TF_JAR) $(AFW_TEST_TF_EXEC_PATH) | $(ACP) $(HOST_OUT_EXECUTABLES)/adb
+ mkdir -p $(PRIVATE_TESTS_DIR)
+ mkdir -p $(PRIVATE_TOOLS_DIR)
+ $(ACP) -fp $(TF_JAR) $(AFW_TEST_TF_JAR) $(AFW_TEST_TF_EXEC_PATH) $(PRIVATE_TOOLS_DIR)
+
+.PHONY: afw-test-tools
+afw-test-tools : $(afw_test_prebuilt_jar)
+
+include $(call all-subdir-makefiles)
diff --git a/tools/prebuilt/CtsDeviceInfo.apk b/tools/prebuilt/CtsDeviceInfo.apk
new file mode 100644
index 0000000..4071caa
--- /dev/null
+++ b/tools/prebuilt/CtsDeviceInfo.apk
Binary files differ
diff --git a/tools/prebuilt/README b/tools/prebuilt/README
new file mode 100644
index 0000000..7bd6280
--- /dev/null
+++ b/tools/prebuilt/README
@@ -0,0 +1,5 @@
+CtsDeviceInfo.apk: prebuilt from oc-release (BID:3842764 OPR1.170323.001)
+https://android-build.googleplex.com/builds/submitted/3842764/cts_arm64/latest
+
+CtsDeviceInfo.apk: prebuilt from oc-release (BID:3751078 OPR1.170221.001)
+https://android-build.googleplex.com/builds/submitted/3751078/cts_arm64/latest
diff --git a/tools/prebuilt/TestDpc.apk b/tools/prebuilt/TestDpc.apk
new file mode 100644
index 0000000..aaf2904
--- /dev/null
+++ b/tools/prebuilt/TestDpc.apk
Binary files differ
diff --git a/tools/tradefed-host/Android.mk b/tools/tradefed-host/Android.mk
new file mode 100644
index 0000000..b2d488b
--- /dev/null
+++ b/tools/tradefed-host/Android.mk
@@ -0,0 +1,47 @@
+#
+# Copyright (C) 2016 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += $(call all-proto-files-under, src)
+LOCAL_SRC_FILES += $(call all-java-files-under, ../../../../cts/common/host-side/tradefed/src)
+
+LOCAL_JAVA_RESOURCE_DIRS := res
+LOCAL_JAVA_RESOURCE_DIRS += ../../../../cts/common/host-side/tradefed/res
+
+LOCAL_MODULE := afw-test-tradefed
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+LOCAL_JAR_MANIFEST := MANIFEST.mf
+LOCAL_STATIC_JAVA_LIBRARIES := host-framework-protos host-libprotobuf-java-nano
+LOCAL_SUITE_BUILD_NUMBER := $(BUILD_NUMBER_FROM_FILE)
+LOCAL_SUITE_TARGET_ARCH := $(TARGET_ARCH)
+LOCAL_SUITE_NAME := CTS
+LOCAL_SUITE_FULLNAME := "for Work Test Harness"
+LOCAL_SUITE_VERSION := 3.1
+
+include $(BUILD_COMPATIBILITY_SUITE)
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/tradefed-host/MANIFEST.mf b/tools/tradefed-host/MANIFEST.mf
new file mode 100644
index 0000000..5528c06
--- /dev/null
+++ b/tools/tradefed-host/MANIFEST.mf
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: com.android.cts.tradefed.testtype
+Implementation-Version: %BUILD_NUMBER%
diff --git a/tools/tradefed-host/README b/tools/tradefed-host/README
new file mode 100644
index 0000000..25bffb6
--- /dev/null
+++ b/tools/tradefed-host/README
@@ -0,0 +1,50 @@
+Android For Work (Afw) Test Trade Federation
+-------------------------------------------
+
+AfW Test Trade Federation, afw-test-tradefed for short, is the built on top
+of the cts-tradefed test harness.
+
+It works in a similar manner to cts-tradefed harness but with customized
+features for AFW testing automation, such as customized device preparers.
+
+Configuring afw-test-tradefed
+----------------------------
+
+1. Ensure 'adb' is in your current PATH. adb can be found in the
+Android SDK available from http://developer.android.com
+
+Example:
+ PATH=$PATH:/home/myuser/android-sdk-linux_x86/platform-tools
+
+2. Connect the device to the host machine.
+
+3. Ensure device is visible via 'adb devices'
+
+Using afw-test-tradefed
+----------------------
+
+To run a test plan on a single device:
+
+1. Make sure you have at least one device connected.
+2. Launch the afw-test-tradefed console by running the 'afw-test-tradefed'.
+ The "afw-test-tradefed' script can be found at android-cts/tools/afw-test-tradefed
+ from the android-afw-test.zip you downloaded.
+
+3. Type: 'run cts --plan afw-userdebug-build' to run test plan afw-userdebug-build.
+
+ Type 'list plans' to find all test plans.
+
+Some other useful commands are:
+
+To run a test package:
+'run cts --package <packagename>'
+
+To run a test class:
+'run cts --class <full test class name>'
+
+To shard a plan test run on multiple devices
+'run cts --plan afw --shards <number of shards>
+note: all connected devices must be running the same build
+
+For more options:
+'run cts --help'
diff --git a/tools/tradefed-host/etc/Android.mk b/tools/tradefed-host/etc/Android.mk
new file mode 100644
index 0000000..3d0920e
--- /dev/null
+++ b/tools/tradefed-host/etc/Android.mk
@@ -0,0 +1,26 @@
+#
+# Copyright (C) 2016 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PREBUILT_EXECUTABLES := afw-test-tradefed
+
+include $(BUILD_HOST_PREBUILT)
+
diff --git a/tools/tradefed-host/etc/afw-test-tradefed b/tools/tradefed-host/etc/afw-test-tradefed
new file mode 100755
index 0000000..b524042
--- /dev/null
+++ b/tools/tradefed-host/etc/afw-test-tradefed
@@ -0,0 +1,103 @@
+#!/bin/bash
+#
+# Copyright (C) 2016 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.
+#
+
+# launcher script for afw-test-tradefed harness
+# Can be used from an Android build environment, or a standalone
+# android-afw-test-harness zip
+
+checkFile() {
+ if [ ! -f "$1" ]; then
+ echo "Unable to locate $1"
+ exit
+ fi;
+}
+
+checkPath() {
+ if ! type -P $1 &> /dev/null; then
+ echo "Unable to find $1 in path."
+ exit
+ fi;
+}
+
+checkPath adb
+checkPath java
+
+# check java version
+JAVA_VERSION=$(java -version 2>&1 | head -n 2 | grep '[ "]1\.[78][\. "$$]')
+if [ "${JAVA_VERSION}" == "" ]; then
+ echo "Wrong java version. 1.7 or 1.8 is required."
+ exit
+fi
+
+# check debug flag and set up remote debugging
+if [ -n "${TF_DEBUG}" ]; then
+ if [ -z "${TF_DEBUG_PORT}" ]; then
+ TF_DEBUG_PORT=10088
+ fi
+ RDBG_FLAG=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=${TF_DEBUG_PORT}
+fi
+
+# get OS
+HOST=`uname`
+if [ "$HOST" == "Linux" ]; then
+ OS="linux-x86"
+elif [ "$HOST" == "Darwin" ]; then
+ OS="darwin-x86"
+else
+ echo "Unrecognized OS"
+ exit
+fi
+
+# check if in Android build env
+if [ ! -z "${ANDROID_BUILD_TOP}" ]; then
+ if [ ! -z "${ANDROID_HOST_OUT}" ]; then
+ CTS_ROOT=${ANDROID_HOST_OUT}/afw-th
+ else
+ CTS_ROOT=${ANDROID_BUILD_TOP}/${OUT_DIR:-out}/host/${OS}/afw-th
+ fi
+ if [ ! -d ${CTS_ROOT} ]; then
+ echo "Could not find $CTS_ROOT in Android build environment. Try 'make afw-test-harness'"
+ exit
+ fi;
+fi;
+
+if [ -z ${CTS_ROOT} ]; then
+ # assume we're in an extracted afw-test install
+ CTS_ROOT="$(dirname $0)/../.."
+fi;
+
+JAR_DIR=${CTS_ROOT}/android-cts/tools
+JARS="tradefed.jar afw-test-tradefed.jar compatibility-host-util.jar"
+
+for JAR in $JARS; do
+ checkFile ${JAR_DIR}/${JAR}
+ JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}
+done
+
+# load any shared libraries for host-side executables
+LIB_DIR=${CTS_ROOT}/android-cts/lib
+if [ "$HOST" == "Linux" ]; then
+ LD_LIBRARY_PATH=${LIB_DIR}:${LIB_DIR}64:${LD_LIBRARY_PATH}
+ export LD_LIBRARY_PATH
+elif [ "$HOST" == "Darwin" ]; then
+ DYLD_LIBRARY_PATH=${LIB_DIR}:${LIB_DIR}64:${DYLD_LIBRARY_PATH}
+ export DYLD_LIBRARY_PATH
+fi
+
+java $RDBG_FLAG \
+ -cp ${JAR_PATH} -DCTS_ROOT=${CTS_ROOT} com.android.compatibility.common.tradefed.command.CompatibilityConsole "$@"
+
diff --git a/tools/tradefed-host/res/config/afw-test-common-base.xml b/tools/tradefed-host/res/config/afw-test-common-base.xml
new file mode 100644
index 0000000..18ca542
--- /dev/null
+++ b/tools/tradefed-host/res/config/afw-test-common-base.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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="Common device setup configuration base">
+
+ <!-- General device setup. -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Disable heads up notifications. -->
+ <option name="run-command" value="settings put global heads_up_notifications_enabled 0"/>
+ <!-- Skip encryption, works for L only; for M+ it's disabled in nfc bump. -->
+ <option name="run-command" value="setprop persist.sys.no_req_encrypt true"/>
+ <!-- Set device screen always on. -->
+ <option name="run-command" value="svc power stayon true"/>
+ <!-- Turn off NFC to avoid devices sending bumps to each other -->
+ <option name="run-command" value="svc nfc disable" />
+ <!-- Disable package verification -->
+ <option name="run-command" value="settings put global package_verifier_enable 0" />
+ <!-- Increase logcat buffer size -->
+ <option name="run-command" value="logcat -G 16M" />
+ </target_preparer>
+
+ <!-- Push afw-test.props -->
+ <include name="afw-test-push-test-suite-config"/>
+
+</configuration>
diff --git a/tools/tradefed-host/res/config/afw-test-common.xml b/tools/tradefed-host/res/config/afw-test-common.xml
new file mode 100644
index 0000000..f604281
--- /dev/null
+++ b/tools/tradefed-host/res/config/afw-test-common.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="Common device setup configuration">
+
+ <!-- Include base common configuration -->
+ <include name="afw-test-common-base" />
+
+</configuration>
diff --git a/tools/tradefed-host/res/config/afw-test-create-managed-profile.xml b/tools/tradefed-host/res/config/afw-test-create-managed-profile.xml
new file mode 100644
index 0000000..6682ee6
--- /dev/null
+++ b/tools/tradefed-host/res/config/afw-test-create-managed-profile.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="Create managed profile">
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestManagedProfileCreator">
+ <option name="profile-owner-apk" value="TestDpc.apk"/>
+ <option name="profile-owner-component" value="com.afwsamples.testdpc/.DeviceAdminReceiver"/>
+ </target_preparer>
+</configuration> \ No newline at end of file
diff --git a/tools/tradefed-host/res/config/afw-test-encrypt-device.xml b/tools/tradefed-host/res/config/afw-test-encrypt-device.xml
new file mode 100644
index 0000000..f6a8d9a
--- /dev/null
+++ b/tools/tradefed-host/res/config/afw-test-encrypt-device.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="Encrypt device">
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestEncryptDevice"/>
+</configuration>
diff --git a/tools/tradefed-host/res/config/afw-test-factory-reset.xml b/tools/tradefed-host/res/config/afw-test-factory-reset.xml
new file mode 100644
index 0000000..a8cf2d0
--- /dev/null
+++ b/tools/tradefed-host/res/config/afw-test-factory-reset.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="Factory reset configuration">
+
+ <!-- Install privileged apps -->
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestPrivAppInstaller">
+ <option name="app-filename" value="AfwThSystemUtil.apk"/>
+ <option name="permission-file" value="afw-test-system-util-permissions.xml"/>
+ </target_preparer>
+
+ <!-- Remove Google accounts before and after test to avoid FRP -->
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestGoogleAccountRemover"/>
+
+ <!-- Disable package verification so that AfwThDeviceAdmin can be installed-->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="settings put global package_verifier_enable 0" />
+ </target_preparer>
+
+ <!-- Install device admin app -->
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="test-file-name" value="AfwThDeviceAdmin.apk" />
+ </target_preparer>
+
+ <!-- Set device admin -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- 'dpm set-active-admin' requires Android min Sdk Version 22 -->
+ <option name="run-command" value="dpm set-active-admin com.android.afwtest.deviceadmin/com.android.afwtest.deviceadmin.AdminReceiver"/>
+ </target_preparer>
+
+ <!-- Factory reset. -->
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestFactoryReset"/>
+
+</configuration>
diff --git a/tools/tradefed-host/res/config/afw-test-push-test-suite-config.xml b/tools/tradefed-host/res/config/afw-test-push-test-suite-config.xml
new file mode 100644
index 0000000..fc6cfd1
--- /dev/null
+++ b/tools/tradefed-host/res/config/afw-test-push-test-suite-config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="Push the test suite configuration file: afw-test.props">
+
+ <!-- Push test suite configuration file -->
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="push" value="afw-test.props->/data/local/tmp/afw-test.props"/>
+ <option name="cleanup" value="true"/>
+ </target_preparer>
+
+</configuration>
diff --git a/tools/tradefed-host/res/config/afw-test-remove-users.xml b/tools/tradefed-host/res/config/afw-test-remove-users.xml
new file mode 100644
index 0000000..bc6c8db
--- /dev/null
+++ b/tools/tradefed-host/res/config/afw-test-remove-users.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="Remove non-primary users.">
+
+ <!-- Remove all non-primary users before and after the test. -->
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestUserRemover">
+ <option name="remove-users-before-test" value="true"/>
+ <option name="remove-users-after-test" value="true"/>
+ </target_preparer>
+
+</configuration>
diff --git a/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-after-test.xml b/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-after-test.xml
new file mode 100644
index 0000000..db392d2
--- /dev/null
+++ b/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-after-test.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="Uninstall TestDpc after the test">
+
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestAppUninstaller" >
+ <option name="after-test" value="com.afwsamples.testdpc"/>
+ <option name="reboot-before-uninstall" value="true"/>
+ </target_preparer>
+
+</configuration>
diff --git a/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-and-reset-users.xml b/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-and-reset-users.xml
new file mode 100644
index 0000000..43227f8
--- /dev/null
+++ b/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-and-reset-users.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="Uninstall TestDpc and reset users, before and after the test">
+
+ <!-- Uninstall TestDpc after the test. -->
+ <include name="afw-test-uninstall-testdpc-after-test"/>
+
+ <!-- Remove all non-primary users before and after the test. -->
+ <include name="afw-test-remove-users"/>
+
+ <!-- Uninstall TestDpc before the test. -->
+ <include name="afw-test-uninstall-testdpc-before-test"/>
+
+</configuration>
diff --git a/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-before-test.xml b/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-before-test.xml
new file mode 100644
index 0000000..5f25742
--- /dev/null
+++ b/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-before-test.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="Uninstall TestDpc before the test">
+
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestAppUninstaller" >
+ <option name="before-test" value="com.afwsamples.testdpc"/>
+ <option name="reboot-before-uninstall" value="true"/>
+ </target_preparer>
+
+</configuration>
diff --git a/tools/tradefed-host/res/config/afw-test-wifi.xml b/tools/tradefed-host/res/config/afw-test-wifi.xml
new file mode 100644
index 0000000..70428df
--- /dev/null
+++ b/tools/tradefed-host/res/config/afw-test-wifi.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="Connect to wifi network">
+
+ <!-- Install device admin app -->
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="test-file-name" value="AfwThUtil.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestWifiPreparer" >
+ <option name="wifi-config-file" value="afw-test.props"/>
+ <option name="wifi-ssid-key" value="wifi_ssid"/>
+ <option name="wifi-security-type-key" value="wifi_security_type"/>
+ <option name="wifi-password-key" value="wifi_password"/>
+ <option name="disconnect-wifi-after-test" value="false"/>
+ </target_preparer>
+
+</configuration>
diff --git a/tools/tradefed-host/res/config/afw-user-build.xml b/tools/tradefed-host/res/config/afw-user-build.xml
new file mode 100644
index 0000000..fb6d96a
--- /dev/null
+++ b/tools/tradefed-host/res/config/afw-user-build.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="Runs AFW test on user build">
+
+ <include name="cts" />
+
+ <option name="compatibility-build-provider:plan" value="afw-user-build" />
+
+ <option name="compatibility:include-filter" value="AfwTestNonSuwPoProvisioningTestCases" />
+
+</configuration>
diff --git a/tools/tradefed-host/res/config/afw-userdebug-build.xml b/tools/tradefed-host/res/config/afw-userdebug-build.xml
new file mode 100644
index 0000000..a13903f
--- /dev/null
+++ b/tools/tradefed-host/res/config/afw-userdebug-build.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="Runs AFW test on user-debug build">
+
+ <include name="cts" />
+
+ <option name="compatibility-build-provider:plan" value="afw-userdebug-build" />
+
+ <option name="compatibility:include-filter" value="AfwTestNfcProvisioningTestCases" />
+ <option name="compatibility:include-filter" value="AfwTestQRCodeProvisioningTestCases" />
+ <option name="compatibility:include-filter" value="AfwTestSuwDoProvisioningTestCases" />
+ <option name="compatibility:include-filter" value="AfwTestSuwPoProvisioningTestCases" />
+
+</configuration>
diff --git a/tools/tradefed-host/res/config/cts.xml b/tools/tradefed-host/res/config/cts.xml
new file mode 100644
index 0000000..76871b9
--- /dev/null
+++ b/tools/tradefed-host/res/config/cts.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="Runs a test plan from Afw Test Harness installation">
+
+ <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
+ <option name="compatibility-build-provider:plan" value="cts"/>
+ <device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" >
+ <option name="device-wait-time" value="480000"/>
+ <option name="bootloader-wait-time" value="60000"/>
+ <option name="shell-wait-time" value="60000"/>
+ </device_recovery>
+ <test class="com.android.afwtest.tradefed.testtype.AfwTest">
+ <option name="screenshot-on-failure" value="true"/>
+ <option name="screenshot" value="true"/>
+ <option name="bugreport-on-failure" value="true"/>
+ <option name="primary-abi-only" value="true"/>
+ <option name="skip-all-system-status-check" value="true"/>
+ </test>
+ <logger class="com.android.tradefed.log.FileLogger" />
+ <result_reporter class="com.android.compatibility.common.tradefed.result.ConsoleReporter" />
+ <result_reporter class="com.android.compatibility.common.tradefed.result.ResultReporter" />
+
+ <!-- Retry logic is overwritten in AfwTestWifiPreparer. Please use its wifi-attempts
+ option directly. -->
+ <option name="wifi-attempts" value="1" />
+ <option name="wifi-retry-wait-time" value="30000" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="rm -rf /sdcard/device-info-files" />
+ <option name="run-command" value="rm -rf /sdcard/report-log-files" />
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DeviceInfoCollector">
+ <option name="apk" value="CtsDeviceInfo.apk"/>
+ <option name="package" value="com.android.compatibility.common.deviceinfo"/>
+ <option name="src-dir" value="/sdcard/device-info-files/"/>
+ <option name="dest-dir" value="device-info-files/"/>
+ <option name="temp-dir" value="temp-device-info-files/"/>
+ </target_preparer>
+
+</configuration>
diff --git a/tools/tradefed-host/res/report/cts_result.xsl b/tools/tradefed-host/res/report/cts_result.xsl
new file mode 100644
index 0000000..c21b26f
--- /dev/null
+++ b/tools/tradefed-host/res/report/cts_result.xsl
@@ -0,0 +1,610 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp "&#160;"> ]>
+<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
+
+ <xsl:template match="/">
+
+ <html>
+ <head>
+ <title>Test Report for <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@build_model" /> - <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@deviceID"/></title>
+ <script>
+ function toggle(id) {
+ e = document.getElementById(id)
+ e.style.display = e.style.display == "none" ? "block" : "none"
+ }
+ </script>
+ <STYLE type="text/css">
+ @import "cts_result.css";
+ </STYLE>
+ </head>
+ <body>
+ <DIV>
+ <TABLE class="title">
+ <TR>
+ <TD width="40%" align="left"><img src="logo.gif"></img></TD>
+ <TD width="60%" align="left">
+ <h1>Test Report for <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@build_model"/> -
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@deviceID"/>
+ </h1>
+ </TD>
+ </TR>
+ </TABLE>
+ </DIV>
+ <img src="newrule-green.png" align="left"></img>
+
+ <br></br>
+
+ <center>
+ <a href="#" onclick="toggle('summary');">Show Device Information</a>
+ </center>
+
+ <br></br>
+
+ <DIV id="summary" style="display: none">
+ <TABLE class="summary">
+ <TR>
+ <TH colspan="2">Device Information</TH>
+ </TR>
+ <TR>
+ <TD width="50%">
+ <!-- Device information -->
+ <TABLE>
+ <TR>
+ <TD class="rowtitle">Build Model</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@build_model"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Build Product</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@buildName"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Build Brand</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@build_brand"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Build Manufacturer</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@build_manufacturer"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Device ID</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@deviceID"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Android Version</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@buildVersion"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Build ID</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@buildID"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Build Fingerprint</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@build_fingerprint"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Build ABI</TD>
+ <TD>
+ <xsl:value-of
+ select="TestResult/DeviceInfo/BuildInfo/@build_abi"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Build ABI2</TD>
+ <TD>
+ <xsl:value-of
+ select="TestResult/DeviceInfo/BuildInfo/@build_abi2"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Android API Level</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@androidPlatformVersion"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Supported Locales</TD>
+ <TD>
+ <xsl:call-template name="formatDelimitedString">
+ <xsl:with-param name="string" select="TestResult/DeviceInfo/BuildInfo/@locales"/>
+ </xsl:call-template>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Screen Size</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/Screen/@screen_size"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Resolution</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/Screen/@resolution"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Density</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/Screen/@screen_density"/>
+ (<xsl:value-of select="TestResult/DeviceInfo/Screen/@screen_density_bucket"/>)
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Phone number</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/PhoneSubInfo/@subscriberId"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">X dpi</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@Xdpi"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Y dpi</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@Ydpi"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Touch</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@touch"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Navigation</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@navigation"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Keypad</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@keypad"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Network</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@network"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">IMEI</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@imei"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">IMSI</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@imsi"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Open GL ES Version</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@openGlEsVersion"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Open GL Compressed Texture Formats</TD>
+ <TD>
+ <UL>
+ <xsl:for-each select="TestResult/DeviceInfo/OpenGLCompressedTextureFormatsInfo/TextureFormat">
+ <LI><xsl:value-of select="@name" /></LI>
+ </xsl:for-each>
+ </UL>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Root Processes</TD>
+ <TD>
+ <UL>
+ <xsl:for-each select="TestResult/DeviceInfo/ProcessInfo/Process[@uid='0']">
+ <LI><xsl:value-of select="@name" /></LI>
+ </xsl:for-each>
+ </UL>
+ </TD>
+ </TR>
+
+ </TABLE>
+ </TD>
+
+ <TD width="50%">
+ <TABLE>
+
+ <TR>
+ <TD class="rowtitle">Features</TD>
+ <TD>
+ <xsl:for-each select="TestResult/DeviceInfo/FeatureInfo/Feature[@type='sdk']">
+ <xsl:text>[</xsl:text>
+ <xsl:choose>
+ <xsl:when test="@available = 'true'">
+ <xsl:text>X</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>_</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>] </xsl:text>
+
+ <xsl:value-of select="@name" />
+ <br />
+ </xsl:for-each>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Other Features</TD>
+ <TD>
+ <UL>
+ <xsl:for-each select="TestResult/DeviceInfo/FeatureInfo/Feature[@type='other']">
+ <LI><xsl:value-of select="@name" /></LI>
+ </xsl:for-each>
+ </UL>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">System Libraries</TD>
+ <TD>
+ <UL>
+ <xsl:for-each select="TestResult/DeviceInfo/SystemLibrariesInfo/Library">
+ <LI><xsl:value-of select="@name" /></LI>
+ </xsl:for-each>
+ </UL>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Partitions</TD>
+ <TD>
+ <pre>
+ <xsl:call-template name="formatDelimitedString">
+ <xsl:with-param name="string" select="TestResult/DeviceInfo/BuildInfo/@partitions" />
+ <xsl:with-param name="numTokensPerRow" select="1" />
+ </xsl:call-template>
+ </pre>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Storage devices</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@storage_devices"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Multi-user support</TD>
+ <TD>
+ <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@multi_user"/>
+ </TD>
+ </TR>
+ </TABLE>
+ </TD>
+ </TR>
+ </TABLE>
+ <br />
+ <br />
+ </DIV>
+
+ <DIV>
+ <TABLE class="summary">
+ <TR>
+ <TH colspan="2">Test Summary</TH>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Afw Test Harness Version</TD>
+ <TD>3.0.1</TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Test timeout</TD>
+ <TD>
+ <xsl:value-of select="TestResult/HostInfo/Cts/IntValue[@name='testStatusTimeoutMs']/@value" /> ms
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Host Info</TD>
+ <TD>
+ <xsl:value-of select="TestResult/HostInfo/@name"/>
+ (<xsl:value-of select="TestResult/HostInfo/Os/@name"/> - <xsl:value-of select="TestResult/HostInfo/Os/@version"/>)
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Plan name</TD>
+ <TD>
+ <xsl:value-of select="TestResult/@testPlan"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Start time</TD>
+ <TD>
+ <xsl:value-of select="TestResult/@starttime"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">End time</TD>
+ <TD>
+ <xsl:value-of select="TestResult/@endtime"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Tests Passed</TD>
+ <TD>
+ <xsl:value-of select="TestResult/Summary/@pass"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Tests Failed</TD>
+ <TD>
+ <xsl:value-of select="TestResult/Summary/@failed"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Tests Timed out</TD>
+ <TD>
+ <xsl:value-of select="TestResult/Summary/@timeout"/>
+ </TD>
+ </TR>
+ <TR>
+ <TD class="rowtitle">Tests Not Executed</TD>
+ <TD>
+ <xsl:value-of select="TestResult/Summary/@notExecuted"/>
+ </TD>
+ </TR>
+ </TABLE>
+ </DIV>
+
+ <!-- High level summary of test execution -->
+ <h2 align="center">Test Summary by Package</h2>
+ <DIV>
+ <TABLE class="testsummary">
+ <TR>
+ <TH>Test Package</TH>
+ <TH>Passed</TH>
+ <TH>Failed</TH>
+ <TH>Timed Out</TH>
+ <TH>Not Executed</TH>
+ <TH>Total Tests</TH>
+ </TR>
+ <xsl:for-each select="TestResult/TestPackage">
+ <TR>
+ <TD>
+ <xsl:variable name="href"><xsl:value-of select="@appPackageName"/></xsl:variable>
+ <a href="#{$href}"><xsl:value-of select="@appPackageName"/></a>
+ </TD>
+ <TD>
+ <xsl:value-of select="count(TestSuite//Test[@result = 'pass'])"/>
+ </TD>
+ <TD>
+ <xsl:value-of select="count(TestSuite//Test[@result = 'fail'])"/>
+ </TD>
+ <TD>
+ <xsl:value-of select="count(TestSuite//Test[@result = 'timeout'])"/>
+ </TD>
+ <TD>
+ <xsl:value-of select="count(TestSuite//Test[@result = 'notExecuted'])"/>
+ </TD>
+ <TD>
+ <xsl:value-of select="count(TestSuite//Test)"/>
+ </TD>
+ </TR>
+ </xsl:for-each> <!-- end package -->
+ </TABLE>
+ </DIV>
+
+ <xsl:call-template name="filteredResultTestReport">
+ <xsl:with-param name="header" select="'Test Failures'" />
+ <xsl:with-param name="resultFilter" select="'fail'" />
+ </xsl:call-template>
+
+ <xsl:call-template name="filteredResultTestReport">
+ <xsl:with-param name="header" select="'Test Timeouts'" />
+ <xsl:with-param name="resultFilter" select="'timeout'" />
+ </xsl:call-template>
+
+ <h2 align="center">Detailed Test Report</h2>
+ <xsl:call-template name="detailedTestReport" />
+
+ </body>
+ </html>
+ </xsl:template>
+
+ <xsl:template name="filteredResultTestReport">
+ <xsl:param name="header" />
+ <xsl:param name="resultFilter" />
+ <xsl:variable name="numMatching" select="count(TestResult/TestPackage/TestSuite//TestCase/Test[@result=$resultFilter])" />
+ <xsl:if test="$numMatching &gt; 0">
+ <h2 align="center"><xsl:value-of select="$header" /> (<xsl:value-of select="$numMatching"/>)</h2>
+ <xsl:call-template name="detailedTestReport">
+ <xsl:with-param name="resultFilter" select="$resultFilter"/>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template name="detailedTestReport">
+ <xsl:param name="resultFilter" />
+ <DIV>
+ <xsl:for-each select="TestResult/TestPackage">
+ <xsl:if test="$resultFilter=''
+ or count(TestSuite//TestCase/Test[@result=$resultFilter]) &gt; 0">
+
+ <TABLE class="testdetails">
+ <TR>
+ <TD class="package" colspan="3">
+ <xsl:variable name="href"><xsl:value-of select="@appPackageName"/></xsl:variable>
+ <a name="{$href}">Compatibility Test Package: <xsl:value-of select="@appPackageName"/>
+ <xsl:if test="@abi">
+ ABI: <xsl:value-of select="@abi"/>
+ </xsl:if>
+ </a>
+ </TD>
+ </TR>
+
+ <TR>
+ <TH width="30%">Test</TH>
+ <TH width="5%">Result</TH>
+ <TH>Details</TH>
+ </TR>
+
+ <!-- test case -->
+ <xsl:for-each select="TestSuite//TestCase">
+
+ <xsl:if test="$resultFilter='' or count(Test[@result=$resultFilter]) &gt; 0">
+ <!-- emit a blank row before every test suite name -->
+ <xsl:if test="position()!=1">
+ <TR><TD class="testcasespacer" colspan="3"></TD></TR>
+ </xsl:if>
+
+ <TR>
+ <TD class="testcase" colspan="3">
+ <xsl:for-each select="ancestor::TestSuite">
+ <xsl:if test="position()!=1">.</xsl:if>
+ <xsl:value-of select="@name"/>
+ </xsl:for-each>
+ <xsl:text>.</xsl:text>
+ <xsl:value-of select="@name"/>
+ </TD>
+ </TR>
+ </xsl:if>
+
+ <!-- test -->
+ <xsl:for-each select="Test">
+ <xsl:if test="$resultFilter='' or $resultFilter=@result">
+ <TR>
+ <TD class="testname"> -- <xsl:value-of select="@name"/></TD>
+
+ <!-- test results -->
+ <xsl:choose>
+ <xsl:when test="string(@KnownFailure)">
+ <!-- "pass" indicates the that test actually passed (results have been inverted already) -->
+ <xsl:if test="@result='pass'">
+ <TD class="pass">
+ <div style="text-align: center; margin-left:auto; margin-right:auto;">
+ Known problem
+ </div>
+ </TD>
+ <TD class="failuredetails"></TD>
+ </xsl:if>
+
+ <!-- "fail" indicates that a known failure actually passed (results have been inverted already) -->
+ <xsl:if test="@result='fail'">
+ <TD class="failed">
+ <div style="text-align: center; margin-left:auto; margin-right:auto;">
+ <xsl:value-of select="@result"/>
+ </div>
+ </TD>
+ <TD class="failuredetails">
+ <div class="details">
+ A test that was a known failure actually passed. Please check.
+ </div>
+ </TD>
+ </xsl:if>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:if test="@result='pass'">
+ <TD class="pass">
+ <div style="text-align: center; margin-left:auto; margin-right:auto;">
+ <xsl:value-of select="@result"/>
+ </div>
+ </TD>
+ <TD class="failuredetails"/>
+ </xsl:if>
+
+ <xsl:if test="@result='fail'">
+ <TD class="failed">
+ <div style="text-align: center; margin-left:auto; margin-right:auto;">
+ <xsl:value-of select="@result"/>
+ </div>
+ </TD>
+ <TD class="failuredetails">
+ <div class="details">
+ <xsl:value-of select="FailedScene/@message"/>
+ </div>
+ </TD>
+ </xsl:if>
+
+ <xsl:if test="@result='timeout'">
+ <TD class="timeout">
+ <div style="text-align: center; margin-left:auto; margin-right:auto;">
+ <xsl:value-of select="@result"/>
+ </div>
+ <TD class="failuredetails"></TD>
+ </TD>
+ </xsl:if>
+
+ <xsl:if test="@result='notExecuted'">
+ <TD class="notExecuted">
+ <div style="text-align: center; margin-left:auto; margin-right:auto;">
+ <xsl:value-of select="@result"/>
+ </div>
+ </TD>
+ <TD class="failuredetails"></TD>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+ </TR> <!-- finished with a row -->
+ </xsl:if>
+ </xsl:for-each> <!-- end test -->
+ </xsl:for-each> <!-- end test case -->
+ </TABLE>
+ </xsl:if>
+ </xsl:for-each> <!-- end test package -->
+ </DIV>
+ </xsl:template>
+
+ <!-- Take a delimited string and insert line breaks after a some number of elements. -->
+ <xsl:template name="formatDelimitedString">
+ <xsl:param name="string" />
+ <xsl:param name="numTokensPerRow" select="10" />
+ <xsl:param name="tokenIndex" select="1" />
+ <xsl:if test="$string">
+ <!-- Requires the last element to also have a delimiter after it. -->
+ <xsl:variable name="token" select="substring-before($string, ';')" />
+ <xsl:value-of select="$token" />
+ <xsl:text>&#160;</xsl:text>
+
+ <xsl:if test="$tokenIndex mod $numTokensPerRow = 0">
+ <br />
+ </xsl:if>
+
+ <xsl:call-template name="formatDelimitedString">
+ <xsl:with-param name="string" select="substring-after($string, ';')" />
+ <xsl:with-param name="numTokensPerRow" select="$numTokensPerRow" />
+ <xsl:with-param name="tokenIndex" select="$tokenIndex + 1" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/TestConfig.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/TestConfig.java
new file mode 100644
index 0000000..1b1dbbc
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/TestConfig.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed;
+
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * A singleton class that reads properties from afw-test.props and provides interfaces
+ * to get test configurations.
+ */
+public class TestConfig {
+ /**
+ * Property key of factory reset timeout in minutes.
+ */
+ public static final String KEY_FACTORY_RESET_TIMEOUT_MIN = "factory_reset_timeout_min";
+
+ /**
+ * Property key of Timeout size, used in the test configuration file.
+ */
+ public static final String KEY_TIMEOUT_SIZE = "timeout_size";
+
+ /**
+ * Mapping from timeout size to its integer value.
+ */
+ private static final Map<String, Integer> TIMEOUT_SIZE_MAPPING =
+ new HashMap<String, Integer>() {{
+ put("S", 1);
+ put("M", 2);
+ put("L", 3);
+ put("XL", 5);
+ put("XXL", 8);
+ }};
+
+ /**
+ * Property key of test timeout in minute, used in the test configuration file.
+ */
+ public static final String KEY_TEST_TIMEOUT_MIN = "test_timeout_min";
+
+ /**
+ * Singleton of this class.
+ */
+ private static TestConfig sTestConfig;
+
+ // Stores configurations loaded from a file.
+ private final Properties mProps;
+
+ /**
+ * Creates a new object by loading configurations from file.
+ *
+ * @param configFile test configuration file
+ */
+ private TestConfig(File configFile) throws IOException {
+ mProps = new Properties();
+ mProps.load(new FileInputStream(configFile));
+ }
+
+ /**
+ * Inits a {@link TestConfig} from a file.
+ *
+ * @param configFile configuration file to read
+ */
+ public static void init(File configFile) throws IOException {
+ sTestConfig = new TestConfig(configFile);
+ }
+
+ /**
+ * Gets the singleton of this class.
+ *
+ * @return {@link TestConfig} singleton, null if it's not initialized.
+ */
+ public static TestConfig getInstance() {
+ return sTestConfig;
+ }
+
+ /**
+ * Gets the property of a specific key.
+ *
+ * @param key property key
+ * @return propery value of the given key
+ * If the key doesn't exist in the configuration file, null will be returned;
+ * If the value of the key is empty, empty string will be returned
+ */
+ public String getProperty(String key) {
+ return mProps.getProperty(key);
+ }
+
+ /**
+ * Gets the property of a specific key.
+ *
+ * @param key property key
+ * @param defaultValue default value if given key is not found
+ * @return property value of the given key;
+ * if given key not found, {@link #defaultValue} is returned
+ */
+ public String getProperty(String key, String defaultValue) {
+ return mProps.getProperty(key, defaultValue);
+ }
+
+ /**
+ * Gets factory reset timeout in minute.
+ *
+ * @return timeout in minute or -1 if either timeout is not specified or invalid
+ */
+ public int getFactoryResetTimeoutMin() {
+ return getFactoryResetTimeoutMin(-1);
+ }
+
+ /**
+ * Gets factory reset timeout in minute.
+ *
+ * @param defaultValue default value if test timeout not specified
+ * @return factory reset timeout in minute
+ */
+ public int getFactoryResetTimeoutMin(int defaultValue) {
+ String value = mProps.getProperty(KEY_FACTORY_RESET_TIMEOUT_MIN);
+ if (value == null || value.isEmpty()) {
+ return defaultValue;
+ }
+
+ return Integer.valueOf(value);
+ }
+
+ /**
+ * Gets timeout size value from props file.
+ *
+ * <p>Possible size values are strings "S", "M", "L", "XL" or "XXL".</p>
+ *
+ * @return integer value of the timeout size.
+ */
+ public int getTimeoutSize() {
+ // Default to M
+ String timeoutSize = getProperty(KEY_TIMEOUT_SIZE, "M");
+ if (!TIMEOUT_SIZE_MAPPING.containsKey(timeoutSize)) {
+ CLog.w("Invalid timeout size, defaulting to M");
+ timeoutSize = "M";
+ }
+
+ return TIMEOUT_SIZE_MAPPING.get(timeoutSize);
+ }
+
+ /**
+ * Gets test timeout in minute.
+ *
+ * @return test timeout in minute or -1 if either timeout is not specified or invalid
+ */
+ public int getTestTimeoutMin() {
+ return getTestTimeoutMin(-1);
+ }
+
+ /**
+ * Gets test timeout in minute.
+ *
+ * @param defaultValue default value if test timeout not specified
+ * @return test timeout in minute
+ */
+ public int getTestTimeoutMin(int defaultValue) {
+ String value = mProps.getProperty(KEY_TEST_TIMEOUT_MIN);
+ if (value == null || value.isEmpty()) {
+ return defaultValue;
+ }
+
+ return Integer.valueOf(value);
+ }
+}
+
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestAdbRootOptionPreparer.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestAdbRootOptionPreparer.java
new file mode 100644
index 0000000..74c4480
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestAdbRootOptionPreparer.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed.targetprep;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.targetprep.ITargetCleaner;
+import com.android.tradefed.targetprep.TargetSetupError;
+
+/**
+ * A {@link ITargetPreparer} that sets enable-root option of the testing device.
+ */
+@OptionClass(alias = "afw-test-adb-root-option")
+public class AfwTestAdbRootOptionPreparer implements ITargetCleaner {
+
+ @Option(name = "enable-root-option",
+ description = "Whether to enable adb root option on the testing device.")
+ private boolean mEnableAdbRootOption = true;
+
+ /**
+ * Original adb root option.
+ */
+ private Boolean mOriginalRootOption = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
+ DeviceNotAvailableException {
+ mOriginalRootOption = device.getOptions().isEnableAdbRoot();
+ device.getOptions().setEnableAdbRoot(mEnableAdbRootOption);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+ throws DeviceNotAvailableException {
+ if (mOriginalRootOption != null) {
+ device.getOptions().setEnableAdbRoot(mOriginalRootOption);
+ }
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestAppUninstaller.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestAppUninstaller.java
new file mode 100644
index 0000000..599e993
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestAppUninstaller.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed.targetprep;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.ITargetCleaner;
+import com.android.tradefed.targetprep.TargetSetupError;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A {@link ITargetPreparer} that helps uninstalling apps before or after the test.
+ */
+@OptionClass(alias="afw-test-app-uninstaller")
+public class AfwTestAppUninstaller implements ITargetCleaner {
+
+ @Option(name = "before-test", description = "packages to uninstall before test")
+ private List<String> mPackageNamesBeforeTest = new LinkedList<>();
+
+ @Option(name = "after-test", description = "packages to uninstall after test")
+ private List<String> mPackageNamesAfterTest = new LinkedList<>();
+
+ @Option(name = "reboot-before-uninstall",
+ description = "if reboot the device before uinstallation")
+ private boolean mRebootBeforeUninstall = false;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo)
+ throws TargetSetupError, DeviceNotAvailableException {
+ String result = uninstall(device, mPackageNamesBeforeTest);
+
+ if (result != null) {
+ throw new TargetSetupError(String.format("Failed to uninstall %s on %s",
+ result, device.getSerialNumber()));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+ throws DeviceNotAvailableException {
+
+ String result = uninstall(device, mPackageNamesAfterTest);
+
+ if (result != null) {
+ throw new RuntimeException(String.format("Failed to uninstall %s on %s",
+ result, device.getSerialNumber()));
+ }
+ }
+
+ /**
+ * Uninstalls list of packages.
+ *
+ * @param device the testing device
+ * @param packages packages to uninstall
+ * @return {@code null} if all given packages are uninstalled, or the name of the first package
+ * that failed to be uninstalled
+ */
+ private String uninstall(ITestDevice device, List<String> packages)
+ throws DeviceNotAvailableException {
+ Set<String> installedPackages = device.getInstalledPackageNames();
+ installedPackages.retainAll(packages);
+
+ if (installedPackages.isEmpty()) {
+ return null;
+ }
+
+ if (mRebootBeforeUninstall) {
+ device.reboot();
+ }
+
+ for (String pkgName : installedPackages) {
+ String result = device.uninstallPackage(pkgName);
+ if (result != null) {
+ return pkgName;
+ }
+
+ CLog.i(String.format("Successfully uninstalled %s on %s",
+ pkgName, device.getSerialNumber()));
+ }
+
+ return null;
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEncryptDevice.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEncryptDevice.java
new file mode 100644
index 0000000..7de4ace
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEncryptDevice.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed.targetprep;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.device.CollectingOutputReceiver;
+import com.android.ddmlib.NullOutputReceiver;
+
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link ITargetPreparer} that encrypts the testing device.
+ *
+ * <p>Requires a device where 'adb root' is possible, typically a userdebug build type.</p>
+ *
+ * <p>If the device is already encrypted, nothing will be done. </p>
+ */
+@OptionClass(alias = "afw-test-encrypt-device")
+public class AfwTestEncryptDevice extends AfwTestTargetPreparer implements ITargetPreparer {
+
+ @Option(name = "inplace",
+ description = "Whether to encrypt the device inplace or by wipe")
+ private Boolean mInplace = true;
+
+ @Option(name = "timeout-secs",
+ description = "Timeout in seconds.")
+ private Long mTimeoutSecs = TimeUnit.MINUTES.toSeconds(5);
+
+ @Option(name = "attempts",
+ description = "Number of attempts to encrypt the device")
+ private int mAttempts = 3;
+
+ /** Adb command timeout, in milliseconds. */
+ private static final int CMD_TIMEOUT = (int)TimeUnit.MINUTES.toMillis(2);
+
+ /** The password for encrypting and decrypting the device. */
+ private static final String ENCRYPTION_PASSWORD = "android";
+
+ /** Encrypting with inplace can take up to 2 hours. */
+ private static final int ENCRYPTION_INPLACE_TIMEOUT_MIN = 2 * 60;
+
+ /** Encrypting with wipe can take up to 20 minutes. */
+ private static final long ENCRYPTION_WIPE_TIMEOUT_MIN = 20;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
+ DeviceNotAvailableException {
+
+ for (int i = 0; i < mAttempts; ++i) {
+ CLog.i(String.format("Encrypting device %s: #%d", device.getSerialNumber(), i+1));
+
+ // Exit from loop if the device is encrypted successfully
+ if (encryptDevice(device)) {
+ break;
+ }
+
+ // The device may become unavailable, wait for it.
+ device.waitForDeviceAvailable(TimeUnit.SECONDS.toMillis(10));
+ }
+
+ // Fail the target preparer if encryption failed
+ if (!device.isDeviceEncrypted()) {
+ throw new TargetSetupError(
+ "Failed to encrypt device " + device.getSerialNumber());
+ }
+
+ device.waitForDeviceAvailable(TimeUnit.SECONDS.toMillis(mTimeoutSecs) * getTimeoutSize());
+ CLog.i(String.format("Device %s is encrypted successfully", device.getSerialNumber()));
+
+ postDeviceEncryption(device);
+ }
+
+ /**
+ * Encrypts the device.
+ *
+ * @param device test device
+ * @return {@code true} if device was encrypted successfully; {@code false} otherwise
+ */
+ protected boolean encryptDevice(ITestDevice device) throws DeviceNotAvailableException {
+ if (!device.isEncryptionSupported()) {
+ throw new UnsupportedOperationException(String.format("Can't encrypt device %s: "
+ + "encryption not supported", device.getSerialNumber()));
+ }
+
+ if (device.isDeviceEncrypted()) {
+ CLog.d("Device %s is already encrypted, skipping...", device.getSerialNumber());
+ return true;
+ }
+
+ String encryptMethod;
+ long timeout;
+ if (mInplace) {
+ encryptMethod = "inplace";
+ timeout = ENCRYPTION_INPLACE_TIMEOUT_MIN;
+ } else {
+ encryptMethod = "wipe";
+ timeout = ENCRYPTION_WIPE_TIMEOUT_MIN;
+ }
+
+ CLog.i("Encrypting device %s via %s", device.getSerialNumber(), encryptMethod);
+
+ // enable crypto takes one of the following formats:
+ // cryptfs enablecrypto <wipe|inplace> <passwd>
+ // cryptfs enablecrypto <wipe|inplace> default|password|pin|pattern [passwd]
+ // Try the first one first, if it outputs "500 <process_id> Usage: ...", try the second.
+ CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+ String command = String.format("vdc cryptfs enablecrypto %s \"%s\"", encryptMethod,
+ ENCRYPTION_PASSWORD);
+ device.executeShellCommand(command, receiver, timeout, TimeUnit.MINUTES, 1);
+ if (receiver.getOutput().split(":")[0].matches("500 \\d+ Usage")) {
+ command = String.format("vdc cryptfs enablecrypto %s default", encryptMethod);
+ device.executeShellCommand(command,
+ new NullOutputReceiver(), timeout, TimeUnit.MINUTES, 1);
+ }
+
+ device.waitForDeviceNotAvailable(CMD_TIMEOUT);
+ device.waitForDeviceOnline();
+
+ return device.isDeviceEncrypted();
+ }
+
+ /**
+ * Actions after encryption.
+ *
+ * @param device test device
+ */
+ protected void postDeviceEncryption(ITestDevice device) throws DeviceNotAvailableException {
+ // By default do nothing.
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEnvDumper.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEnvDumper.java
new file mode 100644
index 0000000..226dbb7
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEnvDumper.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed.targetprep;
+
+import com.google.common.base.Joiner;
+
+import com.android.afwtest.tradefed.utils.TimeUtil;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.PackageInfo;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.ITargetCleaner;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.util.StreamUtil;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A target preparer that dumps testing environment info to a text file.
+ *
+ * <p>
+ * The file is saved at:
+ * ${CTS_ROOT}/android-cts/repository/logs/{given-prefix}_EnvDump_{TimeStamp}.txt.
+ * Generally {given-prefix} should be the test apk file name.
+ * </p>
+ */
+@OptionClass(alias = "afw-test-env-dumper")
+public class AfwTestEnvDumper extends AfwTestTargetPreparer implements ITargetCleaner {
+
+ private static final String DUMP_FILE_NAME_SUFFIX = "EnvDump";
+ private static final String GET_ANDROID_BUILD_FINGERPRINT_CMD =
+ "getprop ro.build.fingerprint";
+
+ @Option(name = "file-name-prefix",
+ description = "Dump file name prefix, suggested to be test apk file name",
+ mandatory = true)
+ private String mFileNamePrefix = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo)
+ throws TargetSetupError,
+ DeviceNotAvailableException {
+ // Do nothing, dump after the test only
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+ throws DeviceNotAvailableException {
+
+ try {
+ String str = getEnv(device);
+
+ // Creates the dump file
+ File dumpFile = getDumpFile(buildInfo);
+
+ OutputStream os = new FileOutputStream(dumpFile);
+ os.write(str.getBytes());
+ StreamUtil.flushAndCloseStream(os);
+
+ CLog.i(String.format("Dumped file: %s", dumpFile.getAbsolutePath()));
+ } catch (IOException exception) {
+ CLog.e("Failed to dump app versions to file", e);
+ }
+ }
+
+ /**
+ * Gets environment configurations as string.
+ *
+ * @param device testing device
+ * @return environment configuration as string
+ */
+ protected String getEnv(ITestDevice device) throws DeviceNotAvailableException {
+ String androidBuild = getAndroidBuildFingerprint(device);
+ String appVersions = getAppVersions(device);
+
+ return Joiner.on(System.lineSeparator()).join(androidBuild, appVersions);
+ }
+
+ /**
+ * Gets device's Android build fingerprint.
+ *
+ * @param device testing device
+ * @return device's Android build fingerprint.
+ * @throws DeviceNotAvailableException if connection to the device was lost during execution of
+ * the command.
+ */
+ private String getAndroidBuildFingerprint(ITestDevice device)
+ throws DeviceNotAvailableException {
+ return device.executeShellCommand(GET_ANDROID_BUILD_FINGERPRINT_CMD);
+ }
+
+ /**
+ * Gets a {@link File} to dump environment info.
+ *
+ * @param buildInfo reference to {@link IBuildInfo}
+ * @return {@link File} object of a unique file
+ */
+ private File getDumpFile(IBuildInfo buildInfo) throws FileNotFoundException {
+ return new File(getCtsBuildHelper(buildInfo).getLogsDir(),
+ String.format("%s_%s_%s.txt",
+ mFileNamePrefix,
+ DUMP_FILE_NAME_SUFFIX,
+ TimeUtil.getResultTimestamp()));
+ }
+
+ /**
+ * Gets versions of all {@link App} as a string.
+ *
+ * @param device reference to {@link ITestDevice}
+ * @return versions of all {@link App} as string
+ */
+ private String getAppVersions(ITestDevice device) throws DeviceNotAvailableException {
+ Set<String> installPkgs = device.getInstalledPackageNames();
+
+ List<String> appVersions = new ArrayList<String>();
+
+ for (Map.Entry<String, String> pkg: getPackagesToDump().entrySet()) {
+ String pkgName = pkg.getKey();
+ String appName = pkg.getValue();
+ // Gets versions of install app only
+ if (installPkgs.contains(pkgName)) {
+ PackageInfo pkgInfo = device.getAppPackageInfo(pkgName);
+ String appVer = String.format("%s: Ver %s", appName, pkgInfo.getVersionName());
+
+ // Log it to tradefed console for debugging purpose
+ CLog.i(appVer);
+
+ appVersions.add(appVer);
+ }
+ }
+
+ return Joiner.on("\n").join(appVersions.iterator());
+ }
+
+
+ /**
+ * Gets set of packages whose info should be dumped.
+ *
+ * @return a {@link Map} with key=full package name and value=app representation name
+ */
+ protected Map<String, String> getPackagesToDump() {
+ Map<String, String> pkgs = new HashMap<String, String>();
+
+ pkgs.put("com.google.android.gms", "Google Mobile Service");
+ pkgs.put("com.android.managedprovisioning", "Managed Provisioning");
+ pkgs.put("com.afwsamples.testdpc", "TestDPC");
+
+ return pkgs;
+ }
+}
+
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestFactoryReset.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestFactoryReset.java
new file mode 100644
index 0000000..5ee82fa
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestFactoryReset.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed.targetprep;
+
+import com.android.afwtest.tradefed.utils.TimeUtil;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.targetprep.TargetSetupError;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link ITargetPreparer} that factory resets the device by sending an intent
+ * to package com.android.afwtest.deviceadmin (AfwThDeviceAdmin.apk).
+ *
+ * <p>Requires a device where 'adb' is available after factory reset, typically
+ * a userdebug build type.
+ * </p>
+ */
+@OptionClass(alias = "afw-test-factory-reset")
+public class AfwTestFactoryReset extends AfwTestTargetPreparer implements ITargetPreparer {
+
+ @Option(name = "use-fastboot",
+ description = "Whether to use 'fastboot format' to do factory reset")
+ private boolean mUseFastboot = false;
+
+ @Option(name = "timeout-secs",
+ description = "Factory reset timeout, in seconds.")
+ private Long mTimeoutSecs = TimeUnit.MINUTES.toSeconds(15);
+
+ @Option(name = "attempts",
+ description = "Number of attempts to start factory reset")
+ private int mAttempts = 2;
+
+ @Option (name = "wipe-protection-data",
+ description = "If factory reset protection data should be wiped")
+ private boolean mWipeProtectionData = false;
+
+ // Command string to change system locale to en_US
+ private static final String CHANGE_LOCALE_TO_EN_US_CMD =
+ "am instrument -w -e locale en_US com.android.afwtest.systemutil/.ChangeLocale";
+
+ // After sending the factory reset intent out, time allowed for
+ // the device to start rebooting
+ private static final long REBOOT_WAIT_TIME_MS = TimeUnit.MINUTES.toMillis(2);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
+ DeviceNotAvailableException {
+ if (getUseFastboot(device)) {
+ CLog.i("Factory resetting device via fastboot");
+ wipeDevice(device);
+ } else {
+ CLog.i("Factory resetting device via DeviceAdmin app");
+ doFactoryReset(device);
+ }
+
+ // Wait for the device to be available
+ long factoryResetTimeoutMs = getFactoryResetTimeout();
+ CLog.i(String.format("Waiting for factory reset to finish: timeout=%d seconds",
+ factoryResetTimeoutMs / 1000));
+ device.waitForDeviceAvailable(factoryResetTimeoutMs);
+
+ // re-enable adb root
+ enableAdbRoot(device);
+
+ CLog.i(String.format("Device %s is factory reset successfully", device.getSerialNumber()));
+
+ postFactoryReset(device);
+ }
+
+ /**
+ * Gets if configured to use fastboot to do factory reset.
+ *
+ * @return {@code true} if use fastboot, {@code false} otherwise
+ */
+ protected boolean getUseFastboot(ITestDevice device) throws DeviceNotAvailableException {
+ return mUseFastboot;
+ }
+
+ /**
+ * Executes actions after factory reset.
+ */
+ protected void postFactoryReset(ITestDevice device)
+ throws DeviceNotAvailableException, TargetSetupError {
+ // Sync time with the host
+ TimeUtil.syncHostTime(device);
+
+ CLog.i("Disabling auto-rotate");
+ device.executeShellCommand("settings put system accelerometer_rotation 0");
+
+ // Change system locale to en_US
+ CLog.i("Changing system locale to en_US");
+ String result = device.executeShellCommand(CHANGE_LOCALE_TO_EN_US_CMD);
+ if (result == null || !result.contains("result=SUCCESS")) {
+ CLog.e(result);
+ } else {
+ CLog.i("System locale changed to en_US");
+ }
+ }
+
+ /**
+ * Gets configured factory reset time out, in ms.
+ *
+ * @return factory reset timeout, in ms
+ */
+ private long getFactoryResetTimeout() {
+ long result = 0;
+ // Get from afw-test.props file
+ if (getTestConfig() != null) {
+ result = TimeUnit.MINUTES.toMillis(getTestConfig().getFactoryResetTimeoutMin(0));
+ }
+ // If not specified in afw-test.props, use mTimeoutSecs
+ if (result == 0) {
+ result = TimeUnit.SECONDS.toMillis(mTimeoutSecs);
+ }
+
+ return result;
+ }
+
+ /**
+ * Does factory reset with device admin.
+ *
+ * @param device test device
+ */
+ private void doFactoryReset(ITestDevice device)
+ throws DeviceNotAvailableException, TargetSetupError {
+ int count = 0;
+ boolean factoryResetStarted;
+ do {
+ count++;
+ CLog.i(String.format("Factory resetting device %s, #%d",
+ device.getSerialNumber(), count));
+
+ CLog.i(device.executeShellCommand(getFactoryResetCommand()));
+ factoryResetStarted = device.waitForDeviceNotAvailable(REBOOT_WAIT_TIME_MS);
+ } while (count < mAttempts && !factoryResetStarted);
+
+ // Wait for the device to reboot
+ if (!factoryResetStarted) {
+ throw new TargetSetupError(
+ "Failed to start factory reset on device " + device.getSerialNumber());
+ }
+ }
+
+ /**
+ * Gets factory reset command string.
+ */
+ private String getFactoryResetCommand() {
+ String result = "am start -n com.android.afwtest.deviceadmin/.FactoryResetActivity";
+ if (mWipeProtectionData) {
+ result += " --ez afwtest.wipe.protection.data true";
+ }
+ return result;
+ }
+}
+
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestGoogleAccountRemover.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestGoogleAccountRemover.java
new file mode 100644
index 0000000..aee6e9d
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestGoogleAccountRemover.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed.targetprep;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.ITargetCleaner;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.util.RunUtil;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Target preparer to remove Google accounts.
+ *
+ * <p>Requires a device where 'adb root' is possible, typically a userdebug build.</p>
+ *
+ * <p>Requires com.anroid.afwtest.systemutil to be pre-installed as privileged app.</p>
+ */
+@OptionClass(alias = "afw-test-google-account-remover")
+public class AfwTestGoogleAccountRemover extends AfwTestTargetPreparer implements ITargetCleaner {
+
+ /**
+ * Package name of the system util app.
+ */
+ private static final String SYSTEM_UTIL_PKG_NAME = "com.android.afwtest.systemutil";
+
+ /**
+ * Action to remove Google account.
+ */
+ private static final String REMOVE_GOOGLE_ACCOUNT_CLASS = ".RemoveGoogleAccount";
+
+ /**
+ * Waiting time for system util app.
+ */
+ private static final long SYSTEM_UTIL_PKG_WAIT_TIME_MS = TimeUnit.MINUTES.toMillis(3);
+
+ @Option(name = "google-account",
+ description = "Google accounts to remove, remove all Google accounts if not none given")
+ private Collection<String> mGoogleAccounts = new ArrayList<String>();
+
+ @Option(name = "remove-before-test",
+ description = "Remove given Google account or all Google accounts before test.")
+ private boolean mRemoveBeforeTest = true;
+
+ @Option(name = "remove-after-test",
+ description = "Remove given Google account or all Google accounts after the test.")
+ private boolean mRemoveAfterTest = true;
+
+ @Option(name = "attempts",
+ description = "Number of attempts")
+ private int mAttempts = 3;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo)
+ throws TargetSetupError, DeviceNotAvailableException {
+ if (waitForAppPkgInfo(device, SYSTEM_UTIL_PKG_NAME, SYSTEM_UTIL_PKG_WAIT_TIME_MS) == null) {
+ throw new TargetSetupError(String.format("%s not installed successfully.",
+ SYSTEM_UTIL_PKG_NAME));
+ }
+
+ if (mRemoveBeforeTest) {
+ doRemoval(device);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+ throws DeviceNotAvailableException {
+ if (waitForAppPkgInfo(device, SYSTEM_UTIL_PKG_NAME, SYSTEM_UTIL_PKG_WAIT_TIME_MS) == null) {
+ throw new RuntimeException(String.format("%s not installed successfully.",
+ SYSTEM_UTIL_PKG_NAME));
+ }
+
+ if (mRemoveAfterTest) {
+ try {
+ doRemoval(device);
+ } catch (TargetSetupError ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+
+ /**
+ * Help function to remove requested Google accounts.
+ *
+ * @param device test device
+ */
+ private void doRemoval(ITestDevice device)
+ throws TargetSetupError, DeviceNotAvailableException {
+ if (mGoogleAccounts.isEmpty()) {
+ removeAllAccounts(device);
+ } else {
+ for (String account : mGoogleAccounts) {
+ removeAccount(device, account);
+ }
+ }
+ }
+
+ /**
+ * Remove all Google accounts.
+ *
+ * @param device test device
+ */
+ private void removeAllAccounts(ITestDevice device)
+ throws TargetSetupError, DeviceNotAvailableException {
+ String removeCmd = String.format("am instrument -w %s/%s",
+ SYSTEM_UTIL_PKG_NAME,
+ REMOVE_GOOGLE_ACCOUNT_CLASS);
+
+ if (executeRemoveAccountCommand(device, removeCmd)) {
+ CLog.i(String.format("All Google accounts were removed from device %s.",
+ device.getSerialNumber()));
+ } else {
+ throw new TargetSetupError(
+ String.format("Failed to remove all Google accounts from device %s.",
+ device.getSerialNumber()));
+ }
+ }
+
+ /**
+ * Remove a Google account.
+ *
+ * @param device test device
+ * @param account account to remove
+ */
+ private void removeAccount(ITestDevice device, String account)
+ throws TargetSetupError, DeviceNotAvailableException {
+ String removeCmd = String.format("am instrument -w -e account \"%s\" %s/%s",
+ account, SYSTEM_UTIL_PKG_NAME, REMOVE_GOOGLE_ACCOUNT_CLASS);
+ if (executeRemoveAccountCommand(device, removeCmd)) {
+ CLog.i(String.format("Google account %s was removed from device %s.",
+ account, device.getSerialNumber()));
+ } else {
+ throw new TargetSetupError(
+ String.format("Failed to remove Google account %s from device %s.",
+ account, device.getSerialNumber()));
+ }
+ }
+
+ /**
+ * Executes shell command to remove Google account.
+ *
+ * @param device test device
+ * @param cmd command to execute
+ * @return {@code true} if success, {@code false} otherwise
+ */
+ private boolean executeRemoveAccountCommand(ITestDevice device, String cmd)
+ throws DeviceNotAvailableException {
+ CLog.v(cmd);
+ for (int i = 0; i < mAttempts; ++i) {
+ CLog.i(String.format("Removing account on device %s, #%d",
+ device.getSerialNumber(), i + 1));
+
+ String result = device.executeShellCommand(cmd);
+ // expected result format is (on success):
+ //
+ // INSTRUMENTATION_RESULT: result=SUCCESS
+ // INSTRUMENTATION_CODE: -1
+ //
+ if (result != null && result.contains("result=SUCCESS")) {
+ return true;
+ } else {
+ CLog.e(String.format("Failed to remove account from device %s: %s.",
+ device.getSerialNumber(), result));
+ // Sleep for 15 seconds before next attempt
+ RunUtil.getDefault().sleep(TimeUnit.SECONDS.toMillis(15));
+ }
+ }
+
+ // OK, failed after all attempts
+ return false;
+ }
+}
+
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestManagedProfileCreator.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestManagedProfileCreator.java
new file mode 100644
index 0000000..1bf5a92
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestManagedProfileCreator.java
@@ -0,0 +1,89 @@
+package com.android.afwtest.tradefed.targetprep;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.ITargetCleaner;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.targetprep.TargetSetupError;
+
+/**
+ * A {@link ITargetPreparer} that creates a managed profile in the testing device.
+ */
+@OptionClass(alias = "afw-test-create-managed-profile")
+public class AfwTestManagedProfileCreator extends AfwTestTargetPreparer
+ implements ITargetCleaner {
+
+ @Option(name = "remove-after-test",
+ description = "Remove work profile after test.")
+ private boolean mRemoveAfterTest = true;
+
+ @Option(name = "profile-owner-component",
+ description = "Profile owner component to set; optional")
+ private String mProfileOwnerComponent = null;
+
+ @Option(name = "profile-owner-apk",
+ description = "Profile owner apk path; optional")
+ private String mProfileOwnerApk = null;
+
+ /** UserID for user in managed profile.*/
+ private int mManagedProfileUserId;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo)
+ throws TargetSetupError, DeviceNotAvailableException {
+
+ String pmCommand = "pm create-user --profileOf 0 --managed "
+ + "TestProfile_" + System.currentTimeMillis();
+ String pmCommandOutput = device.executeShellCommand(pmCommand);
+
+ // Extract the id of the new user.
+ String[] pmCmdTokens = pmCommandOutput.split("\\s+");
+ if (!pmCmdTokens[0].contains("Success:")) {
+ throw new TargetSetupError("Managed profile creation failed.");
+ }
+ mManagedProfileUserId = Integer.parseInt(pmCmdTokens[pmCmdTokens.length-1]);
+
+ // Start managed profile user.
+ device.startUser(mManagedProfileUserId);
+
+ CLog.i(String.format("New user created: %d", mManagedProfileUserId));
+
+ if (mProfileOwnerComponent != null && mProfileOwnerApk != null) {
+ device.installPackageForUser(
+ getApk(buildInfo, mProfileOwnerApk), true, mManagedProfileUserId);
+ String command = String.format("dpm set-profile-owner --user %d %s",
+ mManagedProfileUserId, mProfileOwnerComponent);
+ String commandOutput = device.executeShellCommand(command);
+ String[] cmdTokens = commandOutput.split("\\s+");
+ if (!cmdTokens[0].contains("Success:")) {
+ throw new RuntimeException(String.format("Fail to setup %s as profile owner.",
+ mProfileOwnerComponent));
+ }
+
+ CLog.i(String.format("%s was set as profile owner of user %d",
+ mProfileOwnerComponent, mManagedProfileUserId));
+ }
+
+ // Reboot device to create the apps in managed profile.
+ device.reboot();
+ device.waitForDeviceAvailable();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void tearDown(ITestDevice testDevice, IBuildInfo buildInfo, Throwable throwable)
+ throws DeviceNotAvailableException {
+ if (mRemoveAfterTest) {
+ testDevice.removeUser(mManagedProfileUserId);
+ }
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPrivAppInstaller.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPrivAppInstaller.java
new file mode 100644
index 0000000..303b31d
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPrivAppInstaller.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed.targetprep;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.ITargetCleaner;
+import com.android.tradefed.targetprep.TargetSetupError;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Target preparer to install an app as privileged app by pushing it to /system/priv-app.
+ *
+ * <p>Requires a device where 'adb root' is possible, typically a userdebug build.</p>
+ */
+public class AfwTestPrivAppInstaller extends AfwTestTargetPreparer implements ITargetCleaner {
+
+ /**
+ * System folder for privileged apps and forlder for permissions.
+ */
+ private static final String PRIV_APP_DIR = "/system/priv-app";
+ private static final String PERMISSIONS_APP_DIR = "/etc/permissions";
+
+ @Option(name = "app-filename",
+ description = "app to install")
+ private Collection<String> mApps = new ArrayList<String>();
+
+ @Option(name = "permission-file",
+ description = "Permissions file required for the app")
+ private Collection<String> mPermissionsFiles = new ArrayList<String>();
+
+ @Option(name = "cleanup",
+ description = "true to delete the apk in teardown")
+ private boolean mCleanup = true;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo)
+ throws TargetSetupError, DeviceNotAvailableException {
+
+ if (mApps.isEmpty()) {
+ return;
+ }
+
+ // Enable adb root and remount system partition
+ enableAdbRoot(device);
+ device.remountSystemWritable();
+
+ for (String app : mApps) {
+ String privApp = String.format("%s/%s", PRIV_APP_DIR, app);
+ if (device.pushFile(getApk(buildInfo, app), privApp)) {
+ CLog.i(String.format("Privileged app installed: %s", privApp));
+ } else {
+ throw new TargetSetupError(String.format("Failed to install %s", privApp));
+ }
+ }
+
+ // Copy permission files (Required for API 25)
+ for (String file : mPermissionsFiles) {
+ String permissionFile = String.format("%s/%s", PERMISSIONS_APP_DIR, file);
+ if (device.pushFile(getApk(buildInfo, file), permissionFile)) {
+ CLog.i(String.format("Permissions file copied: %s", permissionFile));
+ } else {
+ throw new TargetSetupError(String.format("Failed to copy %s", permissionFile));
+ }
+ }
+
+ // Reboot to activate the installed apps
+ device.reboot();
+ device.waitForDeviceAvailable();
+ // Enable adb root again
+ enableAdbRoot(device);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+ throws DeviceNotAvailableException {
+ if (!mCleanup || mApps.isEmpty()) {
+ return;
+ }
+
+ if (!device.enableAdbRoot()) {
+ throw new RuntimeException(
+ String.format("Failed to enable adb root: %s", device.getSerialNumber()));
+ }
+
+ device.remountSystemWritable();
+
+ for (String app : mApps) {
+ device.executeShellCommand(String.format("rm %s/%s", PRIV_APP_DIR, app));
+ }
+ }
+}
+
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPullExternalFile.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPullExternalFile.java
new file mode 100644
index 0000000..7a068b2
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPullExternalFile.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed.targetprep;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.ITargetCleaner;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.util.FileUtil;
+
+import java.io.File;
+
+/**
+ * A target cleaner to copy file from the external storage of the device to host after the
+ * test execution.
+ */
+@OptionClass(alias = "afw-test-pull-external-file")
+public class AfwTestPullExternalFile extends AfwTestTargetPreparer implements ITargetCleaner {
+
+ @Option(name = "remote-file",
+ description = "File to copy from external storage.",
+ mandatory = true)
+ private String mRemoteFile = null;
+
+ @Option(name = "local-file",
+ description = "File path relative to android-cts/repository.",
+ mandatory = true)
+ private String mLocalFile = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
+ DeviceNotAvailableException {
+ // Do nothing.
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+ throws DeviceNotAvailableException {
+
+ try {
+ File localFile = new File(getRepositoryDir(buildInfo), mLocalFile);
+ if (localFile.getParentFile().mkdirs()) {
+ localFile.createNewFile();
+ }
+ FileUtil.copyFile(device.pullFileFromExternal(mRemoteFile), localFile);
+ } catch (DeviceNotAvailableException e1) {
+ throw e1;
+ } catch (Exception e2) {
+ // Log error but not failing the target preparer.
+ CLog.e(String.format("Failed to copy remote file.", e2));
+ }
+ }
+}
+
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestSyncTime.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestSyncTime.java
new file mode 100644
index 0000000..8f4fbcd
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestSyncTime.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed.targetprep;
+
+import com.android.afwtest.tradefed.utils.TimeUtil;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.targetprep.TargetSetupError;
+
+/**
+ * A {@link ITargetPreparer} that syncs device time with the running host.
+ *
+ * <p>Requires a device where 'adb root' is possible, typically a userdebug build</p>
+ */
+@OptionClass(alias = "afw-test-sync-time")
+public class AfwTestSyncTime extends AfwTestTargetPreparer implements ITargetPreparer {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
+ DeviceNotAvailableException {
+ enableAdbRoot(device);
+ TimeUtil.syncHostTime(device);
+ }
+}
+
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestTargetPreparer.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestTargetPreparer.java
new file mode 100644
index 0000000..684fe1f
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestTargetPreparer.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed.targetprep;
+
+import com.android.afwtest.tradefed.TestConfig;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.PackageInfo;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.RunUtil;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Abstract class to keep common functionality.
+ */
+public abstract class AfwTestTargetPreparer {
+
+ private CompatibilityBuildHelper mBuildHelper;
+ private Long mTimeoutSecs = TimeUnit.MINUTES.toSeconds(5);
+
+ /**
+ * Gets an instance of {@link CompatibilityBuildHelper}.
+ *
+ * @param buildInfo reference to {@link IBuildInfo}
+ * @return reference to {@link CompatibilityBuildHelper} instance
+ */
+ protected CompatibilityBuildHelper getCtsBuildHelper(IBuildInfo buildInfo) {
+ if (mBuildHelper == null) {
+ mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+ }
+ return mBuildHelper;
+ }
+
+ /**
+ * Gets repository directory.
+ *
+ * @param buildInfo reference to {@link IBuildInfo}
+ * @return File path to repository folder.
+ */
+ protected File getRepositoryDir(IBuildInfo buildInfo) throws FileNotFoundException {
+ return getCtsBuildHelper(buildInfo).getDir();
+ }
+
+ /**
+ * Gets apk file from testcases dir.
+ *
+ * @param buildInfo reference to {@link IBuildInfo}
+ * @param fileName apk file name
+ * @return {@link File} of the apk
+ */
+ protected File getApk(IBuildInfo buildInfo, String fileName) throws TargetSetupError {
+ try {
+ return new File(getCtsBuildHelper(buildInfo).getTestsDir(), fileName);
+ } catch (FileNotFoundException e) {
+ throw new TargetSetupError(String.format("Couldn't get apk: %s", fileName), e);
+ }
+ }
+
+ /**
+ * Enable adb root.
+ *
+ * @param device test device
+ * @throws TargetSetupError if failed to enable adb root
+ * @throws DeviceNotAvailableException if test device becomes unavailable
+ */
+ protected void enableAdbRoot(ITestDevice device)
+ throws TargetSetupError, DeviceNotAvailableException {
+ // Enable adb root
+ if (!device.enableAdbRoot()) {
+ throw new TargetSetupError(
+ String.format("Failed to enable adb root: %s", device.getSerialNumber()));
+ }
+ }
+
+ /**
+ * Waits and gets certain app package info.
+ *
+ * @param device test device
+ * @param pkgName app package name
+ * @param timeoutMs timeout in ms
+ * @return App package info of given package if found, null otherwise
+ * @throws DeviceNotAvailableException if test device becomes unavailable
+ */
+ protected PackageInfo waitForAppPkgInfo(ITestDevice device, String pkgName, long timeoutMs)
+ throws DeviceNotAvailableException {
+
+ long threadSleepTimeMs = TimeUnit.SECONDS.toMillis(5);
+
+ PackageInfo pkgInfo = device.getAppPackageInfo(pkgName);
+
+ while (pkgInfo == null && timeoutMs > 0) {
+ RunUtil.getDefault().sleep(threadSleepTimeMs);
+ timeoutMs -= threadSleepTimeMs;
+
+ pkgInfo = device.getAppPackageInfo(pkgName);
+ }
+
+ return pkgInfo;
+ }
+
+ /**
+ * Gets {@link TestConfig} instance.
+ *
+ * @return {@link TestConfig} instance or null if not found;
+ */
+ protected TestConfig getTestConfig() {
+ return TestConfig.getInstance();
+ }
+
+ /**
+ * Gets timeout size from config file.
+ *
+ * @return timeout size in integer
+ */
+ protected int getTimeoutSize() {
+ TestConfig testConfig = TestConfig.getInstance();
+
+ // Target prepares should still work without test config file;
+ if (testConfig == null) {
+ return 1;
+ }
+
+ return TestConfig.getInstance().getTimeoutSize();
+ }
+
+ /**
+ * Wipes device via fastboot.
+ *
+ * <p>
+ * Refers to com.android.tradefed.targetprep.DeviceWiper
+ * </p>
+ *
+ * @param device test device
+ */
+ protected void wipeDevice(ITestDevice device)
+ throws DeviceNotAvailableException, TargetSetupError {
+
+ CLog.i(String.format("Wiping device %s".format(device.getSerialNumber())));
+
+ device.rebootIntoBootloader();
+ fastbootFormat(device, "cache");
+ fastbootFormat(device, "userdata");
+ device.executeFastbootCommand("reboot");
+ }
+
+
+ /**
+ * Performs fastboot erase/format operation on certain partition
+ *
+ * @param device test device
+ * @param partition android partition, e.g. userdata, cache, system
+ */
+ protected void fastbootFormat(ITestDevice device, String partition)
+ throws DeviceNotAvailableException, TargetSetupError {
+
+ CLog.i(String.format("Attempting: fastboot format %s", partition));
+ CommandResult r = device.executeLongFastbootCommand("format", partition);
+ if (r.getStatus() != CommandStatus.SUCCESS) {
+ throw new TargetSetupError(
+ String.format("format %s failed: %s", partition, r.getStderr()));
+ }
+ }
+
+ /**
+ * "Soft" reboot the device by adb shell stop/start.
+ *
+ * @param device test device
+ */
+ protected void softReboot(ITestDevice device)
+ throws DeviceNotAvailableException, TargetSetupError {
+
+ CLog.i(String.format("Soft reboot device %s".format(device.getSerialNumber())));
+
+ device.executeShellCommand("stop");
+ device.executeShellCommand("start");
+ device.waitForDeviceAvailable();
+ // enableAdbRoot() will check if device.isEnableRoot()
+ device.enableAdbRoot();
+ }
+}
+
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestUserRemover.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestUserRemover.java
new file mode 100644
index 0000000..c21c8e6
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestUserRemover.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed.targetprep;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.ITargetCleaner;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.util.RunUtil;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A target preparer that can remove all non-primary users (user id not 0).
+ */
+@OptionClass(alias = "afw-test-user-remover")
+public class AfwTestUserRemover implements ITargetCleaner {
+
+ @Option(name = "remove-users-before-test",
+ description = "Remove all non-primary users before the test.")
+ private boolean mRemoveUsersBeforeTest = false;
+
+ @Option(name = "remove-users-after-test",
+ description = "Remove all non-primary users after the test.")
+ private boolean mRemoveUsersAfterTest = true;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
+ DeviceNotAvailableException {
+ if (mRemoveUsersBeforeTest && !removeUsers(device)) {
+ throw new TargetSetupError(
+ "Failed to remove all non-primary users on device " + device.getSerialNumber());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+ throws DeviceNotAvailableException {
+ if (mRemoveUsersAfterTest && !removeUsers(device)) {
+ throw new RuntimeException(
+ "Failed to remove all non-primary users on device " + device.getSerialNumber());
+ }
+ }
+
+ /**
+ * Remove all non-primary users.
+ *
+ * @param device the testing device
+ * @return {@code true} if all non-primary users are removed, {@code false} otherwise
+ */
+ private boolean removeUsers(ITestDevice device) throws DeviceNotAvailableException {
+ // Ensure package manager is running
+ device.waitForDeviceAvailable();
+
+ ArrayList<Integer> users = device.listUsers();
+
+ if (users == null) {
+ return true;
+ }
+
+ boolean success = true;
+
+ for (Integer userId : users) {
+ if (userId == 0) {
+ // Can't remove user 0, so don't try.
+ continue;
+ }
+
+ if (device.removeUser(userId)) {
+ CLog.i(String.format("Successfully removed user %d on %s",
+ userId,
+ device.getSerialNumber()));
+ } else {
+ CLog.e(String.format("Failed to remove user %d on %s",
+ userId,
+ device.getSerialNumber()));
+ success = false;
+ }
+ }
+
+ RunUtil.getDefault().sleep(TimeUnit.SECONDS.toMillis(60));
+
+ // Make sure device is still available
+ device.waitForDeviceAvailable();
+
+ return success;
+ }
+}
+
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestWifiPreparer.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestWifiPreparer.java
new file mode 100644
index 0000000..c071bd1
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestWifiPreparer.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed.targetprep;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.ITargetCleaner;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.util.RunUtil;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+/**
+ * A {@link ITargetPreparer} that connects to wifi network with configurations read from file.
+ */
+@OptionClass(alias = "afw-test-wifi")
+public class AfwTestWifiPreparer extends AfwTestTargetPreparer implements ITargetCleaner {
+
+ /**
+ * Package name of the system util app.
+ */
+ private static final String UTIL_PKG_NAME = "com.android.afwtest.util";
+
+ /** Shell command template for connect/disconnect wifi. */
+ private static final String ADB_SHELL_CMD_TEMPL =
+ "am instrument -w %s com.android.afwtest.util/.Wifi";
+
+ @Option(name = "wifi-config-file",
+ description = "The file name containing wifi configuration.")
+ private String mConfigFileName = null;
+
+ @Option(name = "wifi-ssid-key",
+ description = "The key of wifi ssid in the config file.")
+ private String mWifiSsidKey = null;
+
+ @Option(name = "wifi-security-type-key",
+ description = "The key of wifi security type in the config file.")
+ private String mWifiSecurityType = null;
+
+ @Option(name = "wifi-password-key",
+ description = "The key of wifi password in the config file.")
+ private String mWifiPasswordKey = null;
+
+ @Option(name = "wifi-attempts",
+ description = "Number of attempts to connect to wifi network.")
+ private int mWifiAttempts = 5;
+
+ @Option(name = "disconnect-wifi-after-test",
+ description = "Disconnect from wifi network after test completes.")
+ private boolean mDisconnectWifiAfterTest = true;
+
+ /** Whether to connect with AfwThUtil app. */
+ private boolean mWifiConnectedWithUtilApp = false;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
+ DeviceNotAvailableException {
+
+ WifiConfig wifiConfig = getWifiConfig(buildInfo);
+
+ // tradefed doesn't support WEP wifi; connect to it by AfwThUtil
+ if ("WEP".equals(wifiConfig.getSecurityType())) {
+ mWifiConnectedWithUtilApp = true;
+ connectWifiWithUtilApp(device,
+ wifiConfig.getSSID(),
+ wifiConfig.getSecurityType(),
+ wifiConfig.getSecurityKey());
+ return;
+ }
+
+ // Implement retry.
+ for (int i = 0; i < mWifiAttempts; ++i) {
+ try {
+ if (device.connectToWifiNetworkIfNeeded(
+ wifiConfig.getSSID(), wifiConfig.getSecurityKey())) {
+ return;
+ }
+ } catch (Exception e) {
+ CLog.e(e);
+ }
+ boolean lastAttempt = (i + 1) == mWifiAttempts;
+ if (!lastAttempt) {
+ RunUtil.getDefault().sleep(device.getOptions().getWifiRetryWaitTime());
+ }
+ }
+
+ throw new TargetSetupError(String.format("Failed to connect to wifi network %s on %s",
+ wifiConfig.getSSID(), device.getSerialNumber()));
+ }
+
+ /**
+ * Loads configuration of Wi-Fi to be used.
+ *
+ * @param buildInfo data about the build under test.
+ * @return loaded Wi-Fi configuration.
+ */
+ protected WifiConfig getWifiConfig(IBuildInfo buildInfo) throws TargetSetupError {
+ if (mConfigFileName == null) {
+ throw new TargetSetupError("wifi-config-file not specified");
+ }
+
+ if (mWifiSsidKey == null) {
+ throw new TargetSetupError("wifi-ssid-key not specified");
+ }
+
+ File configFile;
+ try {
+ configFile = new File(getCtsBuildHelper(buildInfo).getTestsDir(), mConfigFileName);
+ } catch (FileNotFoundException e) {
+ throw new TargetSetupError("Couldn't find tests directory");
+ }
+ Properties props;
+ try {
+ props = readProperties(configFile);
+ } catch (IOException e) {
+ throw new TargetSetupError(
+ String.format("Failed to read prop file: %s", configFile.getAbsolutePath()));
+ }
+
+ String wifiSsid = props.getProperty(mWifiSsidKey, "");
+ String wifiSecurityType = props.getProperty(mWifiSecurityType, "");
+ String wifiPassword = props.getProperty(mWifiPasswordKey, "");
+
+ if (wifiSsid.isEmpty()) {
+ throw new TargetSetupError(String.format(
+ "%s not specified in file %s", mWifiSsidKey, configFile.getAbsolutePath()));
+ }
+
+ return new WifiConfig(wifiSsid, wifiSecurityType, wifiPassword);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+ throws DeviceNotAvailableException {
+
+ if (mDisconnectWifiAfterTest && device.isWifiEnabled()) {
+ if (mWifiConnectedWithUtilApp) {
+ disconnectWifiWithUtilApp(device);
+ } else {
+ if (device.disconnectFromWifi()) {
+ CLog.i("Successfully disconnected from wifi network on %s",
+ device.getSerialNumber());
+ } else {
+ CLog.w("Failed to disconnect from wifi network on %s",
+ device.getSerialNumber());
+ }
+ }
+ }
+ }
+
+ /**
+ * Connects to wifi with AfwThUtil app.
+ *
+ * @param device test device
+ * @param wifiSSID WIFI SSID
+ * @param securityType WIFI security type
+ * @param password WIFI password
+ */
+ private void connectWifiWithUtilApp(ITestDevice device,
+ String wifiSSID,
+ String securityType,
+ String password)
+ throws TargetSetupError, DeviceNotAvailableException {
+ String args = String.format("-e action connect -e ssid %s", wifiSSID);
+ if (securityType != null && !securityType.isEmpty()) {
+ args = String.format("%s -e security_type %s", args, securityType);
+ }
+ if (password != null && !password.isEmpty()) {
+ args = String.format("%s -e password %s", args, password);
+ }
+
+ String cmdStr = String.format(ADB_SHELL_CMD_TEMPL, args);
+ CLog.i(String.format("Shell: %s", cmdStr));
+ String result = device.executeShellCommand(cmdStr);
+ if (result != null && result.contains("result=SUCCESS")) {
+ CLog.i(String.format(
+ "Successfully connected to wifi network %s(security_type=%s) on %s",
+ wifiSSID,
+ securityType,
+ device.getSerialNumber()));
+ } else {
+ throw new TargetSetupError(String.format(
+ "Failed to connected to wifi network %s(security_type=%s) on %s: %s",
+ wifiSSID,
+ securityType,
+ device.getSerialNumber(),
+ result));
+ }
+ }
+
+ /**
+ * Disconnects from Wifi with AfwThUtil app.
+ *
+ * @param device test device
+ */
+ private void disconnectWifiWithUtilApp(ITestDevice device) throws DeviceNotAvailableException {
+ String cmdStr = String.format(ADB_SHELL_CMD_TEMPL, "-e action disconnect");
+ CLog.i(String.format("Shell: %s", cmdStr));
+ String result = device.executeShellCommand(cmdStr);
+ if (result != null && result.contains("result=SUCCESS")) {
+ CLog.i(String.format("Successfully disconnected from wifi network on %s",
+ device.getSerialNumber()));
+ } else {
+ CLog.w(String.format("Failed to disconnect from wifi network on %s: %s",
+ device.getSerialNumber(),
+ result));
+ }
+ }
+
+ /**
+ * Help function to read {@link Properties} from a file.
+ *
+ * @param file {@link File} to read properties from
+ * @return {@link Properties} read from given file
+ *
+ * @throws IOException if read failed
+ */
+ private static Properties readProperties(File file) throws IOException {
+ InputStream input = null;
+
+ try {
+ input = new FileInputStream(file);
+ Properties props = new Properties();
+ props.load(input);
+ return props;
+ } finally {
+ if (input != null) {
+ input.close();
+ }
+ }
+ }
+
+ /**
+ * Helper class to hold Wi-Fi configuration.
+ */
+ protected final static class WifiConfig {
+ private final String mSSID;
+ private final String mSecurityType;
+ private final String mSecurityKey;
+
+ /**
+ * Constructs new instance of Wi-Fi configuration holder.
+ *
+ * @param ssid SSID of Wi-Fi access point.
+ * @param securityType security type used by Wi-Fi access point.
+ * @param securityKey security key used by Wi-Fi access point.
+ */
+ protected WifiConfig(String ssid, String securityType, String securityKey) {
+ this.mSSID = ssid;
+ this.mSecurityType = securityType;
+ this.mSecurityKey = securityKey;
+ }
+
+ /**
+ * Returns SSID of Wi-Fi access point.
+ */
+ protected String getSSID() {
+ return mSSID;
+ }
+
+ /**
+ * Returns security type used by Wi-Fi access point.
+ */
+ protected String getSecurityType() {
+ return mSecurityType;
+ }
+
+ /**
+ * Returns security key used by Wi-Fi access point.
+ */
+ protected String getSecurityKey() {
+ return mSecurityKey;
+ }
+ }
+}
+
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/AfwTestMetricsCollectorTargetPreparer.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/AfwTestMetricsCollectorTargetPreparer.java
new file mode 100644
index 0000000..10f3adb
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/AfwTestMetricsCollectorTargetPreparer.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2017 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.afwtest.tradefed.targetprep.metrics;
+
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.BuildFingerprint;
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.DeviceInfo;
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.InvocationMetrics;
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.InvocationMetrics.InvocationMetric;
+
+import com.android.afwtest.tradefed.utils.ReflectionUtils;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.BuildError;
+import com.android.tradefed.targetprep.ITargetCleaner;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.google.protobuf.nano.MessageNano;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.Consumer;
+import javax.annotation.concurrent.GuardedBy;
+
+/** {@link ITargetCleaner} to collect metrics during test invocation. */
+public class AfwTestMetricsCollectorTargetPreparer implements ITargetCleaner {
+
+ @Option(
+ name = "collector-class-names",
+ description = "Class names of metrics collectors to use."
+ )
+ private final List<String> mCollectorClassNames = new ArrayList<>();
+
+ // List of metrics collectors.
+ private final List<MetricsCollector> mCollectors = new ArrayList<>();
+
+ // Common list of collected metrics.
+ @GuardedBy("mMetricsLock")
+ private final List<InvocationMetric> mMetrics = new ArrayList<>();
+
+ // Lock to synchronize access to common list of collected metrics
+ // from multiple metrics collectors.
+ // Lock is used instead of synchronized collections to ensure fairness of the access.
+ private final ReadWriteLock mMetricsLock = new ReentrantReadWriteLock(true);
+
+ /** {@inheritDoc} */
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo)
+ throws TargetSetupError, BuildError, DeviceNotAvailableException {
+ if (!isCollectionEnabled()) {
+ return;
+ }
+
+ try {
+ createCollectors();
+ for (MetricsCollector collector : mCollectors) {
+ collector.init(device, buildInfo, this::addMetricToList);
+ }
+ } catch (MetricsCollectorException e) {
+ throw new TargetSetupError(
+ "Exception was thrown during metrics collectors initialization", e, null);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+ throws DeviceNotAvailableException {
+ if (!isCollectionEnabled()) {
+ return;
+ }
+
+ for (MetricsCollector collector : mCollectors) {
+ collector.destroy();
+ }
+ try {
+ writeMetrics(device, buildInfo);
+ } catch (MetricsCollectorException ex) {
+ CLog.e(ex);
+ }
+ }
+
+ /**
+ * Checks if metrics collection is enabled for current invocation.
+ *
+ * @return {@code true} if metrics collection is enabled, {@code false} otherwise.
+ */
+ protected boolean isCollectionEnabled() {
+ return true;
+ }
+
+ /**
+ * {@link Consumer} passed to collectors to ensure synchronized access to the list of collected
+ * metrics.
+ *
+ * @param metric metric to add to the list of collected metrics.
+ */
+ private void addMetricToList(InvocationMetric metric) {
+ mMetricsLock.writeLock().lock();
+ mMetrics.add(metric);
+ mMetricsLock.writeLock().unlock();
+ }
+
+ /**
+ * Collects information about the device on which invocation happened.
+ *
+ * @param device current {@link ITestDevice}.
+ * @param buildInfo current {@link IBuildInfo}.
+ * @return information about the device on which invocation happened.
+ */
+ private DeviceInfo getDeviceInfo(ITestDevice device, IBuildInfo buildInfo) {
+ final Map<String, String> buildAttrs = buildInfo.getBuildAttributes();
+ BuildFingerprint buildFingerprint = new BuildFingerprint();
+ buildFingerprint.productBrand = buildAttrs.get("cts:build_brand");
+ buildFingerprint.productName = buildAttrs.get("cts:build_product");
+ buildFingerprint.productDevice = buildAttrs.get("cts:build_device");
+ buildFingerprint.platformVersion = buildAttrs.get("cts:build_version_release");
+ buildFingerprint.release = buildAttrs.get("cts:build_id");
+ buildFingerprint.versionIncremental = buildAttrs.get("cts:build_version_incremental");
+ buildFingerprint.type = buildAttrs.get("cts:build_type");
+ buildFingerprint.tags = buildAttrs.get("cts:build_tags");
+
+ DeviceInfo deviceInfo = new DeviceInfo();
+ deviceInfo.buildFingerprint = buildFingerprint;
+ deviceInfo.apiLevel = Long.parseLong(buildAttrs.get("cts:build_version_sdk"));
+ try {
+ deviceInfo.gmsVersion =
+ device.getAppPackageInfo("com.google.android.gms").getVersionName();
+ } catch (DeviceNotAvailableException e) {
+ CLog.e(e);
+ deviceInfo.gmsVersion = "N/A";
+ }
+
+ return deviceInfo;
+ }
+
+ /**
+ * Writes collected metrics to file.
+ *
+ * @param buildInfo current {@link IBuildInfo}.
+ * @throws MetricsCollectorException if any error has occurred upon writing collected metrics.
+ */
+ private void writeMetrics(ITestDevice device, IBuildInfo buildInfo)
+ throws MetricsCollectorException {
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo);
+ FileOutputStream os = null;
+ try {
+ InvocationMetrics metrics = new InvocationMetrics();
+ metrics.id = UUID.randomUUID().toString();
+ metrics.timestampMillis = System.currentTimeMillis();
+ metrics.deviceInfo = getDeviceInfo(device, buildInfo);
+
+ mMetricsLock.readLock().lock();
+ metrics.metrics = mMetrics.toArray(InvocationMetric.emptyArray());
+ mMetricsLock.readLock().unlock();
+
+ if (metrics.metrics.length == 0) {
+ return;
+ }
+
+ File logsDir = buildHelper.getLogsDir();
+ // createTempFile generates unique file name.
+ File resultFile = File.createTempFile("provisioning_metrics_", ".pbuf", logsDir);
+ os = new FileOutputStream(resultFile);
+ os.write(MessageNano.toByteArray(metrics));
+ } catch (IOException e) {
+ throw new MetricsCollectorException("Cannot save collected metrics.", e);
+ } finally {
+ if (os != null) {
+ try {
+ os.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates instances of metrics collectors whose class names are specified in
+ * 'collector-class-names' option.
+ *
+ * @throws MetricsCollectorException if an exception occurs during collectors instantiation.
+ */
+ private void createCollectors() throws MetricsCollectorException {
+ for (String className : mCollectorClassNames) {
+ try {
+ mCollectors.add(ReflectionUtils.getInstanceOf(className, MetricsCollector.class));
+ } catch (ReflectiveOperationException e) {
+ throw new MetricsCollectorException(e);
+ }
+ }
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/LogcatMetricsCollector.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/LogcatMetricsCollector.java
new file mode 100644
index 0000000..79feb60
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/LogcatMetricsCollector.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2017 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.afwtest.tradefed.targetprep.metrics;
+
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.InvocationMetrics;
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.InvocationMetrics.InvocationMetric;
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.InvocationMetrics.MetricValue;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import com.android.afwtest.tradefed.utils.StreamDiscarder;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Collector of the metrics from logcat events. */
+public class LogcatMetricsCollector implements MetricsCollector {
+
+ // Logcat process from which events are read.
+ private Process mLogcatProcess;
+
+ // Thread of the event parser.
+ private Thread mParserMain;
+
+ // Thread to read and discard logcat's stderr to prevent pipe buffer overflow.
+ private Thread mLogcatErrDiscarder;
+
+ // Map of logcat events to collect to AfW TH specific metric types.
+ private final Map<Integer, Integer> mEventsToCollect = new HashMap<>();
+
+ // Initializer of set of logcat event to collect as metrics.
+ {
+ mEventsToCollect.put(
+ MetricsEvent.PROVISIONING_TOTAL_TASK_TIME_MS,
+ InvocationMetrics.TOTAL_PROVISIONING_TIME_MS);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void init(
+ ITestDevice iTestDevice,
+ IBuildInfo iBuildInfo,
+ Consumer<InvocationMetric> metricsConsumer)
+ throws MetricsCollectorException {
+
+ try {
+ startLogcat(iTestDevice.getSerialNumber());
+ mParserMain =
+ new Thread(
+ new LogcatEventParserMain(
+ mLogcatProcess.getInputStream(),
+ (eventId, value) -> {
+ if (mEventsToCollect.containsKey(eventId)) {
+ final InvocationMetric metric = new InvocationMetric();
+ final MetricValue metricValue = new MetricValue();
+
+ metricValue.setStringValue(value);
+
+ metric.metricType = mEventsToCollect.get(eventId);
+ metric.setScalarValue(metricValue);
+ metricsConsumer.accept(metric);
+ }
+ }));
+ mParserMain.start();
+ mLogcatErrDiscarder = new Thread(new StreamDiscarder(mLogcatProcess.getErrorStream()));
+ mLogcatErrDiscarder.start();
+ } catch (Exception e) {
+ destroy();
+ throw new MetricsCollectorException(
+ String.format("Failed to initialize %s.", getClass().getName()), e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void destroy() {
+ if (mLogcatProcess != null) {
+ mLogcatProcess.destroy();
+ try {
+ mLogcatProcess.waitFor();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ if (mParserMain != null) {
+ try {
+ mParserMain.join();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ if (mLogcatErrDiscarder != null) {
+ try {
+ mLogcatErrDiscarder.join();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ /**
+ * Starts new logcat process to capture events from device.
+ *
+ * @param serial serial number of a device.
+ * @throws MetricsCollectorException if could not create new logcat process.
+ */
+ private void startLogcat(String serial) throws MetricsCollectorException {
+ ProcessBuilder pb = new ProcessBuilder("adb", "-s", serial, "logcat", "-b", "events");
+ try {
+ mLogcatProcess = pb.start();
+ } catch (IOException e) {
+ throw new MetricsCollectorException("Could not start logcat to capture events.", e);
+ }
+ }
+
+ /** {@link Runnable} for a thread to parse logcat output. */
+ private static final class LogcatEventParserMain implements Runnable {
+
+ // Sample event string:
+ // 03-01 14:30:12.634 788 2845 I sysui_action: [325,4706]
+ private static final Pattern EVENT_PATTERN =
+ Pattern.compile("sysui_action: \\[(\\d+),(.+)\\]");
+
+ private final InputStream mStream;
+ private final BiConsumer<Integer, String> mEventConsumer;
+
+ LogcatEventParserMain(InputStream stream, BiConsumer<Integer, String> eventConsumer) {
+ mStream = stream;
+ mEventConsumer = eventConsumer;
+ }
+
+ @Override
+ public void run() {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(mStream))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ Matcher matcher = EVENT_PATTERN.matcher(line);
+ if (!matcher.find()) {
+ continue;
+ }
+ mEventConsumer.accept(Integer.parseInt(matcher.group(1)), matcher.group(2));
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot read logcat output.", e);
+ }
+ }
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/MemoryMetricsCollector.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/MemoryMetricsCollector.java
new file mode 100644
index 0000000..7b87341
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/MemoryMetricsCollector.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 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.afwtest.tradefed.targetprep.metrics;
+
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.InvocationMetrics.InvocationMetric;
+
+import com.android.afwtest.tradefed.targetprep.metrics.memory.MemoryByUserGrouper;
+import com.android.afwtest.tradefed.targetprep.metrics.memory.MemoryCollectorTask;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+
+import java.util.Arrays;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Collector of memory usage metrics.
+ */
+public class MemoryMetricsCollector implements MetricsCollector {
+
+ /**
+ * Executor to run scheduled metric collection task.
+ */
+ private ScheduledExecutorService mExecutor;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void init(ITestDevice testDevice, IBuildInfo iBuildInfo,
+ Consumer<InvocationMetric> metricsConsumer)
+ throws MetricsCollectorException {
+
+ AtomicLong tickCounter = new AtomicLong(0L);
+
+ mExecutor = new ScheduledThreadPoolExecutor(1);
+ mExecutor.scheduleWithFixedDelay(getCollectorTask(testDevice, metricsConsumer,
+ tickCounter::getAndIncrement), 0, 500, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void destroy() {
+ mExecutor.shutdownNow();
+ }
+
+ /**
+ * Gets {@link Runnable} to process a single iteration of memory information collection.
+ *
+ * @param testDevice current {@link ITestDevice}.
+ * @param metricsConsumer {@link Consumer} to emit collected metrics.
+ * @param tickCounter tick counter.
+ * @return {@link Runnable} to process a single iteration of memory information collection.
+ */
+ protected Runnable getCollectorTask(ITestDevice testDevice,
+ Consumer<InvocationMetric> metricsConsumer,
+ Supplier<Long> tickCounter) {
+ return new MemoryCollectorTask(testDevice, metricsConsumer, tickCounter,
+ Arrays.asList(new MemoryByUserGrouper()));
+ }
+} \ No newline at end of file
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/MetricsCollector.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/MetricsCollector.java
new file mode 100644
index 0000000..d28c837
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/MetricsCollector.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 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.afwtest.tradefed.targetprep.metrics;
+
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.InvocationMetrics.InvocationMetric;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+
+import java.util.function.Consumer;
+
+/** Collects metrics from a device during test invocation. */
+public interface MetricsCollector {
+
+ /**
+ * Initializes metrics collector.
+ *
+ * @param iTestDevice current {@link ITestDevice}.
+ * @param iBuildInfo current {@link IBuildInfo}.
+ * @param metricsConsumer {@link Consumer} to emit collected metrics.
+ * @throws MetricsCollectorException if an error occurred during initialization.
+ */
+ void init(
+ ITestDevice iTestDevice,
+ IBuildInfo iBuildInfo,
+ Consumer<InvocationMetric> metricsConsumer)
+ throws MetricsCollectorException;
+
+ /** Cleans up metrics collector. */
+ void destroy();
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/MetricsCollectorException.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/MetricsCollectorException.java
new file mode 100644
index 0000000..5d4da4e
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/MetricsCollectorException.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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.afwtest.tradefed.targetprep.metrics;
+
+/**
+ * Custom checked exception to wrap exceptions from com.android.afwtest.tradefed.targetprep.metrics
+ * package.
+ */
+public final class MetricsCollectorException extends Exception {
+
+ public MetricsCollectorException() {
+ super();
+ }
+
+ public MetricsCollectorException(String message) {
+ super(message);
+ }
+
+ public MetricsCollectorException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public MetricsCollectorException(Throwable cause) {
+ super(cause);
+ }
+
+ protected MetricsCollectorException(
+ String message,
+ Throwable cause,
+ boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/MetricsUtils.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/MetricsUtils.java
new file mode 100644
index 0000000..e9e58ab
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/MetricsUtils.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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.afwtest.tradefed.targetprep.metrics;
+
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.InvocationMetrics.MetricValueMap;
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.InvocationMetrics.MetricValue;
+
+/**
+ * Utility class containing methods related to metrics collection classes.
+ */
+public final class MetricsUtils {
+
+ /** Prevent class from instantiation. */
+ private MetricsUtils() {
+ }
+
+ /**
+ * Creates an instance of {@link MetricValueMap.ValuesEntry} from given key and value.
+ *
+ * @param key key of the entry to add.
+ * @param value value of the entry to add.
+ * @return created instance of {@link MetricValueMap.ValuesEntry}.
+ */
+ public static MetricValueMap.ValuesEntry toMetricValueMapEntry(String key, long value) {
+ MetricValue metricValue = new MetricValue();
+ metricValue.setUintValue(value);
+
+ MetricValueMap.ValuesEntry valuesEntry = new MetricValueMap.ValuesEntry();
+ valuesEntry.key = key;
+ valuesEntry.value = metricValue;
+
+ return valuesEntry;
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/memory/MemoryByProcessGrouper.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/memory/MemoryByProcessGrouper.java
new file mode 100644
index 0000000..6b5190e
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/memory/MemoryByProcessGrouper.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 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.afwtest.tradefed.targetprep.metrics.memory;
+
+import static com.android.afwtest.tradefed.targetprep.metrics.MetricsUtils.toMetricValueMapEntry;
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.InvocationMetrics;
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.InvocationMetrics.MetricValueMap;
+import static com.android.afwtest.tradefed.utils.AdbShellUtils.ProcessInfo;
+
+import static java.util.stream.Collectors.toList;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * {@link MemoryInfoGrouper} grouping memory data into a map of process name to total PSS consumed
+ * by the process.
+ */
+public class MemoryByProcessGrouper implements MemoryInfoGrouper {
+
+ private Collection<String> mProcesses;
+
+ public MemoryByProcessGrouper(Collection<String> processes) {
+ mProcesses = processes;
+ }
+
+ @Override
+ public Collection<MetricValueMap.ValuesEntry> group(Collection<ProcessInfo> ps,
+ Map<Long, Long> memoryInfo) {
+ return ps.stream()
+ .filter(processInfo -> mProcesses.contains(processInfo.getName()))
+ .map(processInfo ->
+ toMetricValueMapEntry(processInfo.getName(), memoryInfo.getOrDefault(
+ processInfo.getPID(), 0L)))
+ .collect(toList());
+ }
+
+ @Override
+ public int getMetricType() {
+ return InvocationMetrics.PSS_BY_PROCESS;
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/memory/MemoryByUserGrouper.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/memory/MemoryByUserGrouper.java
new file mode 100644
index 0000000..a5a45dc
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/memory/MemoryByUserGrouper.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 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.afwtest.tradefed.targetprep.metrics.memory;
+
+import static com.android.afwtest.tradefed.targetprep.metrics.MetricsUtils.toMetricValueMapEntry;
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.InvocationMetrics;
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.InvocationMetrics.MetricValueMap;
+import static com.android.afwtest.tradefed.utils.AdbShellUtils.ProcessInfo;
+
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.summingLong;
+import static java.util.stream.Collectors.toList;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * {@link MemoryInfoGrouper} grouping memory data into a map of user id to total PSS consumed by all
+ * the processes run by the user.
+ */
+public class MemoryByUserGrouper implements MemoryInfoGrouper {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Collection<MetricValueMap.ValuesEntry> group(Collection<ProcessInfo> ps,
+ Map<Long, Long> memoryInfo) {
+ return ps.stream()
+ .collect(
+ groupingBy(ProcessInfo::getAndroidUserId,
+ summingLong(processInfo ->
+ memoryInfo.getOrDefault(
+ processInfo.getPID(), 0L))))
+ .entrySet().stream()
+ .map(e -> toMetricValueMapEntry("user_" + e.getKey(), e.getValue()))
+ .collect(toList());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getMetricType() {
+ return InvocationMetrics.TOTAL_PSS_BY_USER;
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/memory/MemoryCollectorTask.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/memory/MemoryCollectorTask.java
new file mode 100644
index 0000000..5d10d26
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/memory/MemoryCollectorTask.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 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.afwtest.tradefed.targetprep.metrics.memory;
+
+import static com.android.afwtest.tradefed.targetprep.metrics.MetricsUtils.toMetricValueMapEntry;
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.InvocationMetrics.InvocationMetric;
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.InvocationMetrics.MetricValueMap;
+
+import com.android.afwtest.tradefed.utils.AdbShellUtils;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * {@link Runnable} of the task that is executed in equal time intervals and collects memory
+ * usage information.
+ */
+public class MemoryCollectorTask implements Runnable {
+
+ /**
+ * {@link MemoryInfoGrouper} collection to be used to emit metrics.
+ */
+ private Collection<MemoryInfoGrouper> mMemoryInfoGroupers;
+
+ /**
+ * Current {@link ITestDevice}.
+ */
+ private ITestDevice mTestDevice;
+
+ /**
+ * {@link Consumer} of collected information.
+ */
+ private Consumer<InvocationMetric> mConsumer;
+
+ /**
+ * Tick counter.
+ */
+ private Supplier<Long> mTickCounter;
+
+ /**
+ * Constructs a new instance of the task.
+ *
+ * @param testDevice current {@link ITestDevice}.
+ * @param consumer {@link Consumer} of collected information.
+ * @param tickCounter tick counter.
+ * @param groupers {@link MemoryInfoGrouper} collection to be used to emit metrics.
+ */
+ public MemoryCollectorTask(ITestDevice testDevice,
+ Consumer<InvocationMetric> consumer,
+ Supplier<Long> tickCounter,
+ Collection<MemoryInfoGrouper> groupers) {
+ mTestDevice = testDevice;
+ mConsumer = consumer;
+ mTickCounter = tickCounter;
+ mMemoryInfoGroupers = groupers;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void run() {
+ try {
+ Set<AdbShellUtils.ProcessInfo> ps = AdbShellUtils.getPS(mTestDevice);
+ Map<Long, Long> memoryInfo = AdbShellUtils.getMemoryInfo(mTestDevice);
+ MetricValueMap.ValuesEntry tickEntry =
+ toMetricValueMapEntry("tick", mTickCounter.get());
+
+ mMemoryInfoGroupers
+ .stream()
+ .map(grouper -> {
+ Collection<MetricValueMap.ValuesEntry> entries =
+ grouper.group(ps, memoryInfo);
+ entries.add(tickEntry);
+
+ MetricValueMap.ValuesEntry[] entriesArray =
+ entries.toArray(new MetricValueMap.ValuesEntry[entries.size()]);
+
+ MetricValueMap mapValue = new MetricValueMap();
+ mapValue.values = entriesArray;
+
+ InvocationMetric metric = new InvocationMetric();
+ metric.metricType = grouper.getMetricType();
+ metric.setMapValue(mapValue);
+
+ return metric;
+ }).forEach(mConsumer);
+
+ } catch (DeviceNotAvailableException ignore) {}
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/memory/MemoryInfoGrouper.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/memory/MemoryInfoGrouper.java
new file mode 100644
index 0000000..d1cfa95
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/memory/MemoryInfoGrouper.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 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.afwtest.tradefed.targetprep.metrics.memory;
+
+import static com.android.afwtest.tradefed.targetprep.metrics.nano.Metrics.InvocationMetrics.MetricValueMap;
+import static com.android.afwtest.tradefed.utils.AdbShellUtils.ProcessInfo;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * An object to group memory and process information collected from the device into
+ * performance metrics.
+ */
+public interface MemoryInfoGrouper {
+
+ /**
+ * Groups memory and process information collected from the device into
+ * performance metrics.
+ *
+ * @param ps process information.
+ * @param memoryInfo memory information, map of process id to total PSS consumed be the process.
+ * @return {@link Collection} of performance metrics.
+ */
+ Collection<MetricValueMap.ValuesEntry> group(
+ Collection<ProcessInfo> ps,
+ Map<Long, Long> memoryInfo);
+
+ /**
+ * Gets the type of metrics emitted by the grouper.
+ *
+ * @return the type of metrics emitted by the grouper.
+ */
+ int getMetricType();
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/metrics.proto b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/metrics.proto
new file mode 100644
index 0000000..bd3cde9
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/metrics/metrics.proto
@@ -0,0 +1,100 @@
+// Copyright (C) 2017 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.
+
+syntax = "proto2";
+
+option java_package = "com.android.afwtest.tradefed.targetprep.metrics";
+
+package com_android_afwtest_tradefed_targetprep_metrics;
+
+// Message representing Android Build Fingerprint.
+// google/bullhead/bullhead:7.1.1/N4F26O/3582057:user/release-keys
+message BuildFingerprint {
+ // google
+ required string product_brand = 1;
+ // bullhead
+ required string product_name = 2;
+ // bullhead
+ required string product_device = 3;
+ // 7.1.1
+ required string platform_version = 4;
+ // N4F26O
+ required string release = 5;
+ // 3582057
+ required string version_incremental = 6;
+ // user
+ required string type = 7;
+ // release-keys
+ optional string tags = 8;
+}
+
+// Message describing device on which invocation happened.
+message DeviceInfo {
+ // Fingerprint of the build on the device.
+ required BuildFingerprint build_fingerprint = 1;
+
+ // Version of Google Play Services installed on the device.
+ required string gms_version = 2;
+
+ // API level of the device.
+ required int64 api_level = 3;
+}
+
+// Message describing collection of metrics collected during an invocation.
+message InvocationMetrics {
+
+ // Enum of supported metric types.
+ enum MetricType {
+ UNKNOWN = 1;
+ TOTAL_PROVISIONING_TIME_MS = 2;
+ TOTAL_PSS_BY_USER = 3;
+ PSS_BY_PROCESS = 4;
+ }
+
+ // Value of metric.
+ message MetricValue {
+ oneof value {
+ uint64 uint_value = 1;
+ sint64 sint_value = 2;
+ string string_value = 3;
+ }
+ }
+
+ // Wrapper object to bypass limitation of not being able to have map inside
+ // oneof.
+ message MetricValueMap {
+ map<string, MetricValue> values = 1;
+ }
+
+ // Message describing a single metric collected during an invocation.
+ message InvocationMetric {
+ required MetricType metric_type = 1;
+ oneof metric_value {
+ MetricValue scalar_value = 2;
+ MetricValueMap map_value = 3;
+ }
+ }
+
+ // Unique ID.
+ required string id = 1;
+
+ // Timestamp of the invocation in milliseconds.
+ required int64 timestamp_millis = 2;
+
+ // Information about the device on which invocation happened.
+ required DeviceInfo device_info = 3;
+
+ // Collection of metrics.
+ repeated InvocationMetric metrics = 4;
+} \ No newline at end of file
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/AfwAndroidJUnitTest.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/AfwAndroidJUnitTest.java
new file mode 100644
index 0000000..bfa3e29
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/AfwAndroidJUnitTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed.testtype;
+
+import com.android.afwtest.tradefed.TestConfig;
+import com.android.ddmlib.Log;
+import com.android.tradefed.testtype.AndroidJUnitTest;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test class for Android for Work Test Harness JUnit tests.
+ */
+public class AfwAndroidJUnitTest extends AndroidJUnitTest {
+
+ private static final String LOG_TAG = "afwtest.AfwAndroidJUnitTest";
+
+ // Default execution timeout for afw test packages
+ private static final long TEST_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(10);
+
+ // 1 min in milliseconds
+ private static final long ONE_MIN_MS = TimeUnit.MINUTES.toMillis(1);
+
+ /**
+ * {@inheritDoc}
+ */
+ public AfwAndroidJUnitTest() {
+ super();
+
+ long testTimeout =
+ TimeUnit.MINUTES.toMillis(TestConfig.getInstance().getTestTimeoutMin(0));
+ if (testTimeout == 0) {
+ testTimeout = TEST_TIMEOUT_MS +
+ TestConfig.getInstance().getTimeoutSize() * ONE_MIN_MS * 5;
+ }
+ Log.i(LOG_TAG, String.format("Instrumentation test timeout = %d seconds",
+ TimeUnit.MILLISECONDS.toSeconds(testTimeout)));
+ setTestTimeout((int) testTimeout);
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/AfwTest.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/AfwTest.java
new file mode 100644
index 0000000..b19ea25
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/AfwTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed.testtype;
+
+import com.android.afwtest.tradefed.TestConfig;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.tradefed.testtype.IModuleRepo;
+import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
+import com.android.compatibility.common.tradefed.testtype.ModuleRepo;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.util.FileUtil;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Customized {@link CompatibilityTest} for running afw-test tests.
+ * <p>Supports running all the tests contained in an afw-test plan, or individual test packages.
+ * </p>
+ */
+@OptionClass(alias = "compatibility")
+public class AfwTest extends CompatibilityTest {
+
+ private static final String CMD_DISABLE_PKG_VERIFICATION =
+ "settings put global package_verifier_enable 0";
+
+ @Option(name = "afw-test-config-file",
+ description = "Test harness config file to replace afw-test.props.")
+ private String mConfigFile = null;
+
+ @Option(name = "screenshot",
+ description = "Take a screenshot on every test")
+ private boolean mScreenshot = false;
+
+ /**
+ * A {@link CompatibilityBuildHelper} instance to access afw-test build info, such as the
+ * absolute paths of the test plan file and test case apks.
+ */
+ protected CompatibilityBuildHelper mAfwTestBuild = null;
+
+ /**
+ * Internal {@link ITestInvocationListener} wrapping one provided by framework with additional
+ * listeners specific for {@link AfwTest}.
+ */
+ protected ITestInvocationListener mInternalListener = null;
+
+ /**
+ * A {@link ITestDevice} instance representing a device under test.
+ */
+ protected ITestDevice mDevice = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ public AfwTest() {
+ this(1 /*totalShards*/, new ModuleRepo() /*moduleRepo*/, 0 /*shardIndex*/);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public AfwTest(int totalShards, IModuleRepo moduleRepo, Integer shardIndex) {
+ super(totalShards, moduleRepo, shardIndex);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setBuild(IBuildInfo build) {
+ super.setBuild(build);
+
+ mAfwTestBuild = new CompatibilityBuildHelper(build);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
+ mInternalListener = listener;
+
+ beforeTest();
+ super.run(mInternalListener);
+ afterTest();
+ }
+
+ /**
+ * Executes general preparation steps before running the test.
+ *
+ * @throws DeviceNotAvailableException if connection to the device is lost.
+ */
+ protected void beforeTest() throws DeviceNotAvailableException {
+ CLog.i("Running afw-test");
+
+ mDevice = getDevice();
+ if (mDevice == null) {
+ throw new IllegalArgumentException("Device not found");
+ }
+
+ try {
+ // Replace afw-test.props with a customized file if there is.
+ if (mConfigFile != null) {
+ FileUtil.copyFile(new File(mConfigFile), getTestConfigFile());
+ }
+
+ // Init TestConfig singleton.
+ TestConfig.init(getTestConfigFile());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ // Disable package verification.
+ // This is necessary because CtsTest.run() will push TestDeviceSetup.apk
+ // to the testing device which will trigger package verification dialog.
+ mDevice.executeShellCommand(CMD_DISABLE_PKG_VERIFICATION);
+
+ if (mScreenshot) {
+ mInternalListener = new ScreenshotListener(mInternalListener, mDevice);
+ }
+ }
+
+ /**
+ * Executes general tear down steps after running the test.
+ *
+ * @throws DeviceNotAvailableException if connection to the device is lost.
+ */
+ protected void afterTest() throws DeviceNotAvailableException {
+ // Disable package verification again; it might be enabled by some tests.
+ mDevice.executeShellCommand(CMD_DISABLE_PKG_VERIFICATION);
+ }
+
+ /**
+ * Reads test configuration file, afw-test.props.
+ *
+ * @return {@link File} object keeping loaded content from afw-test.props
+ */
+ protected File getTestConfigFile() throws IOException {
+ return new File(mAfwTestBuild.getTestsDir(), "afw-test.props");
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/ScreenshotListener.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/ScreenshotListener.java
new file mode 100644
index 0000000..e48dd65
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/ScreenshotListener.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed.testtype;
+
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.FileInputStreamSource;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.ResultForwarder;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * Test invocation listener to capture device screenshot after the test
+ */
+public class ScreenshotListener extends ResultForwarder {
+
+ private static final String UIAUTOMATOR = "/system/bin/uiautomator";
+ private static final String UIAUTOMATOR_DUMP_COMMAND = "dump";
+ private static final String UIDUMP_DEVICE_PATH = "/data/local/tmp/uidump.xml";
+
+ final private ITestDevice mDevice;
+ private boolean testFailed = false;
+
+ public ScreenshotListener(ITestInvocationListener listener, ITestDevice device) {
+ super(listener);
+ mDevice = device;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
+ super.testEnded(test, testMetrics);
+ CLog.i("Capturing device screenshot after test %s", test.toString());
+ try {
+ final InputStreamSource screenSource = mDevice.getScreenshot("PNG",
+ /* rescale */ !testFailed);
+ super.testLog(String.format("%s-screenshot", test.toString()), LogDataType.PNG,
+ screenSource);
+ screenSource.cancel();
+ } catch (DeviceNotAvailableException e) {
+ CLog.e(e);
+ CLog.e("Device %s became unavailable while capturing screenshot",
+ mDevice.getSerialNumber());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testFailed(TestIdentifier test, String trace) {
+ super.testFailed(test, trace);
+
+ testFailed = true;
+
+ CLog.i("Dumping device ui layout after test %s", test.toString());
+ try {
+ mDevice.executeShellCommand(
+ String.format("%s %s %s", UIAUTOMATOR, UIAUTOMATOR_DUMP_COMMAND,
+ UIDUMP_DEVICE_PATH));
+ final File dumpFile = mDevice.pullFile(UIDUMP_DEVICE_PATH);
+ final InputStreamSource dumpSource = new FileInputStreamSource(dumpFile,
+ /* deleteFileOnCancel */ true);
+ super.testLog(String.format("%s-uidump", test.toString()), LogDataType.XML, dumpSource);
+ dumpSource.cancel();
+ } catch (DeviceNotAvailableException e) {
+ CLog.e(e);
+ CLog.e("Device %s became unavailable while capturing ui layout dump",
+ mDevice.getSerialNumber());
+ }
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/utils/AdbShellUtils.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/utils/AdbShellUtils.java
new file mode 100644
index 0000000..50e7706
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/utils/AdbShellUtils.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2017 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.afwtest.tradefed.utils;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility class containing methods related to executing various ADB Shell commands.
+ */
+public final class AdbShellUtils {
+
+ /** Prevent class from instantiation. */
+ private AdbShellUtils() {
+ }
+
+ /**
+ * Gets process list currently running on the device grouped by user.
+ *
+ * @param testDevice device to get process list from.
+ * @return {@link Map} of user ids to a {@link Set} of process ids run by the user.
+ * @throws DeviceNotAvailableException if device became unavailable during the execution of
+ * the method.
+ */
+ public static Set<ProcessInfo> getPS(ITestDevice testDevice)
+ throws DeviceNotAvailableException {
+ Set<ProcessInfo> result = new HashSet<>();
+ String psOutput = testDevice.executeShellCommand("ps -e");
+ for (String line : psOutput.split("\n")) {
+ if (line.contains("USER")) {
+ continue;
+ }
+
+ String[] columns = line.split("\\s+");
+ String user = columns[0];
+ long pid = Long.parseLong(columns[1]);
+ long ppid = Long.parseLong(columns[2]);
+ String name = columns[8];
+
+ result.add(new ProcessInfo(user, pid, ppid, name));
+ }
+ return result;
+ }
+
+ /**
+ * Gets memory consumption information about processes currently running on the device.
+ *
+ * @param testDevice device to get memory information from.
+ * @return {@link Map} of process ids to total PSS of the process.
+ * @throws DeviceNotAvailableException if device became unavailable during the execution of
+ * the method.
+ */
+ public static Map<Long, Long> getMemoryInfo(ITestDevice testDevice)
+ throws DeviceNotAvailableException {
+ Map<Long, Long> result = new HashMap<>();
+ String miOutput = testDevice.executeShellCommand("su system dumpsys meminfo -c");
+ for (String line : miOutput.split("\n")) {
+ String[] columns = line.split(",");
+ if (columns[0].equals("proc")) {
+ long pid = Long.parseLong(columns[3]);
+ long pss = Long.parseLong(columns[4]);
+ result.put(pid, pss);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Class representing information about process retrieved from ps command.
+ */
+ public static final class ProcessInfo {
+
+ /**
+ * {@link Pattern} of Android user name that encodes user id and activity id.
+ */
+ private static final Pattern APP_PROCESS_USER_PATTERN = Pattern.compile("u(\\d+)_a\\d+");
+
+ private final String mUser;
+ private final long mPID;
+ private final long mPPID;
+ private final String mName;
+
+ private ProcessInfo(String user, long pid, long ppid, String name) {
+ this.mUser = user;
+ this.mPID = pid;
+ this.mPPID = ppid;
+ this.mName = name;
+ }
+
+ /**
+ * Gets user name running the process.
+ *
+ * @return user name running the process.
+ */
+ public String getUser() {
+ return mUser;
+ }
+
+ /**
+ * Gets the id of the process.
+ *
+ * @return the id of the process.
+ */
+ public long getPID() {
+ return mPID;
+ }
+
+ /**
+ * Gets the id of the parent of the process.
+ *
+ * @return the id of the parent of the process.
+ */
+ public long getPPID() {
+ return mPPID;
+ }
+
+ /**
+ * Get the name of the process.
+ *
+ * @return the name of the process.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Gets the id if Android user running the process, or -1 of the process is run by
+ * the system.
+ *
+ * @return the id if Android user running the process, or -1 of the process is run by
+ * the system.
+ */
+ public long getAndroidUserId() {
+ long userId = -1;
+ Matcher matcher = APP_PROCESS_USER_PATTERN.matcher(mName);
+ if (matcher.find()) {
+ userId = Long.parseLong(matcher.group(1));
+ }
+ return userId;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ProcessInfo that = (ProcessInfo) o;
+ return mPID == that.mPID &&
+ mPPID == that.mPPID &&
+ Objects.equals(mUser, that.mUser) &&
+ Objects.equals(mName, that.mName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUser, mPID, mPPID, mName);
+ }
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/utils/ReflectionUtils.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/utils/ReflectionUtils.java
new file mode 100644
index 0000000..e51aa1b
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/utils/ReflectionUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 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.afwtest.tradefed.utils;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+/** Set of utility methods for reflection operations. */
+public final class ReflectionUtils {
+
+ /** Prevent class from instantiation. */
+ private ReflectionUtils() {}
+
+ /**
+ * Creates instance of specified class.
+ *
+ * @param className name of the class to instantiate.
+ * @param clazz {@link Class} extended by specified class to cast result to.
+ * @param args arguments of constructor to invoke.
+ * @return instance of specified class.
+ * @throws ClassNotFoundException if specified class cannot be found in classpath.
+ * @throws NoSuchMethodException if no constructor can be found suitable for specified
+ * constructor arguments.
+ * @throws IllegalAccessException if access to suitable constructor is forbidden.
+ * @throws InstantiationException if the class that declares the underlying constructor
+ * represents an abstract class.
+ * @throws InvocationTargetException if the underlying constructor throws an exception.
+ * @throws IllegalArgumentException if class specified by name is not assignable to the class
+ * specified by class instance.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T getInstanceOf(String className, Class<T> clazz, Object... args)
+ throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
+ InstantiationException, InvocationTargetException {
+
+ Class<?> resolvedClass = clazz.getClassLoader().loadClass(className);
+ if (!clazz.isAssignableFrom(resolvedClass)) {
+ throw new IllegalArgumentException(
+ String.format(
+ "An instance of %s cannot be assigned to %s.",
+ resolvedClass.getName(), clazz.getName()));
+ }
+ Constructor<T> constructor =
+ (Constructor<T>) resolvedClass.getConstructor(getClassesOf(args));
+ return constructor.newInstance(args);
+ }
+
+ /**
+ * Gets classes of specified objects.
+ *
+ * @param objects objects to get classes of.
+ * @return classes of specified objects.
+ */
+ private static Class<?>[] getClassesOf(Object... objects) {
+ Class<?>[] result = new Class[objects.length];
+ for (int i = 0; i < objects.length; i++) {
+ result[i] = objects[i].getClass();
+ }
+ return result;
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/utils/StreamDiscarder.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/utils/StreamDiscarder.java
new file mode 100644
index 0000000..dd4191c
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/utils/StreamDiscarder.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 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.afwtest.tradefed.utils;
+
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * {@link Runnable} that reads and discards data from specified stream.
+ */
+public final class StreamDiscarder implements Runnable {
+
+ private static final int BATCH_SIZE = 1024;
+
+ private final InputStream mStream;
+ private final byte[] buffer;
+
+ /**
+ * Constructs {@link StreamDiscarder} instance.
+ *
+ * @param is {@link InputStream} to read and discard data from.
+ */
+ public StreamDiscarder(InputStream is) {
+ mStream = is;
+ buffer = new byte[BATCH_SIZE];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @SuppressWarnings("StatementWithEmptyBody")
+ public void run() {
+ try {
+ while (!Thread.interrupted() && mStream.read(buffer) > 0) {}
+ } catch (IOException e) {
+ CLog.e(e);
+ }
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/utils/TimeUtil.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/utils/TimeUtil.java
new file mode 100644
index 0000000..d5730ce
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/utils/TimeUtil.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 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.afwtest.tradefed.utils;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Time related utils.
+ */
+public class TimeUtil {
+
+ /**
+ * Return the current timestamp in a compressed format, e.g.: 2015.11.25_11.42.12
+ */
+ public static String getResultTimestamp() {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss");
+ return dateFormat.format(new Date());
+ }
+
+ /**
+ * Sync device time with host.
+ *
+ * @param device test device
+ */
+ public static void syncHostTime(ITestDevice device) throws DeviceNotAvailableException {
+ CLog.i(String.format("Before time sync: %s", device.executeShellCommand("date -u")));
+
+ Date date = new Date();
+ String dateString = null;
+ if (device.getApiLevel() < 23) {
+ // set date in epoch format
+ dateString = Long.toString(date.getTime() / 1000); //ms to s
+ } else {
+ // set date with POSIX like params
+ SimpleDateFormat sdf = new java.text.SimpleDateFormat("MMddHHmmyyyy.ss");
+ sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
+ dateString = sdf.format(date);
+ }
+ // best effort, no verification
+ String cmd = String.format("date -u %s", dateString);
+ CLog.i(String.format("Setting time: %s", cmd));
+ CLog.i(device.executeShellCommand(cmd));
+
+ CLog.i(String.format("After time sync: %s", device.executeShellCommand("date -u")));
+ }
+
+}
diff --git a/zip_exclude.lst b/zip_exclude.lst
new file mode 100644
index 0000000..0fbdb2b
--- /dev/null
+++ b/zip_exclude.lst
@@ -0,0 +1,3 @@
+*resource*
+*repository/logs*
+*repository/results* \ No newline at end of file