aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJinhui Wang <jinhuiw@google.com>2016-06-20 19:42:59 -0700
committerJinhui Wang <jinhuiw@google.com>2016-06-21 09:45:49 -0700
commit8c28a9831d53a17178c4f3acd96ff992eef2eb77 (patch)
tree1c827bcfdd656f8a1790943b59540f8adf06507d
parent2e8c262068ccf34de556967679083d72626c7876 (diff)
downloadAfwTestHarness-8c28a9831d53a17178c4f3acd96ff992eef2eb77.tar.gz
Import tools/tradefed module
Change-Id: I511b95fece32050614e5b9ab14e210e8c642350d
-rw-r--r--tools/Android.mk33
-rw-r--r--tools/prebuilt/CtsDeviceInfo.apkbin0 -> 9197 bytes
-rw-r--r--tools/prebuilt/TestDeviceSetup.apkbin0 -> 15366 bytes
-rw-r--r--tools/tradefed-host/Android.mk38
-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-tradefed102
-rw-r--r--tools/tradefed-host/res/config/afw-test-common.xml31
-rw-r--r--tools/tradefed-host/res/config/afw-test-factory-reset.xml46
-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.xml26
-rw-r--r--tools/tradefed-host/res/config/cts.xml39
-rw-r--r--tools/tradefed-host/res/report/cts_result.xsl612
-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.java81
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEnvDumper.java164
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestFactoryReset.java214
-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/AfwTestPrivAppInstaller.java103
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPullExternalFile.java79
-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.java139
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestUserRemover.java110
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestWifiPreparer.java147
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/AfwTest.java117
-rw-r--r--tools/tradefed-host/src/com/android/afwtest/tradefed/utils/TimeUtil.java66
-rw-r--r--tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java72
-rw-r--r--tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsInstrumentationApkTest.java141
-rw-r--r--tools/utils/afwtest/__init__.py19
-rw-r--r--tools/utils/afwtest/tools.py251
-rwxr-xr-xtools/utils/buildAfwTest.py104
38 files changed, 3543 insertions, 0 deletions
diff --git a/tools/Android.mk b/tools/Android.mk
new file mode 100644
index 0000000..c31684f
--- /dev/null
+++ b/tools/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.
+#
+
+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-test/android-cts/tools/afw-test-prebuilt.jar
+$(afw_test_prebuilt_jar): PRIVATE_TESTS_DIR := $(HOST_OUT)/afw-test/android-cts/repository/testcases
+$(afw_test_prebuilt_jar): PRIVATE_PLANS_DIR := $(HOST_OUT)/afw-test/android-cts/repository/plans
+$(afw_test_prebuilt_jar): PRIVATE_TOOLS_DIR := $(HOST_OUT)/afw-test/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_PLANS_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..c245c81
--- /dev/null
+++ b/tools/prebuilt/CtsDeviceInfo.apk
Binary files differ
diff --git a/tools/prebuilt/TestDeviceSetup.apk b/tools/prebuilt/TestDeviceSetup.apk
new file mode 100644
index 0000000..379a34b
--- /dev/null
+++ b/tools/prebuilt/TestDeviceSetup.apk
Binary files differ
diff --git a/tools/tradefed-host/Android.mk b/tools/tradefed-host/Android.mk
new file mode 100644
index 0000000..01295cb
--- /dev/null
+++ b/tools/tradefed-host/Android.mk
@@ -0,0 +1,38 @@
+#
+# 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_JAVA_RESOURCE_DIRS := res
+
+LOCAL_MODULE := afw-test-tradefed
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_JAVA_LIBRARIES := tradefed-prebuilt
+LOCAL_STATIC_JAVA_LIBRARIES := cts-tradefed
+
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+LOCAL_JAR_MANIFEST := MANIFEST.mf
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# 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..c04c875
--- /dev/null
+++ b/tools/tradefed-host/etc/afw-test-tradefed
@@ -0,0 +1,102 @@
+#!/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 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-test
+ else
+ CTS_ROOT=${ANDROID_BUILD_TOP}/${OUT_DIR:-out}/host/${OS}/afw-test
+ 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-prebuilt.jar afw-test-tradefed.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.cts.tradefed.command.CtsConsole "$@"
+
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..b32545a
--- /dev/null
+++ b/tools/tradefed-host/res/config/afw-test-common.xml
@@ -0,0 +1,31 @@
+<?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">
+
+ <!-- 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"/>
+ <!-- Disable package verification -->
+ <option name="run-command" value="settings put global package_verifier_enable 0" />
+ </target_preparer>
+
+</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..5081af2
--- /dev/null
+++ b/tools/tradefed-host/res/config/afw-test-factory-reset.xml
@@ -0,0 +1,46 @@
+<?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"/>
+ </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.cts.tradefed.targetprep.CtsApkInstaller">
+ <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..32ad213
--- /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.cts.tradefed.targetprep.CtsFilePusher">
+ <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..962aec3
--- /dev/null
+++ b/tools/tradefed-host/res/config/afw-test-wifi.xml
@@ -0,0 +1,26 @@
+<?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">
+
+ <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-password-key" value="wifi_password"/>
+ <option name="disconnect-wifi-after-test" value="false"/>
+ </target_preparer>
+
+</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..ebe5040
--- /dev/null
+++ b/tools/tradefed-host/res/config/cts.xml
@@ -0,0 +1,39 @@
+<?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.cts.tradefed.build.CtsBuildProvider" />
+ <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="disable-reboot" value="true"/>
+ <option name="screenshot-on-failure" value="true"/>
+ <option name="screenshot" value="true"/>
+ <option name="bugreport" value="true"/>
+ </test>
+ <logger class="com.android.tradefed.log.FileLogger" />
+ <result_reporter class="com.android.cts.tradefed.result.CtsXmlResultReporter" />
+ <result_reporter class="com.android.cts.tradefed.result.CtsTestLogReporter" />
+
+ <!-- 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" />
+</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..d370ca4
--- /dev/null
+++ b/tools/tradefed-host/res/report/cts_result.xsl
@@ -0,0 +1,612 @@
+<?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>
+ <xsl:value-of select="1.4"/>
+ </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..da66069
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEncryptDevice.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.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 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;
+
+ /**
+ * {@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 (device.encryptDevice(mInplace)) {
+ 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()));
+ }
+}
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..cdb0b28
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEnvDumper.java
@@ -0,0 +1,164 @@
+/*
+ * 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.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";
+
+ @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("Dumped file: " + dumpFile.getAbsolutePath());
+ } catch (IOException exception) {
+ CLog.e("Failed to dump app versions to file", e);
+ }
+ }
+
+ /**
+ * Gets environment configurations as string.
+ *
+ * @param deivce testing device
+ * @return environment configuration as string
+ */
+ protected String getEnv(ITestDevice device) throws DeviceNotAvailableException {
+ return getAppVersions(device);
+ }
+
+ /**
+ * 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) {
+ 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..4c849f2
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestFactoryReset.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.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 com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+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;
+ }
+
+ /**
+ * Wipes device via fastboot.
+ *
+ * @param device test device
+ */
+ private 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
+ */
+ private void fastbootFormat(ITestDevice device, String partition)
+ throws DeviceNotAvailableException, TargetSetupError {
+
+ CLog.i("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()));
+ }
+ }
+}
+
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/AfwTestPrivAppInstaller.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPrivAppInstaller.java
new file mode 100644
index 0000000..f2f1a57
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPrivAppInstaller.java
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+ private static final String PRIV_APP_DIR = "/system/priv-app";
+
+ @Option(name = "app-filename",
+ description = "app to install")
+ private Collection<String> mApps = 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));
+ }
+ }
+
+ // 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..4efd8ca
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPullExternalFile.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.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;
+import java.io.IOException;
+
+/**
+ * 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..b066374
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestTargetPreparer.java
@@ -0,0 +1,139 @@
+/*
+ * 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.cts.tradefed.build.CtsBuildHelper;
+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.targetprep.TargetSetupError;
+import com.android.tradefed.util.RunUtil;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Abstract class to keep common functionality.
+ */
+public abstract class AfwTestTargetPreparer {
+
+ private CtsBuildHelper mBuildHelper;
+
+ /**
+ * Gets an instance of {@link CtsBuildHelper}.
+ *
+ * @param buildInfo reference to {@link IBuildInfo}
+ * @return reference to {@link CtsBuildHelper} instance
+ */
+ protected CtsBuildHelper getCtsBuildHelper(IBuildInfo buildInfo) {
+ if (mBuildHelper == null) {
+ mBuildHelper = CtsBuildHelper.createBuildHelper(buildInfo);
+ }
+ return mBuildHelper;
+ }
+
+ /**
+ * Gets repository directory.
+ *
+ * @param buildInfo reference to {@link IBuildInfo}
+ * @return File path to repository folder.
+ */
+ protected File getRepositoryDir(IBuildInfo buildInfo) {
+ return new File(getCtsBuildHelper(buildInfo).getCtsDir(), "repository");
+ }
+
+ /**
+ * 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) {
+ return new File(getCtsBuildHelper(buildInfo).getTestCasesDir(), fileName);
+ }
+
+ /**
+ * 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();
+ }
+}
+
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..832a9a9
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestUserRemover.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 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("Successfully removed user %d on %s", userId, device.getSerialNumber());
+ } else {
+ CLog.e("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..1a039a4
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestWifiPreparer.java
@@ -0,0 +1,147 @@
+/*
+ * 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.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 final class AfwTestWifiPreparer extends AfwTestTargetPreparer implements ITargetCleaner {
+
+ @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-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;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
+ DeviceNotAvailableException {
+
+ if (mConfigFileName == null) {
+ throw new TargetSetupError("wifi-config-file not specified");
+ }
+
+ if (mWifiSsidKey == null) {
+ throw new TargetSetupError("wifi-ssid-key not specified");
+ }
+
+ File configFile = new File(getCtsBuildHelper(buildInfo).getTestCasesDir(), mConfigFileName);
+ Properties props = null;
+ try {
+ props = readProperties(configFile);
+ } catch (IOException e) {
+ throw new TargetSetupError("Failed to read prop file: " + configFile.getAbsolutePath());
+ }
+
+ String wifiSsid = props.getProperty(mWifiSsidKey, "");
+ String wifiPassword = props.getProperty(mWifiPasswordKey, "");
+
+ if (wifiSsid.isEmpty()) {
+ throw new TargetSetupError(
+ mWifiSsidKey + " not specified in file " + configFile.getAbsolutePath());
+ }
+
+ // Implement retry.
+ for (int i = 0; i < mWifiAttempts; ++i) {
+ if (device.connectToWifiNetworkIfNeeded(wifiSsid, wifiPassword)) {
+ return;
+ }
+ 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",
+ wifiSsid, device.getSerialNumber()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+ throws DeviceNotAvailableException {
+
+ if (mDisconnectWifiAfterTest && device.isWifiEnabled()) {
+ 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());
+ }
+ }
+ }
+
+ /**
+ * 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();
+ }
+ }
+ }
+}
+
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..a39a91a
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/AfwTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.cts.tradefed.build.CtsBuildHelper;
+import com.android.cts.tradefed.testtype.CtsTest;
+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.result.ITestInvocationListener;
+import com.android.tradefed.util.FileUtil;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Customized {@link CtsTest} for running afw-test tests.
+ * <p>Supports running all the tests contained in an afw-test plan, or individual test packages.
+ * </p>
+ */
+public class AfwTest extends CtsTest {
+
+ 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;
+
+ /**
+ * A {@link CtsBuildHelper} instance to access afw-test build info, such as the
+ * absolute paths of the test plan file and test case apks.
+ */
+ private CtsBuildHelper mAfwTestBuild = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ public AfwTest() {
+ this(0 /*shardAssignment*/, 1 /*totalShards*/);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public AfwTest(int shardAssignment, int totalShards) {
+ super(shardAssignment, totalShards);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setBuild(IBuildInfo build) {
+ super.setBuild(build);
+
+ mAfwTestBuild = CtsBuildHelper.createBuildHelper(build);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
+ CLog.i("Running afw-test");
+
+ ITestDevice device = getDevice();
+ if (device == 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.
+ device.executeShellCommand(CMD_DISABLE_PKG_VERIFICATION);
+
+ super.run(listener);
+ }
+
+ /**
+ * 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.getTestCasesDir(), "afw-test.props");
+ }
+}
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/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
new file mode 100644
index 0000000..847aeef
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.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.cts.tradefed.build;
+
+import com.android.tradefed.build.FolderBuildInfo;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.IBuildProvider;
+import com.android.tradefed.build.IFolderBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.ddmlib.Log;
+
+import java.io.File;
+
+/**
+ * A simple {@link IBuildProvider} that uses a pre-existing CTS install.
+ */
+public class CtsBuildProvider implements IBuildProvider {
+ private static final String TAG = "afwtest";
+
+ public static final String CTS_BUILD_VERSION = "1.5";
+
+ public static final String CTS_PACKAGE = "com.android.cts.tradefed.testtype";
+
+ @Option(name="cts-install-path", description="the path to the cts installation to use")
+ private String mCtsRootDirPath = System.getProperty("CTS_ROOT");
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IBuildInfo getBuild() {
+ Log.i(TAG, String.format("Afw Test Harness Version: %s", CTS_BUILD_VERSION));
+
+ if (mCtsRootDirPath == null) {
+ throw new IllegalArgumentException("Missing --cts-install-path");
+ }
+ IFolderBuildInfo ctsBuild = new FolderBuildInfo(
+ Package.getPackage(CTS_PACKAGE).getImplementationVersion(), "cts", "cts");
+ ctsBuild.setRootDir(new File(mCtsRootDirPath));
+ return ctsBuild;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void buildNotTested(IBuildInfo info) {
+ // ignore
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void cleanUp(IBuildInfo info) {
+ // ignore
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsInstrumentationApkTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsInstrumentationApkTest.java
new file mode 100644
index 0000000..920fb9f
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsInstrumentationApkTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.cts.tradefed.testtype;
+
+import com.android.afwtest.tradefed.TestConfig;
+import com.android.cts.tradefed.build.CtsBuildHelper;
+import com.android.cts.util.AbiUtils;
+import com.android.ddmlib.Log;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.InstrumentationTest;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An {@link InstrumentationTest} that will install CTS apks
+ * before test execution, and uninstall on execution completion.
+ */
+public class CtsInstrumentationApkTest extends InstrumentationTest implements IBuildReceiver {
+
+ private static final String LOG_TAG = "afwtest.CtsInstrumentationApkTest";
+
+ // 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);
+
+
+ /** The file names of the CTS apks to install */
+ private Collection<String> mInstallFileNames = new ArrayList<String>();
+ private Collection<String> mUninstallPackages = new ArrayList<String>();
+ protected CtsBuildHelper mCtsBuild = null;
+ protected IAbi mAbi = null;
+
+ /**
+ * @param abi the ABI to run the test on
+ */
+ public void setAbi(IAbi abi) {
+ mAbi = abi;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setBuild(IBuildInfo build) {
+ mCtsBuild = CtsBuildHelper.createBuildHelper(build);
+ }
+
+ /**
+ * Add an apk to install.
+ *
+ * @param apkFileName the apk file name
+ * @param packageName the apk's Android package name
+ */
+ public void addInstallApk(String apkFileName, String packageName) {
+ mInstallFileNames.add(apkFileName);
+ mUninstallPackages.add(packageName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void run(final ITestInvocationListener listener)
+ throws DeviceNotAvailableException {
+ ITestDevice testDevice = getDevice();
+
+ Log.d(LOG_TAG, "Running customized CtsInstrumentationRunner.");
+
+ if (testDevice == null) {
+ Log.e(LOG_TAG, "Missing device.");
+ return;
+ }
+ if (mCtsBuild == null) {
+ Log.e(LOG_TAG, "Missing build");
+ return;
+ }
+ boolean success = true;
+ for (String apkFileName : mInstallFileNames) {
+ Log.d(LOG_TAG, String.format("Installing %s on %s", apkFileName,
+ testDevice.getSerialNumber()));
+ try {
+ File apkFile = mCtsBuild.getTestApp(apkFileName);
+ String errorCode = null;
+ String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ errorCode = testDevice.installPackage(apkFile, true, options);
+ if (errorCode != null) {
+ Log.e(LOG_TAG, String.format("Failed to install %s on %s. Reason: %s",
+ apkFileName, testDevice.getSerialNumber(), errorCode));
+ success = false;
+ }
+ } catch (FileNotFoundException e) {
+ Log.e(LOG_TAG, String.format("Could not find file %s", apkFileName));
+ success = false;
+ }
+ }
+ if (success) {
+ 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)));
+
+ super.setTestTimeout((int)testTimeout);
+ // Shell timeout must be larger than test timeout
+ super.setShellTimeout(testTimeout + ONE_MIN_MS);
+ super.run(listener);
+ }
+ for (String packageName : mUninstallPackages) {
+ Log.d(LOG_TAG, String.format("Uninstalling %s on %s", packageName,
+ testDevice.getSerialNumber()));
+ testDevice.uninstallPackage(packageName);
+ }
+ }
+}
diff --git a/tools/utils/afwtest/__init__.py b/tools/utils/afwtest/__init__.py
new file mode 100644
index 0000000..7391cf0
--- /dev/null
+++ b/tools/utils/afwtest/__init__.py
@@ -0,0 +1,19 @@
+#!/usr/bin/python2.4
+
+# Copyright (C) 2009 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 initialization for the afw-test package."""
+
+__all__ = ['tools']
diff --git a/tools/utils/afwtest/tools.py b/tools/utils/afwtest/tools.py
new file mode 100644
index 0000000..3b352ce
--- /dev/null
+++ b/tools/utils/afwtest/tools.py
@@ -0,0 +1,251 @@
+#!/usr/bin/python2.4
+
+# 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.
+
+"""Utility classes for AfW Test."""
+
+import os
+import re
+import xml.dom.minidom as minidom
+
+
+class TestPackage(object):
+ """This class represents a test package.
+
+ Each test package consists of one or more suites, each containing one or more subsuites and/or
+ one or more test cases. Each test case contains one or more tests.
+
+ The package structure is currently stored using Python dictionaries and lists. Translation
+ to XML is done via a DOM tree that gets created on demand.
+
+ TODO: Instead of using an internal data structure, using a DOM tree directly would increase
+ the usability. For example, one could easily create an instance by parsing an existing XML.
+ """
+
+ class TestSuite(object):
+ """A test suite."""
+
+ def __init__(self, is_root=False):
+ self.is_root = is_root
+ self.test_cases = {}
+ self.test_suites = {}
+
+ def Add(self, names):
+ if len(names) == 2:
+ # names contains the names of the test case and the test
+ test_case = self.test_cases.setdefault(names[0], [])
+ test_case.append(names[1])
+ else:
+ sub_suite = self.test_suites.setdefault(names[0], TestPackage.TestSuite())
+ sub_suite.Add(names[1:])
+
+ def WriteDescription(self, doc, parent):
+ """Recursively append all suites and testcases to the parent tag."""
+ for (suite_name, suite) in self.test_suites.iteritems():
+ child = doc.createElement('TestSuite')
+ child.setAttribute('name', suite_name)
+ parent.appendChild(child)
+ # recurse into child suites
+ suite.WriteDescription(doc, child)
+ for (case_name, test_list) in self.test_cases.iteritems():
+ child = doc.createElement('TestCase')
+ child.setAttribute('name', case_name)
+ parent.appendChild(child)
+ for test_name in test_list:
+ test = doc.createElement('Test')
+ test.setAttribute('name', test_name)
+ child.appendChild(test)
+
+ def __init__(self, package_name, app_package_name=''):
+ self.encoding = 'UTF-8'
+ self.attributes = {'name': package_name, 'AndroidFramework': 'Android 1.0',
+ 'version': '1.0', 'targetNameSpace': '', 'targetBinaryName': '',
+ 'jarPath': '', 'appPackageName': app_package_name}
+ self.root_suite = self.TestSuite(is_root=True)
+
+ def AddTest(self, name):
+ """Add a test to the package.
+
+ Test names are given in the form "testSuiteName.testSuiteName.TestCaseName.testName".
+ Test suites can be nested to any depth.
+
+ Args:
+ name: The name of the test to add.
+ """
+ parts = name.split('.')
+ self.root_suite.Add(parts)
+
+ def AddAttribute(self, name, value):
+ """Add an attribute to the test package itself."""
+ self.attributes[name] = value
+
+ def GetDocument(self):
+ """Returns a minidom Document representing the test package structure."""
+ doc = minidom.Document()
+ package = doc.createElement('TestPackage')
+ for (attr, value) in self.attributes.iteritems():
+ package.setAttribute(attr, value)
+ self.root_suite.WriteDescription(doc, package)
+ doc.appendChild(package)
+ return doc
+
+ def WriteDescription(self, writer):
+ """Write the description as XML to the given writer."""
+ doc = self.GetDocument()
+ doc.writexml(writer, addindent=' ', newl='\n', encoding=self.encoding)
+ doc.unlink()
+
+
+class TestPlan(object):
+ """A afw-test test plan generator."""
+
+ def __init__(self, name, all_packages):
+ """Instantiate a test plan with a list of available package names.
+
+ Args:
+ name: The name of the test plan
+ all_packages: The full list of available packages. Subsequent calls to Exclude() and
+ Include() select from the packages given here.
+ """
+ self.name = name
+ self.all_packages = all_packages
+ self.map = None
+
+ def GetName(self):
+ """Gets the name of the test plan.
+
+ Returns:
+ The plan name.
+ """
+
+ return self.name
+
+ def Exclude(self, pattern):
+ """Exclude all packages matching the given regular expression from the plan.
+
+ If this is the first call to Exclude() or Include(), all packages are selected before applying
+ the exclusion.
+
+ Args:
+ pattern: A regular expression selecting the package names to exclude.
+ """
+ if not self.map:
+ self.Include('.*')
+ exp = re.compile(pattern)
+ for package in self.all_packages:
+ if exp.match(package):
+ self.map[package] = False
+
+ def Include(self, pattern):
+ """Include all packages matching the given regular expressions in the plan.
+
+ Args:
+ pattern: A regular expression selecting the package names to include.
+ """
+ if not self.map:
+ self.map = {}
+ for package in self.all_packages:
+ self.map[package] = False
+ exp = re.compile(pattern)
+ for package in self.all_packages:
+ if exp.match(package):
+ self.map[package] = True
+
+ def Write(self, file_name):
+ """Write the test plan to the given file.
+
+ Requires Include() or Exclude() to be called prior to calling this method.
+
+ Args:
+ file_name: The name of the file into which the test plan should be written.
+ """
+ doc = minidom.Document()
+ plan = doc.createElement('TestPlan')
+ plan.setAttribute('version', '1.0')
+ doc.appendChild(plan)
+ for package in self.all_packages:
+ if self.map[package]:
+ entry = doc.createElement('Entry')
+ entry.setAttribute('name', package)
+ plan.appendChild(entry)
+ stream = open(file_name, 'w')
+ doc.writexml(stream, addindent=' ', newl='\n', encoding='UTF-8')
+ stream.close()
+
+def GetTestPlans(test_plan_def_file, test_repository):
+ """Gets all test plans from a test plan definition file.
+
+ Args:
+ test_plan_def_file: The file path of the test plan definitions.
+ test_repository: The directory containing the test packages and their package descriptions.
+
+ Returns:
+ List of TestPlan.
+ """
+
+ plan_def = minidom.parse(test_plan_def_file)
+ plans = []
+
+ for plan in plan_def.getElementsByTagName('plan'):
+ descriptions = []
+ plan_name = plan.getAttribute('name')
+ for p in plan.getElementsByTagName('package'):
+ pkg_name = p.getAttribute('name')
+ descriptions.append(os.path.join(test_repository, pkg_name + '.xml'))
+
+ packages = []
+ for description in descriptions:
+ doc = XmlFile(description)
+ packages.append(doc.GetAttr('TestPackage', 'appPackageName'))
+
+ test_plan = TestPlan(plan_name, packages);
+ test_plan.Include('.*')
+ plans.append(test_plan)
+
+ return plans
+
+
+class XmlFile(object):
+ """This class parses Xml files and allows reading attribute values by tag and attribute name."""
+
+ def __init__(self, path):
+ """Instantiate the class using the manifest file denoted by path."""
+ self.doc = minidom.parse(path)
+
+ def GetAndroidAttr(self, tag, attr_name):
+ """Get the value of the given attribute in the first matching tag.
+
+ Args:
+ tag: The name of the tag to search.
+ attr_name: An attribute name in the android manifest namespace.
+
+ Returns:
+ The value of the given attribute in the first matching tag.
+ """
+ element = self.doc.getElementsByTagName(tag)[0]
+ return element.getAttributeNS('http://schemas.android.com/apk/res/android', attr_name)
+
+ def GetAttr(self, tag, attr_name):
+ """Return the value of the given attribute in the first matching tag.
+
+ Args:
+ tag: The name of the tag to search.
+ attr_name: An attribute name in the default namespace.
+
+ Returns:
+ The value of the given attribute in the first matching tag.
+ """
+ element = self.doc.getElementsByTagName(tag)[0]
+ return element.getAttribute(attr_name)
diff --git a/tools/utils/buildAfwTest.py b/tools/utils/buildAfwTest.py
new file mode 100755
index 0000000..d32719e
--- /dev/null
+++ b/tools/utils/buildAfwTest.py
@@ -0,0 +1,104 @@
+#!/usr/bin/python
+
+# 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.
+
+"""Module for generating AfW test descriptions and test plans."""
+
+import glob
+import os
+import re
+import subprocess
+import sys
+import xml.dom.minidom as dom
+from afwtest import tools
+from multiprocessing import Pool
+
+def GetSubDirectories(root):
+ """Return all directories under the given root directory."""
+ return [x for x in os.listdir(root) if os.path.isdir(os.path.join(root, x))]
+
+
+def GetMakeFileVars(makefile_path):
+ """Extracts variable definitions from the given make file.
+
+ Args:
+ makefile_path: Path to the make file.
+
+ Returns:
+ A dictionary mapping variable names to their assigned value.
+ """
+ result = {}
+ pattern = re.compile(r'^\s*([^:#=\s]+)\s*:=\s*(.*?[^\\])$', re.MULTILINE + re.DOTALL)
+ stream = open(makefile_path, 'r')
+ content = stream.read()
+ for match in pattern.finditer(content):
+ result[match.group(1)] = match.group(2)
+ stream.close()
+ return result
+
+
+class AfwTestBuilder(object):
+ """Main class for generating test descriptions and test plans."""
+
+ def __init__(self, argv):
+ """Initialize the AfwTestBuilder from command line arguments."""
+ if not len(argv) == 6:
+ print 'Usage: %s <testRoot> <xtsOutputDir> <tempDir> <androidRootDir> <planDefFile>' % argv[0]
+ print ''
+ print 'testRoot: Directory under which to search for AfW tests.'
+ print 'afwtestOutputDir: Directory in which the AfW Test repository should be created.'
+ print 'tempDir: Directory to use for storing temporary files.'
+ print 'androidRootDir: Root directory of the Android source tree.'
+ print 'planDefFile: test plan definition file'
+ sys.exit(1)
+ self.test_root = sys.argv[1]
+ self.out_dir = sys.argv[2]
+ self.temp_dir = sys.argv[3]
+ self.android_root = sys.argv[4]
+ self.plan_def_file = sys.argv[5]
+
+ self.test_repository = os.path.join(self.out_dir, 'repository/testcases')
+ self.plan_repository = os.path.join(self.out_dir, 'repository/plans')
+
+ def GenerateTestDescriptions(self):
+ """Generate test descriptions for all packages."""
+ pool = Pool(processes=2)
+
+ # generate test descriptions for android tests
+ results = []
+ pool.close()
+ pool.join()
+ return sum(map(lambda result: result.get(), results))
+
+ def __WritePlan(self, plan):
+ print 'Generating test plan %s' % plan.GetName()
+ plan.Write(os.path.join(self.plan_repository, plan.GetName() + '.xml'))
+
+ def GenerateTestPlans(self):
+ """Generate test plans."""
+
+ test_plans = tools.GetTestPlans(self.plan_def_file, self.test_repository)
+ for plan in test_plans:
+ self.__WritePlan(plan)
+
+def LogGenerateDescription(name):
+ print 'Generating test description for package %s' % name
+
+if __name__ == '__main__':
+ builder = AfwTestBuilder(sys.argv)
+ result = builder.GenerateTestDescriptions()
+ if result != 0:
+ sys.exit(result)
+ builder.GenerateTestPlans()