DO NOT MERGE - Merge pi-platform-release (PPRL.190505.001) into stage-aosp-master

Bug: 132622481
Change-Id: I4827bec08e2a14687627fd6ef9c40e3b86d4c7ec
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index 94a0e94..0000000
--- a/Android.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-include $(call all-subdir-makefiles)
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..d5f07b9
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,14 @@
+#  Android EngProd Approvers
+guangzhu@google.com
+fdeng@google.com
+moonk@google.com
+jdesprez@google.com
+sbasi@google.com
+
+# Android Partner Eng Approvers
+aaronholden@google.com
+yuji@google.com
+nickrose@google.com
+
+# File Specific Approvers
+per-file Backup* = anniemeng@google.com, brufino@google.com, nathch@google.com, rthakohov@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..12604f9
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,11 @@
+[Builtin Hooks]
+google_java_format = true
+
+[Tool Paths]
+google-java-format = ${REPO_ROOT}/prebuilts/tools/common/google-java-format/google-java-format
+google-java-format-diff = ${REPO_ROOT}/prebuilts/tools/common/google-java-format/google-java-format-diff.py
+
+[Hook Scripts]
+# `^.` is a RegExr that matches any character at the beginning, so this hook
+# is basically applied to ALL files in a git commit.
+aospcheck_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "^."
diff --git a/build/Android.bp b/build/Android.bp
new file mode 100644
index 0000000..fcf438f
--- /dev/null
+++ b/build/Android.bp
@@ -0,0 +1,16 @@
+bootstrap_go_package {
+    name: "soong-suite-harness",
+    pkgPath: "android/soong/suite_harness",
+    deps: [
+        "blueprint",
+        "blueprint-pathtools",
+        "blueprint-proptools",
+        "soong",
+        "soong-android",
+        "soong-java",
+    ],
+    srcs: [
+        "tradefed_binary.go",
+    ],
+    pluginFor: ["soong_build"],
+}
diff --git a/build/tradefed_binary.go b/build/tradefed_binary.go
new file mode 100644
index 0000000..6e11eac
--- /dev/null
+++ b/build/tradefed_binary.go
@@ -0,0 +1,156 @@
+// Copyright 2018 Google Inc. All rights reserved.
+//
+// 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 suite_harness
+
+import (
+	"strings"
+
+	"github.com/google/blueprint"
+
+	"android/soong/android"
+	"android/soong/java"
+)
+
+var pctx = android.NewPackageContext("android/soong/suite_harness")
+
+func init() {
+	android.RegisterModuleType("tradefed_binary_host", tradefedBinaryFactory)
+
+	pctx.Import("android/soong/android")
+}
+
+type TradefedBinaryProperties struct {
+	Short_name string
+	Full_name  string
+	Version    string
+}
+
+// tradefedBinaryFactory creates an empty module for the tradefed_binary module type,
+// which is a java_binary with some additional processing in tradefedBinaryLoadHook.
+func tradefedBinaryFactory() android.Module {
+	props := &TradefedBinaryProperties{}
+	module := java.BinaryHostFactory()
+	module.AddProperties(props)
+	android.AddLoadHook(module, tradefedBinaryLoadHook(props))
+
+	return module
+}
+
+const genSuffix = "-gen"
+
+// tradefedBinaryLoadHook adds extra resources and libraries to tradefed_binary modules.
+func tradefedBinaryLoadHook(tfb *TradefedBinaryProperties) func(ctx android.LoadHookContext) {
+	return func(ctx android.LoadHookContext) {
+		genName := ctx.ModuleName() + genSuffix
+
+		// Create a submodule that generates the test-suite-info.properties file
+		// and copies DynamicConfig.xml if it is present.
+		ctx.CreateModule(android.ModuleFactoryAdaptor(tradefedBinaryGenFactory),
+			&TradefedBinaryGenProperties{
+				Name:       &genName,
+				Short_name: tfb.Short_name,
+				Full_name:  tfb.Full_name,
+				Version:    tfb.Version,
+			})
+
+		props := struct {
+			Java_resources []string
+			Libs           []string
+		}{}
+
+		// Add dependencies required by all tradefed_binary modules.
+		props.Libs = []string{
+			"tradefed",
+			"loganalysis",
+			"hosttestlib",
+			"compatibility-host-util",
+		}
+
+		// Add the files generated by the submodule created above to the resources.
+		props.Java_resources = []string{":" + genName}
+
+		ctx.AppendProperties(&props)
+
+	}
+}
+
+type TradefedBinaryGenProperties struct {
+	Name       *string
+	Short_name string
+	Full_name  string
+	Version    string
+}
+
+type tradefedBinaryGen struct {
+	android.ModuleBase
+
+	properties TradefedBinaryGenProperties
+
+	gen android.Paths
+}
+
+func tradefedBinaryGenFactory() android.Module {
+	tfg := &tradefedBinaryGen{}
+	tfg.AddProperties(&tfg.properties)
+	android.InitAndroidModule(tfg)
+	return tfg
+}
+
+func (tfg *tradefedBinaryGen) DepsMutator(android.BottomUpMutatorContext) {}
+
+var tradefedBinaryGenRule = pctx.StaticRule("tradefedBinaryGenRule", blueprint.RuleParams{
+	Command: `rm -f $out && touch $out && ` +
+		`echo "# This file is auto generated by Android.mk. Do not modify." >> $out && ` +
+		`echo "build_number = ${buildNumber}" >> $out && ` +
+		`echo "target_arch = ${arch}" >> $out && ` +
+		`echo "name = ${name}" >> $out && ` +
+		`echo "fullname = ${fullname}" >> $out && ` +
+		`echo "version = ${version}" >> $out`,
+}, "buildNumber", "arch", "name", "fullname", "version")
+
+func (tfg *tradefedBinaryGen) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	outputFile := android.PathForModuleOut(ctx, "test-suite-info.properties")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   tradefedBinaryGenRule,
+		Output: outputFile,
+		Args: map[string]string{
+			"buildNumber": ctx.Config().BuildNumberFromFile(),
+			"arch":        ctx.Config().DevicePrimaryArchType().String(),
+			"name":        tfg.properties.Short_name,
+			"fullname":    tfg.properties.Full_name,
+			"version":     tfg.properties.Version,
+		},
+	})
+
+	tfg.gen = append(tfg.gen, outputFile)
+
+	dynamicConfig := android.ExistentPathForSource(ctx, ctx.ModuleDir(), "DynamicConfig.xml")
+	if dynamicConfig.Valid() {
+		outputFile := android.PathForModuleOut(ctx, strings.TrimSuffix(ctx.ModuleName(), genSuffix)+".dynamic")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.Cp,
+			Input:  dynamicConfig.Path(),
+			Output: outputFile,
+		})
+
+		tfg.gen = append(tfg.gen, outputFile)
+	}
+}
+
+func (tfg *tradefedBinaryGen) Srcs() android.Paths {
+	return append(android.Paths(nil), tfg.gen...)
+}
+
+var _ android.SourceFileProducer = (*tradefedBinaryGen)(nil)
diff --git a/common/Android.mk b/common/Android.mk
deleted file mode 100644
index a8b6af7..0000000
--- a/common/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-include $(call all-subdir-makefiles)
diff --git a/common/host-side/Android.mk b/common/host-side/Android.mk
deleted file mode 100644
index a8b6af7..0000000
--- a/common/host-side/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-include $(call all-subdir-makefiles)
diff --git a/common/host-side/manifest-generator/Android.bp b/common/host-side/manifest-generator/Android.bp
new file mode 100644
index 0000000..9d6cdb8
--- /dev/null
+++ b/common/host-side/manifest-generator/Android.bp
@@ -0,0 +1,25 @@
+// Copyright (C) 2015 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.
+
+java_library_host {
+    name: "compatibility-manifest-generator",
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: ["kxml2-2.3.0"],
+
+    manifest: "MANIFEST.mf",
+
+    use_tools_jar: true,
+}
diff --git a/common/host-side/manifest-generator/Android.mk b/common/host-side/manifest-generator/Android.mk
deleted file mode 100644
index b976329..0000000
--- a/common/host-side/manifest-generator/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_STATIC_JAVA_LIBRARIES := kxml2-2.3.0
-
-LOCAL_JAR_MANIFEST := MANIFEST.mf
-
-LOCAL_CLASSPATH := $(HOST_JDK_TOOLS_JAR)
-
-LOCAL_MODULE := compatibility-manifest-generator
-
-LOCAL_MODULE_TAGS := optional
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/common/host-side/manifest-generator/tests/Android.bp b/common/host-side/manifest-generator/tests/Android.bp
new file mode 100644
index 0000000..b8f012d
--- /dev/null
+++ b/common/host-side/manifest-generator/tests/Android.bp
@@ -0,0 +1,24 @@
+// Copyright (C) 2015 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.
+
+java_test_host {
+    name: "compatibility-manifest-generator-tests",
+
+    srcs: ["src/**/*.java"],
+
+    libs: [
+        "compatibility-manifest-generator",
+        "junit",
+    ],
+}
diff --git a/common/host-side/manifest-generator/tests/Android.mk b/common/host-side/manifest-generator/tests/Android.mk
deleted file mode 100644
index 1601fc8..0000000
--- a/common/host-side/manifest-generator/tests/Android.mk
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright (C) 2015 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_LIBRARIES := compatibility-manifest-generator junit-host
-
-LOCAL_MODULE := compatibility-manifest-generator-tests
-
-LOCAL_MODULE_TAGS := optional
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/common/host-side/tradefed/.classpath b/common/host-side/tradefed/.classpath
index 2c7514c..375e89b 100644
--- a/common/host-side/tradefed/.classpath
+++ b/common/host-side/tradefed/.classpath
@@ -6,14 +6,14 @@
 	<classpathentry kind="src" path="res"/>
 	<classpathentry kind="src" path="tests/src"/>
 	<classpathentry kind="lib" path="tests/res/testtype/testJar1.jar"/>
-	<classpathentry kind="lib" path="tests/res/testtype/testJar2.jar"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/tradefederation"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/cts-common-util"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
-	<classpathentry kind="var" path="TRADEFED_ROOT/out/host/common/obj/JAVA_LIBRARIES/jsonlib_intermediates/classes.jar"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/cts-hostside-util"/>
-	<classpathentry kind="var" path="TRADEFED_ROOT/out/host/common/obj/JAVA_LIBRARIES/google-api-java-client-min-repackaged_intermediates/classes.jar"/>
 	<classpathentry kind="var" path="TRADEFED_ROOT/out/host/common/obj/JAVA_LIBRARIES/host-libprotobuf-java-full_intermediates/classes.jar"/>
+	<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/cts/libs/json/json/linux_glibc_common/javac/json.jar"/>
+	<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/tools/tradefederation/core/tradefed-protos/linux_glibc_common/combined/tradefed-protos.jar"/>
+	<classpathentry kind="var" path="TRADEFED_ROOT/prebuilts/tools/common/google-api-java-client/1.23.0/google-api-java-client-min-repackaged-1.23.0.jar"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/common/host-side/tradefed/Android.bp b/common/host-side/tradefed/Android.bp
new file mode 100644
index 0000000..aba4209
--- /dev/null
+++ b/common/host-side/tradefed/Android.bp
@@ -0,0 +1,25 @@
+// Copyright (C) 2018 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.
+
+java_library_host {
+    name: "compatibility-tradefed",
+    srcs: ["src/**/*.java"],
+    java_resource_dirs: ["res"],
+    defaults: ["tradefed_errorprone_defaults"],
+    libs: [
+        "tradefed",
+        "compatibility-host-util",
+        "google-api-java-client-min-repackaged",
+    ],
+}
diff --git a/common/host-side/tradefed/Android.mk b/common/host-side/tradefed/Android.mk
deleted file mode 100644
index 3ed225e..0000000
--- a/common/host-side/tradefed/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-include $(call all-subdir-makefiles)
\ No newline at end of file
diff --git a/common/host-side/tradefed/res/config/common-compatibility-config.xml b/common/host-side/tradefed/res/config/common-compatibility-config.xml
index f72efb8..8182ddf 100644
--- a/common/host-side/tradefed/res/config/common-compatibility-config.xml
+++ b/common/host-side/tradefed/res/config/common-compatibility-config.xml
@@ -15,7 +15,6 @@
 -->
 <configuration description="Common config for Compatibility suites">
     <option name="dynamic-sharding" value="true" />
-    <option name="disable-strict-sharding" value="true" />
     <device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" />
     <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
     <test class="com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite" />
@@ -29,6 +28,6 @@
         <option name="log-level-display" value="WARN" />
     </logger>
     <result_reporter class="com.android.compatibility.common.tradefed.result.ConsoleReporter" />
-    <result_reporter class="com.android.compatibility.common.tradefed.result.ResultReporter" />
-    <result_reporter class="com.android.tradefed.result.suite.SuiteResultReporter" />
+    <result_reporter class="com.android.compatibility.common.tradefed.result.suite.CompatibilityProtoResultReporter" />
+    <result_reporter class="com.android.compatibility.common.tradefed.result.suite.CertificationSuiteResultReporter" />
 </configuration>
diff --git a/common/host-side/tradefed/res/report/compatibility_failures.xsl b/common/host-side/tradefed/res/report/compatibility_failures.xsl
index 46055d8..2c94ac8 100644
--- a/common/host-side/tradefed/res/report/compatibility_failures.xsl
+++ b/common/host-side/tradefed/res/report/compatibility_failures.xsl
@@ -128,6 +128,8 @@
                             <th>Module</th>
                             <th>Passed</th>
                             <th>Failed</th>
+                            <th>Assumption Failure</th>
+                            <th>Ignored</th>
                             <th>Total Tests</th>
                             <th>Done</th>
                         </tr>
@@ -149,7 +151,13 @@
                                     <xsl:value-of select="count(TestCase/Test[@result = 'fail'])"/>
                                 </td>
                                 <td>
-                                    <xsl:value-of select="count(TestCase/Test[@result = 'fail']) + @pass"/>
+                                    <xsl:value-of select="count(TestCase/Test[@result = 'ASSUMPTION_FAILURE'])"/>
+                                </td>
+                                <td>
+                                    <xsl:value-of select="count(TestCase/Test[@result = 'IGNORED'])"/>
+                                </td>
+                                <td>
+                                    <xsl:value-of select="@total_tests"/>
                                 </td>
                                 <td>
                                     <xsl:value-of select="@done"/>
@@ -210,6 +218,15 @@
                                             <td class="failuredetails"/>
                                         </xsl:if>
 
+                                        <xsl:if test="@result='IGNORED'">
+                                            <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;">
@@ -223,6 +240,19 @@
                                             </td>
                                         </xsl:if>
 
+                                        <xsl:if test="@result='ASSUMPTION_FAILURE'">
+                                            <td class="pass">
+                                                <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="Failure/@message"/>
+                                                </div>
+                                            </td>
+                                        </xsl:if>
+
                                         <xsl:if test="@result='not_executed'">
                                             <td class="not_executed">
                                                 <div style="text-align: center; margin-left:auto; margin-right:auto;">
diff --git a/common/host-side/tradefed/res/report/compatibility_result.xsl b/common/host-side/tradefed/res/report/compatibility_result.xsl
index 870f994..531107f 100644
--- a/common/host-side/tradefed/res/report/compatibility_result.xsl
+++ b/common/host-side/tradefed/res/report/compatibility_result.xsl
@@ -17,7 +17,7 @@
 <!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:output method="html" version="5.0" encoding="UTF-8" indent="yes"/>
 
     <xsl:template match="/">
 
@@ -128,6 +128,8 @@
                             <th>Module</th>
                             <th>Passed</th>
                             <th>Failed</th>
+                            <th>Assumption Failure</th>
+                            <th>Ignored</th>
                             <th>Total Tests</th>
                             <th>Done</th>
                         </tr>
@@ -144,6 +146,12 @@
                                     <xsl:value-of select="count(TestCase/Test[@result = 'fail'])"/>
                                 </td>
                                 <td>
+                                    <xsl:value-of select="count(TestCase/Test[@result = 'ASSUMPTION_FAILURE'])"/>
+                                </td>
+                                <td>
+                                    <xsl:value-of select="count(TestCase/Test[@result = 'IGNORED'])"/>
+                                </td>
+                                <td>
                                     <xsl:value-of select="count(TestCase/Test)"/>
                                 </td>
                                 <td>
@@ -224,6 +232,15 @@
                                             <td class="failuredetails"/>
                                         </xsl:if>
 
+                                        <xsl:if test="@result='IGNORED'">
+                                            <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;">
@@ -233,7 +250,27 @@
                                             <td class="failuredetails">
                                                 <div class="details">
                                                     <xsl:choose>
-                                                        <xsl:when test="$fullStackTrace=true()">
+                                                        <xsl:when test="boolean($fullStackTrace)=true()">
+                                                            <xsl:value-of select="Failure/StackTrace" />
+                                                        </xsl:when>
+                                                        <xsl:otherwise>
+                                                            <xsl:value-of select="Failure/@message"/>
+                                                        </xsl:otherwise>
+                                                    </xsl:choose>
+                                                </div>
+                                            </td>
+                                        </xsl:if>
+
+                                        <xsl:if test="@result='ASSUMPTION_FAILURE'">
+                                            <td class="pass">
+                                                <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:choose>
+                                                        <xsl:when test="boolean($fullStackTrace)=true()">
                                                             <xsl:value-of select="Failure/StackTrace" />
                                                         </xsl:when>
                                                         <xsl:otherwise>
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
index 1b20fdd..0cb312a 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
@@ -19,10 +19,13 @@
 import com.android.tradefed.build.IDeviceBuildInfo;
 import com.android.tradefed.build.IFolderBuildInfo;
 import com.android.tradefed.build.VersionedFile;
+import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.util.FileUtil;
 
+
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.HashMap;
@@ -47,8 +50,6 @@
     private static final String DYNAMIC_CONFIG_OVERRIDE_URL = "DYNAMIC_CONFIG_OVERRIDE_URL";
     private static final String BUSINESS_LOGIC_HOST_FILE = "BUSINESS_LOGIC_HOST_FILE";
     private static final String RETRY_COMMAND_LINE_ARGS = "retry_command_line_args";
-    private static final String ALT_HOST_TESTCASE_DIR = "ANDROID_HOST_OUT_TESTCASES";
-    private static final String ALT_TARGET_TESTCASE_DIR = "ANDROID_TARGET_OUT_TESTCASES";
 
     private static final String CONFIG_PATH_PREFIX = "DYNAMIC_CONFIG_FILE:";
 
@@ -118,8 +119,25 @@
                 CONFIG_PATH_PREFIX + moduleName /* version */);
     }
 
+    /**
+     * Set the business logic file for this invocation.
+     *
+     * @param hostFile The business logic host file.
+     */
     public void setBusinessLogicHostFile(File hostFile) {
-        mBuildInfo.addBuildAttribute(BUSINESS_LOGIC_HOST_FILE, hostFile.getAbsolutePath());
+        setBusinessLogicHostFile(hostFile, null);
+    }
+
+    /**
+     * Set the business logic file with specific module id for this invocation.
+     *
+     * @param hostFile The business logic host file.
+     * @param moduleId The name of the moduleId.
+     */
+    public void setBusinessLogicHostFile(File hostFile, String moduleId) {
+        String key = (moduleId == null) ? "" : moduleId;
+        mBuildInfo.setFile(BUSINESS_LOGIC_HOST_FILE + key, hostFile,
+                hostFile.getName()/* version */);
     }
 
     public void setModuleIds(String[] moduleIds) {
@@ -145,7 +163,19 @@
      * @return whether the business logic file has been set for this invocation.
      */
     public boolean hasBusinessLogicHostFile() {
-        return mBuildInfo.getBuildAttributes().get(BUSINESS_LOGIC_HOST_FILE) != null;
+        return hasBusinessLogicHostFile(null);
+    }
+
+    /**
+     * Check whether the business logic file has been set with specific module id for this
+     * invocation.
+     *
+     * @param moduleId The name of the moduleId.
+     * @return True if the business logic file has been set. False otherwise.
+     */
+    public boolean hasBusinessLogicHostFile(String moduleId) {
+        String key = (moduleId == null) ? "" : moduleId;
+        return mBuildInfo.getFile(BUSINESS_LOGIC_HOST_FILE + key) != null;
     }
 
     /**
@@ -153,8 +183,19 @@
      * invocation, or null if the business logic file has not been set.
      */
     public File getBusinessLogicHostFile() {
-        return (hasBusinessLogicHostFile()) ?
-                new File(mBuildInfo.getBuildAttributes().get(BUSINESS_LOGIC_HOST_FILE)) : null;
+        return getBusinessLogicHostFile(null);
+    }
+
+    /**
+     * Get the file containing business logic data with specific module id for this invocation.
+     *
+     * @param moduleId The name of the moduleId.
+     * @return a {@link File} representing the file containing business logic data with
+     * specific module id for this invocation , or null if the business logic file has not been set.
+     */
+    public File getBusinessLogicHostFile(String moduleId) {
+        String key = (moduleId == null) ? "" : moduleId;
+        return mBuildInfo.getFile(BUSINESS_LOGIC_HOST_FILE + key);
     }
 
     /**
@@ -237,12 +278,13 @@
      * @throws FileNotFoundException if the directory structure is not valid.
      */
     public File getTestsDir() throws FileNotFoundException {
-        // We have 3 options that can be the test modules dir (and we're going
+        // We have 2 options that can be the test modules dir (and we're going
         // look for them in the following order):
         //   1. ../android-*ts/testcases/
-        //   2. ALT_HOST_TESTCASE_DIR
-        //   3. ALT_TARGET_TESTCASE_DIR (we'll skip this since if #2 fails, this
-        //      will inevitably fail as well.)
+        //   2. The build info tests dir
+        // ANDROID_HOST_OUT and ANDROID_TARGET_OUT are already linked
+        // by tradefed to the tests dir when they exists so there is
+        // no need to explicitly search them.
 
         File testsDir = null;
         try {
@@ -258,13 +300,6 @@
             }
         }
 
-        if (testsDir == null) {
-            String altTestsDir = System.getenv().get(ALT_HOST_TESTCASE_DIR);
-            if (altTestsDir != null) {
-                testsDir = new File(altTestsDir);
-            }
-        }
-
         // This just means we have no signs of where to check for the test dir.
         if (testsDir == null) {
             throw new FileNotFoundException(
@@ -285,31 +320,26 @@
      * @throws FileNotFoundException if the test file cannot be found
      */
     public File getTestFile(String filename) throws FileNotFoundException {
-        // We have a lot of places to check for the test file.
-        //   1. ../android-*ts/testcases/
-        //   2. ALT_HOST_TESTCASE_DIR/
-        //   3. ALT_TARGET_TESTCASE_DIR/
+        return getTestFile(filename, null);
+    }
 
-        // Our search depends on our run env, if we're in *ts, then we only want
-        // to check #1.  If we're in gen tf, then we only want to check #2/3.
-        // In *ts mode, getTestsDir will return #1, in gen tf mode, it'll return
-        // #2.  In the event we're in *ts mode and the file isn't in #1, #2 or
-        // #3, then the user probably needs to run lunch to setup the env.
-        String altTargetTestDir = System.getenv().get(ALT_TARGET_TESTCASE_DIR);
-        if (altTargetTestDir == null) {
-            altTargetTestDir = "";
-        }
-        String[] testDirs = {getTestsDir().toString(), altTargetTestDir};
+    /**
+     * @return a {@link File} representing the test file in the test modules directory.
+     * @throws FileNotFoundException if the test file cannot be found
+     */
+    public File getTestFile(String filename, IAbi abi) throws FileNotFoundException {
+        File[] testDirs = {getTestsDir()};
 
-        File testFile;
-        for (String testDir: testDirs) {
-            testFile = new File(testDir, filename);
-            if (testFile.exists()) {
-                return testFile;
+        // The file may be in a subdirectory so do a more through search
+        // if it did not exist.
+        File testFile = null;
+        for (File testDir: testDirs) {
+            try {
+                testFile = FileUtil.findFile(filename, abi, testDir);
+            } catch (IOException e) {
+                throw new FileNotFoundException(String.format(
+                        "Failure in finding compatibility test file %s due to %s", filename, e));
             }
-            // The file may be in a subdirectory so do a more through search
-            // if it did not exist.
-            testFile = FileUtil.findFile(new File(testDir), filename);
             if (testFile != null) {
                 return testFile;
             }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
index 5dcf2dd..7747ca5 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
@@ -18,30 +18,41 @@
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider;
 import com.android.compatibility.common.tradefed.result.SubPlanHelper;
+import com.android.compatibility.common.tradefed.result.suite.CertificationResultXml;
 import com.android.compatibility.common.tradefed.testtype.ModuleRepo;
-import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite;
 import com.android.compatibility.common.util.ResultHandler;
-import com.android.compatibility.common.util.TestStatus;
 import com.android.tradefed.build.BuildRetrievalError;
+import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.command.Console;
 import com.android.tradefed.config.ArgsOptionParser;
 import com.android.tradefed.config.ConfigurationException;
 import com.android.tradefed.config.ConfigurationFactory;
 import com.android.tradefed.config.IConfiguration;
 import com.android.tradefed.config.IConfigurationFactory;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.suite.SuiteResultHolder;
+import com.android.tradefed.testtype.Abi;
+import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.IRuntimeHintProvider;
 import com.android.tradefed.testtype.suite.TestSuiteInfo;
-import com.android.tradefed.util.ArrayUtil;
+import com.android.tradefed.util.AbiUtils;
 import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.MultiMap;
 import com.android.tradefed.util.Pair;
 import com.android.tradefed.util.RegexTrie;
 import com.android.tradefed.util.TableFormatter;
 import com.android.tradefed.util.TimeUtil;
+import com.android.tradefed.util.VersionParser;
+
+import com.google.common.base.Joiner;
 
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -49,6 +60,8 @@
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -67,16 +80,26 @@
         MODULE_SPLIT_EXCLUSIONS.add("CtsDeqpTestCases");
     }
     private final static String ADD_PATTERN = "a(?:dd)?";
+    private static final String LATEST_RESULT_DIR = "latest";
     private CompatibilityBuildHelper mBuildHelper;
+    private IBuildInfo mBuildInfo;
 
     /**
      * {@inheritDoc}
      */
     @Override
     public void run() {
-        printLine(String.format("Android %s %s (%s)", TestSuiteInfo.getInstance().getFullName(),
-                TestSuiteInfo.getInstance().getVersion(),
-                TestSuiteInfo.getInstance().getBuildNumber()));
+        String buildNumber = TestSuiteInfo.getInstance().getBuildNumber();
+        String versionFile = VersionParser.fetchVersion();
+        if (versionFile != null) {
+            buildNumber = versionFile;
+        }
+        printLine(
+                String.format(
+                        "Android %s %s (%s)",
+                        TestSuiteInfo.getInstance().getFullName(),
+                        TestSuiteInfo.getInstance().getVersion(),
+                        buildNumber));
         printLine("Use \"help\" or \"help all\" to get more information on running commands.");
         super.run();
     }
@@ -225,24 +248,25 @@
     }
 
     private void listModules() {
-        File[] files = null;
-        try {
-            files = getBuildHelper().getTestsDir().listFiles(new ModuleRepo.ConfigFilter());
-        } catch (FileNotFoundException e) {
-            printLine(e.getMessage());
-            e.printStackTrace();
-        }
-        if (files != null && files.length > 0) {
-            List<String> modules = new ArrayList<>();
-            for (File moduleFile : files) {
-                modules.add(FileUtil.getBaseName(moduleFile.getName()));
+        CompatibilityTestSuite test = new CompatibilityTestSuite() {
+            @Override
+            public Set<IAbi> getAbis(ITestDevice device) throws DeviceNotAvailableException {
+                Set<String> abiStrings = getAbisForBuildTargetArch();
+                Set<IAbi> abis = new LinkedHashSet<>();
+                for (String abi : abiStrings) {
+                    if (AbiUtils.isAbiSupportedByCompatibility(abi)) {
+                        abis.add(new Abi(abi, AbiUtils.getBitness(abi)));
+                    }
+                }
+                return abis;
             }
-            Collections.sort(modules);
-            for (String module : modules) {
-                printLine(module);
-            }
+        };
+        if (getBuild() != null) {
+            test.setBuild(getBuild());
+            LinkedHashMap<String, IConfiguration> configs = test.loadTests();
+            printLine(String.format("%s", Joiner.on("\n").join(configs.keySet())));
         } else {
-            printLine("No modules found");
+            printLine("Error fetching information about modules.");
         }
     }
 
@@ -346,48 +370,70 @@
     private void listResults() {
         TableFormatter tableFormatter = new TableFormatter();
         List<List<String>> table = new ArrayList<>();
-        List<IInvocationResult> results = null;
+
+        List<File> resultDirs = null;
+        Map<SuiteResultHolder, File> holders = new LinkedHashMap<>();
         try {
-            results = ResultHandler.getLightResults(getBuildHelper().getResultsDir());
+            resultDirs = getResults(getBuildHelper().getResultsDir());
         } catch (FileNotFoundException e) {
             throw new RuntimeException("Error while parsing results directory", e);
         }
-        if (results.size() > 0) {
-            for (int i = 0; i < results.size(); i++) {
-                IInvocationResult result = results.get(i);
-                Map<String, String> invocationInfo = result.getInvocationInfo();
-
-                // invocation attributes are not always present (e.g. in the case of halted runs)
-                // replace null entries with the string "Unknown"
-                for (Map.Entry<String, String> entry : invocationInfo.entrySet()) {
-                    if (entry.getValue() == null) {
-                        invocationInfo.put(entry.getKey(), "Unknown");
-                    }
-                }
-
-                String moduleProgress = String.format("%d of %d",
-                        result.getModuleCompleteCount(), result.getModules().size());
-
-                table.add(Arrays.asList(
-                        Integer.toString(i),
-                        Integer.toString(result.countResults(TestStatus.PASS)),
-                        Integer.toString(result.countResults(TestStatus.FAIL)),
-                        moduleProgress,
-                        CompatibilityBuildHelper.getDirSuffix(result.getStartTime()),
-                        result.getTestPlan(),
-                        ArrayUtil.join(", ", result.getDeviceSerials()),
-                        invocationInfo.get("build_id"),
-                        invocationInfo.get("build_product")
-                        ));
+        CertificationResultXml xmlParser = new CertificationResultXml();
+        for (File resultDir : resultDirs) {
+            if (LATEST_RESULT_DIR.equals(resultDir.getName())) {
+                continue;
             }
-
-            // add the table header to the beginning of the list
-            table.add(0, Arrays.asList("Session", "Pass", "Fail", "Modules Complete",
-                "Result Directory", "Test Plan", "Device serial(s)", "Build ID", "Product"));
-            tableFormatter.displayTable(table, new PrintWriter(System.out, true));
-        } else {
-            printLine(String.format("No results found"));
+            try {
+                holders.put(xmlParser.parseResults(resultDir, true), resultDir);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
         }
+
+        if (holders.isEmpty()) {
+            printLine(String.format("No results found"));
+            return;
+        }
+        int i = 0;
+        for (SuiteResultHolder holder : holders.keySet()) {
+            String moduleProgress = String.format("%d of %d",
+                    holder.completeModules, holder.totalModules);
+
+            table.add(
+                    Arrays.asList(
+                            Integer.toString(i),
+                            Long.toString(holder.passedTests),
+                            Long.toString(holder.failedTests),
+                            moduleProgress,
+                            holders.get(holder).getName(),
+                            holder.context
+                                    .getAttributes()
+                                    .get(CertificationResultXml.SUITE_PLAN_ATTR)
+                                    .get(0),
+                            Joiner.on(", ").join(holder.context.getShardsSerials().values()),
+                            printAttributes(holder.context.getAttributes(), "build_id"),
+                            printAttributes(holder.context.getAttributes(), "build_product")));
+            i++;
+        }
+
+        // add the table header to the beginning of the list
+        table.add(0, Arrays.asList("Session", "Pass", "Fail", "Modules Complete",
+                "Result Directory", "Test Plan", "Device serial(s)", "Build ID", "Product"));
+        tableFormatter.displayTable(table, new PrintWriter(System.out, true));
+    }
+
+    private String printAttributes(MultiMap<String, String> map, String key) {
+        if (map.get(key) == null) {
+            return "unknown";
+        }
+        return map.get(key).get(0);
+    }
+
+    /**
+     * Returns the list of all results directories.
+     */
+    private List<File> getResults(File resultsDir) {
+        return ResultHandler.getResultDirectories(resultsDir);
     }
 
     private void listSubPlans() {
@@ -427,14 +473,25 @@
 
     private CompatibilityBuildHelper getBuildHelper() {
         if (mBuildHelper == null) {
+            IBuildInfo build = getBuild();
+            if (build == null) {
+                return null;
+            }
+            mBuildHelper = new CompatibilityBuildHelper(build);
+        }
+        return mBuildHelper;
+    }
+
+    private IBuildInfo getBuild() {
+        if (mBuildInfo == null) {
             try {
                 CompatibilityBuildProvider buildProvider = new CompatibilityBuildProvider();
-                mBuildHelper = new CompatibilityBuildHelper(buildProvider.getBuild());
+                mBuildInfo = buildProvider.getBuild();
             } catch (BuildRetrievalError e) {
                 e.printStackTrace();
             }
         }
-        return mBuildHelper;
+        return mBuildInfo;
     }
 
     public static void main(String[] args) throws InterruptedException, ConfigurationException {
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/InvocationFailureHandler.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/InvocationFailureHandler.java
index dffe3ba..abbf14c 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/InvocationFailureHandler.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/InvocationFailureHandler.java
@@ -60,8 +60,9 @@
             File f = buildHelper.getInvocationFailureFile();
             if (!f.exists()) {
                 f.createNewFile();
-                FileUtil.writeToFile(cause.toString(), f);
             }
+            // Append to previous failures to get them all.
+            FileUtil.writeToFile(cause.toString(), f, true);
         } catch (IOException e) {
             CLog.e("Exception while writing invocation failure file.");
             CLog.e(e);
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
index 9d670df..ede5b1f 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
@@ -33,6 +33,8 @@
 import com.android.compatibility.common.util.TestStatus;
 import com.android.ddmlib.Log.LogLevel;
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IConfigurationReceiver;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.Option.Importance;
 import com.android.tradefed.config.OptionClass;
@@ -60,6 +62,7 @@
 import com.android.tradefed.util.proto.TfMetricProtoUtil;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.xml.XmlEscapers;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -85,7 +88,7 @@
  */
 @OptionClass(alias="result-reporter")
 public class ResultReporter implements ILogSaverListener, ITestInvocationListener,
-       ITestSummaryListener, IShardableListener {
+       ITestSummaryListener, IShardableListener, IConfigurationReceiver {
 
     public static final String INCLUDE_HTML_IN_ZIP = "html-in-zip";
     private static final String UNKNOWN_DEVICE = "unknown_device";
@@ -182,6 +185,9 @@
     // Elapsed time from invocation started to ended.
     private long mElapsedTime;
 
+    /** Invocation level configuration */
+    private IConfiguration mConfiguration = null;
+
     /**
      * Default constructor.
      */
@@ -198,6 +204,12 @@
         mMasterResultReporter = masterResultReporter;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public void setConfiguration(IConfiguration configuration) {
+        mConfiguration = configuration;
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -381,7 +393,7 @@
      */
     @Override
     public void testFailed(TestDescription test, String trace) {
-        mCurrentResult.failed(trace);
+        mCurrentResult.failed(sanitizeXmlContent(trace));
     }
 
     /**
@@ -728,6 +740,7 @@
             fis = new FileInputStream(resultFile);
             logFile = mLogSaver.saveLogData("log-result", LogDataType.XML, fis);
             debug("Result XML URL: %s", logFile.getUrl());
+            logReportFiles(mConfiguration, resultFile, resultFile.getName(), LogDataType.XML);
         } catch (IOException ioe) {
             CLog.e("[%s] error saving XML with log saver", mDeviceSerial);
             CLog.e(ioe);
@@ -741,6 +754,8 @@
                 zipResultStream = new FileInputStream(zippedResults);
                 logFile = mLogSaver.saveLogData("results", LogDataType.ZIP, zipResultStream);
                 debug("Result zip URL: %s", logFile.getUrl());
+                logReportFiles(
+                        mConfiguration, zippedResults, "results", LogDataType.ZIP);
             } finally {
                 StreamUtil.close(zipResultStream);
             }
@@ -1044,4 +1059,26 @@
     public boolean waitForFinalized(long timeout, TimeUnit unit) throws InterruptedException {
         return mFinalized.await(timeout, unit);
     }
+
+    private static String sanitizeXmlContent(String s) {
+        return XmlEscapers.xmlContentEscaper().escape(s);
+    }
+
+    /** Re-log a result file to all reporters so they are aware of it. */
+    private void logReportFiles(
+            IConfiguration configuration, File resultFile, String dataName, LogDataType type) {
+        if (configuration == null) {
+            return;
+        }
+        List<ITestInvocationListener> listeners = configuration.getTestInvocationListeners();
+        try (FileInputStreamSource source = new FileInputStreamSource(resultFile)) {
+            for (ITestInvocationListener listener : listeners) {
+                if (listener.equals(this)) {
+                    // Avoid logging agaisnt itself
+                    continue;
+                }
+                listener.testLog(dataName, type, source);
+            }
+        }
+    }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationResultXml.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationResultXml.java
index 4911d92..2e31722 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationResultXml.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationResultXml.java
@@ -31,12 +31,12 @@
  */
 public class CertificationResultXml extends XmlSuiteResultFormatter {
 
+    public static final String SUITE_PLAN_ATTR = "suite_plan";
     private static final String LOG_URL_ATTR = "log_url";
     private static final String REPORT_VERSION_ATTR = "report_version";
     private static final String REFERENCE_URL_ATTR = "reference_url";
     private static final String RESULT_FILE_VERSION = "5.0";
     private static final String SUITE_NAME_ATTR = "suite_name";
-    private static final String SUITE_PLAN_ATTR = "suite_plan";
     private static final String SUITE_VERSION_ATTR = "suite_version";
     private static final String SUITE_BUILD_ATTR = "suite_build_number";
 
@@ -48,6 +48,11 @@
     private String mLogUrl;
 
     /**
+     * Empty version of the constructor when loading results.
+     */
+    public CertificationResultXml() {}
+
+    /**
      * Create an XML report specialized for the Compatibility Test cases.
      */
     public CertificationResultXml(String suiteName,
@@ -89,12 +94,25 @@
     public void parseSuiteAttributes(XmlPullParser parser, IInvocationContext context)
             throws XmlPullParserException {
         mSuiteName = parser.getAttributeValue(NS, SUITE_NAME_ATTR);
+        context.addInvocationAttribute(SUITE_NAME_ATTR, mSuiteName);
+
         mSuiteVersion = parser.getAttributeValue(NS, SUITE_VERSION_ATTR);
+        context.addInvocationAttribute(SUITE_VERSION_ATTR, mSuiteVersion);
+
         mSuitePlan = parser.getAttributeValue(NS, SUITE_PLAN_ATTR);
+        context.addInvocationAttribute(SUITE_PLAN_ATTR, mSuitePlan);
+
         mSuiteBuild = parser.getAttributeValue(NS, SUITE_BUILD_ATTR);
+        context.addInvocationAttribute(SUITE_BUILD_ATTR, mSuiteBuild);
 
         mReferenceUrl = parser.getAttributeValue(NS, REFERENCE_URL_ATTR);
+        if (mReferenceUrl != null) {
+            context.addInvocationAttribute(REFERENCE_URL_ATTR, mReferenceUrl);
+        }
         mLogUrl = parser.getAttributeValue(NS, LOG_URL_ATTR);
+        if (mLogUrl != null) {
+            context.addInvocationAttribute(LOG_URL_ATTR, mLogUrl);
+        }
     }
 
     /**
@@ -114,6 +132,19 @@
     }
 
     /**
+     * Parse the information in 'Build' tag.
+     */
+    @Override
+    public void parseBuildInfoAttributes(XmlPullParser parser, IInvocationContext context)
+            throws XmlPullParserException {
+        for (int index = 0; index < parser.getAttributeCount(); index++) {
+            String key = parser.getAttributeName(index);
+            String value = parser.getAttributeValue(NS, key);
+            context.addInvocationAttribute(key, value);
+        }
+    }
+
+    /**
      * Returns the compatibility prefix for attributes.
      */
     public String getAttributesPrefix() {
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationSuiteResultReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationSuiteResultReporter.java
index 8399d9d..8c497a3 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationSuiteResultReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationSuiteResultReporter.java
@@ -20,11 +20,15 @@
 import com.android.compatibility.common.util.ResultHandler;
 import com.android.compatibility.common.util.ResultUploader;
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IConfigurationReceiver;
 import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
 import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.FileInputStreamSource;
 import com.android.tradefed.result.ILogSaver;
-import com.android.tradefed.result.ILogSaverListener;
+import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.ITestSummaryListener;
 import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.result.LogDataType;
@@ -65,8 +69,9 @@
  * Extension of {@link XmlFormattedGeneratorReporter} and {@link SuiteResultReporter} to handle
  * Compatibility specific format and operations.
  */
+@OptionClass(alias = "result-reporter")
 public class CertificationSuiteResultReporter extends XmlFormattedGeneratorReporter
-        implements ILogSaverListener, ITestSummaryListener {
+        implements IConfigurationReceiver, ITestSummaryListener {
 
     public static final String LATEST_LINK_NAME = "latest";
     public static final String SUMMARY_FILE = "invocation_summary.txt";
@@ -78,7 +83,10 @@
     @Option(name = "result-server", description = "Server to publish test results.")
     private String mResultServer;
 
-    @Option(name = "disable-result-posting", description = "Disable result posting into report server.")
+    @Option(
+            name = "disable-result-posting",
+            description ="Disable result posting into report server."
+    )
     private boolean mDisableResultPosting = false;
 
     @Option(name = "include-test-log-tags", description = "Include test log tags in report.")
@@ -105,8 +113,10 @@
     private ResultUploader mUploader;
 
     private LogFileSaver mTestLogSaver;
-    /** Log saver to receive when files are logged */
+    /** Invocation level Log saver to receive when files are logged */
     private ILogSaver mLogSaver;
+    /** Invocation level configuration */
+    private IConfiguration mConfiguration = null;
 
     private String mReferenceUrl;
 
@@ -223,12 +233,20 @@
         mLogSaver = saver;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public void setConfiguration(IConfiguration configuration) {
+        mConfiguration = configuration;
+    }
+
     /**
      * Create directory structure where results and logs will be written.
      */
     private void initializeResultDirectories() {
         CLog.d("Initializing result directory");
-
+        // TODO: Clean up start time handling to avoid relying on buildinfo
+        getPrimaryBuildInfo().addBuildAttribute(CompatibilityBuildHelper.START_TIME_MS,
+                Long.toString(getStartTime()));
         try {
             mResultDir = mBuildHelper.getResultDir();
             if (mResultDir != null) {
@@ -251,7 +269,7 @@
         mUploader = new ResultUploader(mResultServer, mBuildHelper.getSuiteName());
         try {
             mLogDir = new File(mBuildHelper.getLogsDir(),
-                    CompatibilityBuildHelper.getDirSuffix(mBuildHelper.getStartTime()));
+                    CompatibilityBuildHelper.getDirSuffix(getStartTime()));
         } catch (FileNotFoundException e) {
             CLog.e(e);
         }
@@ -456,6 +474,7 @@
             fis = new FileInputStream(resultFile);
             logFile = mLogSaver.saveLogData("log-result", LogDataType.XML, fis);
             CLog.d("Result XML URL: %s", logFile.getUrl());
+            logReportFiles(mConfiguration, resultFile, resultFile.getName(), LogDataType.XML);
         } catch (IOException ioe) {
             CLog.e("error saving XML with log saver");
             CLog.e(ioe);
@@ -469,6 +488,7 @@
                 zipResultStream = new FileInputStream(zippedResults);
                 logFile = mLogSaver.saveLogData("results", LogDataType.ZIP, zipResultStream);
                 CLog.d("Result zip URL: %s", logFile.getUrl());
+                logReportFiles(mConfiguration, zippedResults, "results", LogDataType.ZIP);
             } finally {
                 StreamUtil.close(zipResultStream);
             }
@@ -519,7 +539,9 @@
             Transformer transformer = TransformerFactory.newInstance().newTransformer(
                     new StreamSource(xslStream));
             transformer.transform(new StreamSource(inputXml), new StreamResult(outputStream));
-        } catch (IOException | TransformerException ignored) { }
+        } catch (IOException | TransformerException ignored) {
+            CLog.e(ignored);
+        }
         return failureReport;
     }
 
@@ -530,4 +552,22 @@
             String buildFingerprint) {
         CertificationChecksumHelper.tryCreateChecksum(resultDir, results, buildFingerprint);
     }
+
+    /** Re-log a result file to all reporters so they are aware of it. */
+    private void logReportFiles(
+            IConfiguration configuration, File resultFile, String dataName, LogDataType type) {
+        if (configuration == null) {
+            return;
+        }
+        List<ITestInvocationListener> listeners = configuration.getTestInvocationListeners();
+        try (FileInputStreamSource source = new FileInputStreamSource(resultFile)) {
+            for (ITestInvocationListener listener : listeners) {
+                if (listener.equals(this)) {
+                    // Avoid logging agaisnt itself
+                    continue;
+                }
+                listener.testLog(dataName, type, source);
+            }
+        }
+    }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CompatibilityProtoResultReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CompatibilityProtoResultReporter.java
new file mode 100644
index 0000000..823dc2b
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CompatibilityProtoResultReporter.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 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.compatibility.common.tradefed.result.suite;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.proto.ProtoResultReporter;
+import com.android.tradefed.result.proto.TestRecordProto.TestRecord;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Proto reporter that will drop a {@link TestRecord} protobuf in the result directory.
+ */
+public class CompatibilityProtoResultReporter extends ProtoResultReporter {
+
+    public static final String PROTO_FILE_NAME = "test-record.pb";
+
+    private CompatibilityBuildHelper mBuildHelper;
+
+    /** The directory containing the results */
+    private File mResultDir = null;
+
+    @Override
+    public void processStartInvocation(
+            TestRecord invocationStartRecord, IInvocationContext invocationContext) {
+        if (mBuildHelper == null) {
+            mBuildHelper = new CompatibilityBuildHelper(invocationContext.getBuildInfos().get(0));
+        }
+    }
+
+    @Override
+    public void processFinalProto(TestRecord finalRecord) {
+        super.processFinalProto(finalRecord);
+
+        mResultDir = getResultDirectory();
+        File protoFile = new File(mResultDir, PROTO_FILE_NAME);
+        try {
+            finalRecord.writeDelimitedTo(new FileOutputStream(protoFile));
+        } catch (IOException e) {
+            CLog.e(e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    private File getResultDirectory() {
+        try {
+            mResultDir = mBuildHelper.getResultDir();
+            if (mResultDir != null) {
+                mResultDir.mkdirs();
+            }
+        } catch (FileNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+        if (mResultDir == null) {
+            throw new RuntimeException("Result Directory was not created");
+        }
+        if (!mResultDir.exists()) {
+            throw new RuntimeException("Result Directory was not created: " +
+                    mResultDir.getAbsolutePath());
+        }
+        CLog.d("Results Directory: %s", mResultDir.getAbsolutePath());
+        return mResultDir;
+    }
+}
\ No newline at end of file
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/PreviousResultLoader.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/PreviousResultLoader.java
new file mode 100644
index 0000000..7eef1f3
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/PreviousResultLoader.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2018 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.compatibility.common.tradefed.result.suite;
+
+import com.android.annotations.VisibleForTesting;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider;
+import com.android.compatibility.common.tradefed.targetprep.BuildFingerPrintPreparer;
+import com.android.compatibility.common.tradefed.testtype.retry.RetryFactoryTest;
+import com.android.compatibility.common.util.ResultHandler;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.build.BuildRetrievalError;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.IBuildProvider;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.invoker.TestInvocation;
+import com.android.tradefed.invoker.proto.InvocationContext.Context;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.proto.TestRecordProto.TestRecord;
+import com.android.tradefed.result.suite.SuiteResultHolder;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.testtype.suite.retry.ITestSuiteResultLoader;
+import com.android.tradefed.util.proto.TestRecordProtoUtil;
+
+import com.google.api.client.util.Strings;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of {@link ITestSuiteResultLoader} to reload CTS previous results.
+ */
+public final class PreviousResultLoader implements ITestSuiteResultLoader {
+
+    /** Usually associated with ro.build.fingerprint. */
+    public static final String BUILD_FINGERPRINT = "build_fingerprint";
+    /** Usally associated with ro.vendor.build.fingerprint. */
+    public static final String BUILD_VENDOR_FINGERPRINT = "build_vendor_fingerprint";
+    /**
+     * Some suites have a business need to alter the original real device fingerprint value, in this
+     * case we expect an "unaltered" version to be available to still do the original check.
+     */
+    public static final String BUILD_FINGERPRINT_UNALTERED = "build_fingerprint_unaltered";
+
+    @Option(name = RetryFactoryTest.RETRY_OPTION,
+            shortName = 'r',
+            description = "retry a previous session's failed and not executed tests.",
+            mandatory = true)
+    private Integer mRetrySessionId = null;
+
+    @Option(
+        name = "fingerprint-property",
+        description = "The property name to check for the fingerprint."
+    )
+    private String mFingerprintProperty = "ro.build.fingerprint";
+
+    private TestRecord mTestRecord;
+    private IInvocationContext mPreviousContext;
+    private String mExpectedFingerprint;
+    private String mExpectedVendorFingerprint;
+    private String mUnalteredFingerprint;
+
+    private File mResultDir;
+
+    private IBuildProvider mProvider;
+
+    @Override
+    public void init() {
+        IBuildInfo info = null;
+        try {
+            info = getProvider().getBuild();
+        } catch (BuildRetrievalError e) {
+            throw new RuntimeException(e);
+        }
+        CompatibilityBuildHelper helperBuild = new CompatibilityBuildHelper(info);
+        mResultDir = null;
+        try {
+            CLog.logAndDisplay(LogLevel.DEBUG, "Start loading the record protobuf.");
+            mResultDir =
+                    ResultHandler.getResultDirectory(helperBuild.getResultsDir(), mRetrySessionId);
+            mTestRecord =
+                    TestRecordProtoUtil.readFromFile(
+                            new File(mResultDir, CompatibilityProtoResultReporter.PROTO_FILE_NAME));
+            CLog.logAndDisplay(LogLevel.DEBUG, "Done loading the record protobuf.");
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        Context contextProto = null;
+        try {
+            contextProto = mTestRecord.getDescription().unpack(Context.class);
+        } catch (InvalidProtocolBufferException e) {
+            throw new RuntimeException(e);
+        }
+        mPreviousContext = InvocationContext.fromProto(contextProto);
+
+        // Validate the fingerprint
+        // TODO: Use fingerprint argument from TestRecord but we have to deal with suite namespace
+        // for example: cts:build_fingerprint instead of just build_fingerprint.
+        try {
+            CLog.logAndDisplay(LogLevel.DEBUG, "Start parsing previous test_results.xml");
+            CertificationResultXml xmlParser = new CertificationResultXml();
+            SuiteResultHolder holder = xmlParser.parseResults(mResultDir, true);
+            CLog.logAndDisplay(LogLevel.DEBUG, "Done parsing previous test_results.xml");
+            mExpectedFingerprint = holder.context.getAttributes()
+                    .getUniqueMap().get(BUILD_FINGERPRINT);
+            if (mExpectedFingerprint == null) {
+                throw new IllegalArgumentException(
+                        String.format(
+                                "Could not find the %s field in the loaded result.",
+                                BUILD_FINGERPRINT));
+            }
+            /** If available in the report, collect the vendor fingerprint too. */
+            mExpectedVendorFingerprint =
+                    holder.context.getAttributes().getUniqueMap().get(BUILD_VENDOR_FINGERPRINT);
+            if (mExpectedVendorFingerprint == null) {
+                throw new IllegalArgumentException(
+                        String.format(
+                                "Could not find the %s field in the loaded result.",
+                                BUILD_VENDOR_FINGERPRINT));
+            }
+            // Some cases will have an unaltered fingerprint
+            mUnalteredFingerprint =
+                    holder.context.getAttributes().getUniqueMap().get(BUILD_FINGERPRINT_UNALTERED);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getCommandLine() {
+        List<String> command = mPreviousContext.getAttributes().get(
+                TestInvocation.COMMAND_ARGS_KEY);
+        if (command == null) {
+            throw new RuntimeException("Couldn't find the command line arg.");
+        }
+        return command.get(0);
+    }
+
+    @Override
+    public TestRecord loadPreviousRecord() {
+        return mTestRecord;
+    }
+
+    @Override
+    public final void cleanUp() {
+        if (mTestRecord != null) {
+            mTestRecord = null;
+        }
+    }
+
+    @Override
+    public final void customizeConfiguration(IConfiguration config) {
+        // This is specific to Compatibility checking and does not work for multi-device.
+        List<ITargetPreparer> preparers = config.getTargetPreparers();
+        List<ITargetPreparer> newList = new ArrayList<>();
+        // Add the fingerprint checker first to ensure we check it before rerunning the config.
+        BuildFingerPrintPreparer fingerprintChecker = new BuildFingerPrintPreparer();
+        fingerprintChecker.setExpectedFingerprint(mExpectedFingerprint);
+        fingerprintChecker.setExpectedVendorFingerprint(mExpectedVendorFingerprint);
+        fingerprintChecker.setFingerprintProperty(mFingerprintProperty);
+        if (!Strings.isNullOrEmpty(mUnalteredFingerprint)) {
+            fingerprintChecker.setUnalteredFingerprint(mUnalteredFingerprint);
+        }
+        newList.add(fingerprintChecker);
+        newList.addAll(preparers);
+        config.setTargetPreparers(newList);
+
+        // Add the file copier last to copy from previous sesssion
+        List<ITestInvocationListener> listeners = config.getTestInvocationListeners();
+        PreviousSessionFileCopier copier = new PreviousSessionFileCopier();
+        copier.setPreviousSessionDir(mResultDir);
+        listeners.add(copier);
+    }
+
+    @VisibleForTesting
+    protected void setProvider(IBuildProvider provider) {
+        mProvider = provider;
+    }
+
+    private IBuildProvider getProvider() {
+        if (mProvider == null) {
+            mProvider = new CompatibilityBuildProvider();
+        }
+        return mProvider;
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/PreviousSessionFileCopier.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/PreviousSessionFileCopier.java
new file mode 100644
index 0000000..87a667b
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/PreviousSessionFileCopier.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2018 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.compatibility.common.tradefed.result.suite;
+
+import com.android.annotations.VisibleForTesting;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.ChecksumReporter;
+import com.android.compatibility.common.util.ResultHandler;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.invoker.IInvocationContext;
+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.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Recursively copy all the files from a previous session into the current one if they don't exists
+ * already.
+ */
+public class PreviousSessionFileCopier implements ITestInvocationListener {
+
+    private static final List<String> NOT_RETRY_FILES =
+            Arrays.asList(
+                    ChecksumReporter.NAME,
+                    ChecksumReporter.PREV_NAME,
+                    ResultHandler.FAILURE_REPORT_NAME,
+                    "diffs");
+
+    private CompatibilityBuildHelper mBuildHelper;
+    private File mPreviousSessionDir = null;
+
+    /** Sets the previous session directory to copy from. */
+    public void setPreviousSessionDir(File previousSessionDir) {
+        mPreviousSessionDir = previousSessionDir;
+    }
+
+    @Override
+    public void invocationStarted(IInvocationContext context) {
+        if (mBuildHelper == null) {
+            mBuildHelper = createCompatibilityHelper(context.getBuildInfos().get(0));
+        }
+    }
+
+    @Override
+    public void invocationEnded(long elapsedTime) {
+        if (mPreviousSessionDir == null) {
+            CLog.e("Could not copy previous sesson files.");
+            return;
+        }
+        File resultDir = getResultDirectory();
+        copyRetryFiles(mPreviousSessionDir, resultDir);
+    }
+
+    @VisibleForTesting
+    protected CompatibilityBuildHelper createCompatibilityHelper(IBuildInfo info) {
+        return new CompatibilityBuildHelper(info);
+    }
+
+    /**
+     * Recursively copy any other files found in the previous session's result directory to the new
+     * result directory, so long as they don't already exist. For example, a "screenshots" directory
+     * generated in a previous session by a passing test will not be generated on retry unless
+     * copied from the old result directory.
+     *
+     * @param oldDir
+     * @param newDir
+     */
+    private void copyRetryFiles(File oldDir, File newDir) {
+        File[] oldChildren = oldDir.listFiles();
+        for (File oldChild : oldChildren) {
+            if (NOT_RETRY_FILES.contains(oldChild.getName())) {
+                continue; // do not copy this file/directory or its children
+            }
+            File newChild = new File(newDir, oldChild.getName());
+            if (!newChild.exists()) {
+                // If this old file or directory doesn't exist in new dir, simply copy it
+                try {
+                    CLog.d("Copying %s to new session.", oldChild.getName());
+                    if (oldChild.isDirectory()) {
+                        FileUtil.recursiveCopy(oldChild, newChild);
+                    } else {
+                        FileUtil.copyFile(oldChild, newChild);
+                    }
+                } catch (IOException e) {
+                    CLog.w("Failed to copy file \"%s\" from previous session", oldChild.getName());
+                }
+            } else if (oldChild.isDirectory() && newChild.isDirectory()) {
+                // If both children exist as directories, make sure the children of the old child
+                // directory exist in the new child directory.
+                copyRetryFiles(oldChild, newChild);
+            }
+        }
+    }
+
+    private File getResultDirectory() {
+        File resultDir = null;
+        try {
+            resultDir = mBuildHelper.getResultDir();
+            if (resultDir != null) {
+                resultDir.mkdirs();
+            }
+        } catch (FileNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+        if (resultDir == null) {
+            throw new RuntimeException("Result Directory was not created");
+        }
+        if (!resultDir.exists()) {
+            throw new RuntimeException(
+                    "Result Directory was not created: " + resultDir.getAbsolutePath());
+        }
+        CLog.d("Results Directory: %s", resultDir.getAbsolutePath());
+        return resultDir;
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ApkInstrumentationPreparer.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ApkInstrumentationPreparer.java
index 81a5f11..80493f7 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ApkInstrumentationPreparer.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ApkInstrumentationPreparer.java
@@ -17,26 +17,25 @@
 package com.android.compatibility.common.tradefed.targetprep;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
 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.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.CollectingTestListener;
 import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.TestResult;
+import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.targetprep.BuildError;
 import com.android.tradefed.targetprep.ITargetCleaner;
 import com.android.tradefed.targetprep.TargetSetupError;
 import com.android.tradefed.testtype.AndroidJUnitTest;
-import com.android.tradefed.util.proto.TfMetricProtoUtil;
 
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.Map.Entry;
 
 /**
  * Target preparer that instruments an APK.
@@ -60,10 +59,6 @@
     @Option(name = "throw-error", description = "Whether to throw error for device test failure")
     protected boolean mThrowError = true;
 
-    protected ConcurrentHashMap<TestDescription, Map<String, String>> testMetrics =
-            new ConcurrentHashMap<>();
-    private ConcurrentHashMap<TestDescription, String> testFailures = new ConcurrentHashMap<>();
-
     /**
      * {@inheritDoc}
      */
@@ -101,14 +96,13 @@
         try {
             instrument(device, buildInfo);
         } catch (FileNotFoundException e1) {
-            logError("Couldn't find apk to instrument");
-            logError(e1);
+            CLog.e("Couldn't find apk to instrument");
+            CLog.e(e1);
         }
     }
 
     private boolean instrument(ITestDevice device, IBuildInfo buildInfo)
             throws DeviceNotAvailableException, FileNotFoundException {
-        ITestInvocationListener listener = new TargetPreparerListener();
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo);
 
         File apkFile = buildHelper.getTestFile(mApkFileName);
@@ -117,44 +111,37 @@
         }
 
         if (device.getAppPackageInfo(mPackageName) != null) {
-            logInfo("Package %s already present on the device, uninstalling ...", mPackageName);
+            CLog.i("Package %s already present on the device, uninstalling ...", mPackageName);
             device.uninstallPackage(mPackageName);
         }
 
-        logInfo("Instrumenting package: %s", mPackageName);
+        CLog.i("Instrumenting package: %s", mPackageName);
+        CollectingTestListener listener = new CollectingTestListener();
         AndroidJUnitTest instrTest = new AndroidJUnitTest();
         instrTest.setDevice(device);
         instrTest.setInstallFile(apkFile);
         instrTest.setPackageName(mPackageName);
+        instrTest.setRerunMode(false);
+        instrTest.setReRunUsingTestFile(false);
+        // TODO: Make this configurable.
+        instrTest.setIsolatedStorage(false);
         instrTest.run(listener);
-        boolean success = true;
-        if (!testFailures.isEmpty()) {
-            for (TestDescription test : testFailures.keySet()) {
-                success = false;
-                String trace = testFailures.get(test);
+        TestRunResult result = listener.getCurrentRunResults();
+
+        for (Entry<TestDescription, TestResult> results : result.getTestResults().entrySet()) {
+            if (TestStatus.FAILURE.equals(results.getValue().getStatus())) {
                 if (mThrowError) {
-                    logError("Target preparation step %s failed.\n%s", test.getTestName(), trace);
+                    CLog.e(
+                            "Target preparation step %s failed.\n%s",
+                            results.getKey(), results.getValue().getStackTrace());
                 } else {
-                    logWarning("Target preparation step %s failed.\n%s", test.getTestName(),
-                            trace);
+                    CLog.w(
+                            "Target preparation step %s failed.\n%s",
+                            results.getKey(), results.getValue().getStackTrace());
                 }
             }
         }
-        return success;
+        // If any failure return false
+        return !(result.isRunFailure() || result.hasFailedTests());
     }
-
-    private class TargetPreparerListener implements ITestInvocationListener {
-
-        @Override
-        public void testEnded(TestDescription test, HashMap<String, Metric> metrics) {
-            testMetrics.put(test, TfMetricProtoUtil.compatibleConvert(metrics));
-        }
-
-        @Override
-        public void testFailed(TestDescription test, String trace) {
-            testFailures.put(test, trace);
-        }
-
-    }
-
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/BuildFingerPrintPreparer.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/BuildFingerPrintPreparer.java
new file mode 100644
index 0000000..a96b76a
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/BuildFingerPrintPreparer.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2018 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.compatibility.common.tradefed.targetprep;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.targetprep.BaseTargetPreparer;
+import com.android.tradefed.targetprep.BuildError;
+import com.android.tradefed.targetprep.TargetSetupError;
+
+/**
+ * Special preparer used to check the build fingerprint of a device against an expected one.
+ *
+ * <p>An "unaltered" fingerprint might be available. Which reflects that the official fingerprint
+ * has been modified for business reason, but we still want to validate the original device is the
+ * same.
+ */
+public final class BuildFingerPrintPreparer extends BaseTargetPreparer {
+
+    private String mExpectedFingerprint = null;
+    private String mExpectedVendorFingerprint = null;
+    private String mUnalteredFingerprint = null;
+
+    private String mFingerprintProperty = "ro.build.fingerprint";
+    private String mVendorFingerprintProperty = "ro.vendor.build.fingerprint";
+
+    @Override
+    public void setUp(ITestDevice device, IBuildInfo buildInfo)
+            throws TargetSetupError, BuildError, DeviceNotAvailableException {
+        if (mExpectedFingerprint == null) {
+            throw new TargetSetupError("build fingerprint shouldn't be null",
+                    device.getDeviceDescriptor());
+        }
+        try {
+            String compare = mExpectedFingerprint;
+            if (mUnalteredFingerprint != null) {
+                compare = mUnalteredFingerprint;
+            }
+            String currentBuildFingerprint = device.getProperty(mFingerprintProperty);
+            if (!compare.equals(currentBuildFingerprint)) {
+                throw new TargetSetupError(
+                        String.format(
+                                "Device build fingerprint must match %s. Found '%s' instead.",
+                                compare, currentBuildFingerprint),
+                        device.getDeviceDescriptor());
+            }
+            if (mExpectedVendorFingerprint != null) {
+                String currentBuildVendorFingerprint =
+                        device.getProperty(mVendorFingerprintProperty);
+                if (!mExpectedVendorFingerprint.equals(currentBuildVendorFingerprint)) {
+                    throw new TargetSetupError(
+                            String.format(
+                                    "Device vendor build fingerprint must match %s - found %s instead.",
+                                    mExpectedVendorFingerprint, currentBuildVendorFingerprint),
+                            device.getDeviceDescriptor());
+                }
+            }
+        } catch (DeviceNotAvailableException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Sets the expected fingerprint we are checking against.
+     */
+    public void setExpectedFingerprint(String expectedFingerprint) {
+        mExpectedFingerprint = expectedFingerprint;
+    }
+
+    /**
+     * Returns the expected fingerprint.
+     */
+    public String getExpectedFingerprint() {
+        return mExpectedFingerprint;
+    }
+
+    /**
+     * Allow to override the base fingerprint property. In some cases, we want to check the
+     * "ro.vendor.build.fingerpint" for example.
+     */
+    public void setFingerprintProperty(String property) {
+        mFingerprintProperty = property;
+    }
+
+    /** Sets the unchanged original fingerprint. */
+    public void setUnalteredFingerprint(String unalteredFingerprint) {
+        mUnalteredFingerprint = unalteredFingerprint;
+    }
+
+    /** Set the property value associated with ro.vendor.build.fingerprint */
+    public void setExpectedVendorFingerprint(String expectedVendorFingerprint) {
+        mExpectedVendorFingerprint = expectedVendorFingerprint;
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/BusinessLogicPreparer.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/BusinessLogicPreparer.java
index e038884..b33ee08 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/BusinessLogicPreparer.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/BusinessLogicPreparer.java
@@ -22,14 +22,19 @@
 import com.android.compatibility.common.util.FeatureUtil;
 import com.android.compatibility.common.util.PropertyUtil;
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.GlobalConfiguration;
 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.invoker.IInvocationContext;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.targetprep.BuildError;
 import com.android.tradefed.targetprep.ITargetCleaner;
 import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IAbiReceiver;
+import com.android.tradefed.testtype.IInvocationContextReceiver;
 import com.android.tradefed.testtype.suite.TestSuiteInfo;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.MultiMap;
@@ -64,12 +69,16 @@
 /**
  * Pushes business Logic to the host and the test device, for use by test cases in the test suite.
  */
-@OptionClass(alias="business-logic-preparer")
-public class BusinessLogicPreparer implements ITargetCleaner {
+@OptionClass(alias = "business-logic-preparer")
+public class BusinessLogicPreparer implements IAbiReceiver, IInvocationContextReceiver,
+        ITargetCleaner {
 
     /* Placeholder in the service URL for the suite to be configured */
     private static final String SUITE_PLACEHOLDER = "{suite-name}";
 
+    /* String for the key to get file from GlobalConfiguration */
+    private static final String GLOBAL_APE_API_KEY = "ape-api-key";
+
     /* String for creating files to store the business logic configuration on the host */
     private static final String FILE_LOCATION = "business-logic";
     /* String for creating cached business logic configuration files */
@@ -128,8 +137,37 @@
             "connection to the business logic service, in seconds.")
     private int mMaxConnectionTime = DEFAULT_CONNECTION_TIME;
 
+    @Option(name = "config-filename", description = "The module name for module-level " +
+            "configurations, or the suite name for suite-level configurations. Will lookup " +
+            "suite name if not provided.")
+    private String mModuleName = null;
+
+    @Option(name = "version", description = "The module configuration version to retrieve.")
+    private String mModuleVersion = null;
+
     private String mDeviceFilePushed;
     private String mHostFilePushed;
+    private IAbi mAbi = null;
+    private IInvocationContext mModuleContext = null;
+
+    /** {@inheritDoc} */
+    @Override
+    public void setAbi(IAbi abi) {
+        mAbi = abi;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public IAbi getAbi() {
+        return mAbi;
+    }
+
+
+    /** {@inheritDoc} */
+    @Override
+    public void setInvocationContext(IInvocationContext invocationContext) {
+        mModuleContext = invocationContext;
+    }
 
     /**
      * {@inheritDoc}
@@ -137,6 +175,20 @@
     @Override
     public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, BuildError,
             DeviceNotAvailableException {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo);
+        if (buildHelper.hasBusinessLogicHostFile()) {
+            CLog.i("Business logic file already collected, skipping BusinessLogicPreparer.");
+            return;
+        }
+        // Ensure mModuleName is set.
+        if (mModuleName == null) {
+            mModuleName = "";
+            CLog.w("Option config-filename isn't set. Using empty string instead.");
+        }
+        if (mModuleVersion == null) {
+            CLog.w("Option version isn't set. Using 'null' instead.");
+            mModuleVersion = "null";
+        }
         String requestParams = buildRequestParams(device, buildInfo);
         String baseUrl = mUrl.replace(SUITE_PLACEHOLDER, getSuiteName());
         String businessLogicString = null;
@@ -165,10 +217,10 @@
                 return;
             } else {
                 throw new TargetSetupError(String.format("Cannot connect to business logic "
-                        + "service for suite %s.\nIf this problem persists, re-invoking with "
+                        + "service for config %s.\nIf this problem persists, re-invoking with "
                         + "option '--ignore-business-logic-failure' will cause tests to execute "
                         + "anyways (though tests depending on the remote configuration will fail).",
-                        TestSuiteInfo.getInstance().getName()), device.getDeviceDescriptor());
+                        mModuleName), device.getDeviceDescriptor());
             }
         }
 
@@ -180,12 +232,13 @@
             File hostFile = FileUtil.createTempFile(FILE_LOCATION, FILE_EXT);
             FileUtil.writeToFile(businessLogicString, hostFile);
             mHostFilePushed = hostFile.getAbsolutePath();
-            CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo);
-            buildHelper.setBusinessLogicHostFile(hostFile);
+            // Ensure bitness is set.
+            String bitness = (mAbi != null) ? mAbi.getBitness() : "";
+            buildHelper.setBusinessLogicHostFile(hostFile, bitness + mModuleName);
         } catch (IOException e) {
             throw new TargetSetupError(String.format(
-                    "Retrieved business logic for suite %s could not be written to host",
-                    TestSuiteInfo.getInstance().getName()), device.getDeviceDescriptor());
+                    "Retrieved business logic for config %s could not be written to host",
+                    mModuleName), device.getDeviceDescriptor());
         }
         // Push business logic string to device file
         removeDeviceFile(device); // remove any existing business logic file from device
@@ -193,9 +246,8 @@
             mDeviceFilePushed = BusinessLogic.DEVICE_FILE;
         } else {
             throw new TargetSetupError(String.format(
-                    "Retrieved business logic for suite %s could not be written to device %s",
-                    TestSuiteInfo.getInstance().getName(), device.getSerialNumber()),
-                    device.getDeviceDescriptor());
+                    "Retrieved business logic for config %s could not be written to device %s",
+                    mModuleName, device.getSerialNumber()), device.getDeviceDescriptor());
         }
     }
 
@@ -205,7 +257,12 @@
             throws DeviceNotAvailableException {
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo);
         MultiMap<String, String> paramMap = new MultiMap<>();
-        paramMap.put("suite_version", buildHelper.getSuiteVersion());
+        String suiteVersion = buildHelper.getSuiteVersion();
+        if (suiteVersion == null) {
+            suiteVersion = "null";
+        }
+        paramMap.put("suite_version", suiteVersion);
+        paramMap.put("module_version", mModuleVersion);
         paramMap.put("oem", String.valueOf(PropertyUtil.getManufacturer(device)));
         for (String feature : getBusinessLogicFeatures(device, buildInfo)) {
             paramMap.put("features", feature);
@@ -225,9 +282,31 @@
         return paramString;
     }
 
+    /**
+     * Return the the first element of test-suite-tag from configuration if it's not empty,
+     * otherwise, return the name from test-suite-info.properties.
+     */
     @VisibleForTesting
     String getSuiteName() {
-        return TestSuiteInfo.getInstance().getName().toLowerCase();
+        String suiteName = null;
+        if (mModuleContext == null) {
+            suiteName = TestSuiteInfo.getInstance().getName().toLowerCase();
+        } else {
+            List<String> testSuiteTags = mModuleContext.getConfigurationDescriptor().
+                    getSuiteTags();
+            if (!testSuiteTags.isEmpty()) {
+                if (testSuiteTags.size() >= 2) {
+                    CLog.i("More than 2 test-suite-tag are defined. test-suite-tag: " +
+                        testSuiteTags);
+                }
+                suiteName = testSuiteTags.get(0).toLowerCase();
+                CLog.i("Using %s from test suite tags to get value from dynamic config", suiteName);
+            } else {
+                suiteName = TestSuiteInfo.getInstance().getName().toLowerCase();
+                CLog.i("Using %s from TestSuiteInfo to get value from dynamic config", suiteName);
+            }
+        }
+        return suiteName;
     }
 
     /* Get device properties list, with element format "<property_name>:<property_value>" */
@@ -466,20 +545,27 @@
 
     /** Remove business logic file from the device */
     private static void removeDeviceFile(ITestDevice device) throws DeviceNotAvailableException {
-        device.executeShellCommand(String.format("rm -rf %s", BusinessLogic.DEVICE_FILE));
+        device.deleteFile(BusinessLogic.DEVICE_FILE);
     }
 
-    /*
-    * Returns an OAuth2 token string obtained using a service account json key file.
-    *
-    * Uses the service account key file location stored in environment variable 'APE_API_KEY'
-    * to request an OAuth2 token.
-    */
+    /**
+     * Returns an OAuth2 token string obtained using a service account json key file.
+     *
+     * Uses the service account key file location stored in environment variable 'APE_API_KEY'
+     * to request an OAuth2 token. If APE_API_KEY wasn't set, try to get if file is dynamically
+     * downloaded from GlobalConfiguration.
+     */
     private String getToken() {
         String keyFilePath = System.getenv("APE_API_KEY");
         if (Strings.isNullOrEmpty(keyFilePath)) {
-            CLog.d("Environment variable APE_API_KEY not set.");
-            return null;
+            File globalKeyFile = GlobalConfiguration.getInstance().getHostOptions().
+                getServiceAccountJsonKeyFiles().get(GLOBAL_APE_API_KEY);
+            if (globalKeyFile == null || !globalKeyFile.exists()) {
+                CLog.d("Unable to fetch the service key because neither environment variable " +
+                        "APE_API_KEY is set nor the key file is dynamically downloaded.");
+                return null;
+            }
+            keyFilePath = globalKeyFile.getAbsolutePath();
         }
         if (Strings.isNullOrEmpty(mApiScope)) {
             CLog.d("API scope not set, use flag --business-logic-api-scope.");
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/CrashReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/CrashReporter.java
new file mode 100644
index 0000000..51e712b
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/CrashReporter.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2019 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.compatibility.common.tradefed.targetprep;
+
+import com.android.compatibility.common.util.CrashUtils;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.ddmlib.MultiLineReceiver;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.BackgroundDeviceAction;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.BaseTargetPreparer;
+import com.android.tradefed.targetprep.ITargetCleaner;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.regex.Matcher;
+import org.json.JSONArray;
+
+/**
+ * Starts and kills the crash reporter thread. This thread uploads crash results to devices as they
+ * occurring allowing for device side crash analysis.
+ */
+public class CrashReporter extends BaseTargetPreparer implements ITargetCleaner {
+
+    private BackgroundDeviceAction mBackgroundThread;
+
+    /** Uploads the current buffer of Crashes to the phone under the current test name. */
+    private static void upload(ITestDevice device, String testname, JSONArray crashes) {
+        try {
+            if (testname == null) {
+                CLog.logAndDisplay(LogLevel.ERROR, "Attempted upload with no test name");
+                return;
+            }
+            device.executeShellCommand(
+                    String.format("rm -f %s%s", CrashUtils.DEVICE_PATH, CrashUtils.LOCK_FILENAME));
+            Path tmpPath = Files.createTempFile(testname, ".txt");
+            try {
+                Files.setPosixFilePermissions(
+                        tmpPath, PosixFilePermissions.fromString("rw-r--r--"));
+                File reportFile = tmpPath.toFile();
+                try (BufferedWriter writer = new BufferedWriter(new FileWriter(reportFile))) {
+                    writer.write(crashes.toString());
+                }
+                device.pushFile(reportFile, CrashUtils.DEVICE_PATH + testname);
+            } finally {
+                Files.deleteIfExists(tmpPath);
+            }
+            device.executeShellCommand(
+                    String.format("touch %s%s", CrashUtils.DEVICE_PATH, CrashUtils.LOCK_FILENAME));
+        } catch (IOException | RuntimeException | DeviceNotAvailableException e) {
+            CLog.logAndDisplay(LogLevel.ERROR, "Upload to device failed");
+            CLog.logAndDisplay(LogLevel.ERROR, e.getMessage());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setUp(ITestDevice device, IBuildInfo buildInfo) {
+        try {
+            device.executeShellCommand("rm -rf " + CrashUtils.DEVICE_PATH);
+            device.executeShellCommand("mkdir " + CrashUtils.DEVICE_PATH);
+        } catch (DeviceNotAvailableException e) {
+            CLog.logAndDisplay(
+                    LogLevel.ERROR,
+                    "CrashReporterThread failed to setup storage directory on device");
+            CLog.logAndDisplay(LogLevel.ERROR, e.getMessage());
+            return;
+        }
+        mBackgroundThread =
+                new BackgroundDeviceAction(
+                        "logcat",
+                        "CrashReporter logcat thread",
+                        device,
+                        new CrashReporterReceiver(device),
+                        0);
+        mBackgroundThread.start();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) {
+        if (mBackgroundThread != null) {
+            mBackgroundThread.cancel();
+        }
+    }
+
+    /**
+     * Scans through lines received, parses out crashes, and stores them into a buffer. When a new
+     * test started signal is received the buffered is cleared. When an upload signal is received
+     * uploads the current buffer to the phone.
+     */
+    private class CrashReporterReceiver extends MultiLineReceiver {
+
+        private String mTestName;
+        private JSONArray mCrashes;
+        private StringBuilder mLogcatChunk = new StringBuilder();
+        private ITestDevice mDevice;
+
+        public CrashReporterReceiver(ITestDevice device) {
+            mDevice = device;
+        }
+
+        private void processLogLine(String line) {
+            mLogcatChunk.append(line);
+            Matcher m;
+            if ((m = CrashUtils.sNewTestPattern.matcher(line)).matches()) {
+                mTestName = m.group(1);
+                mCrashes = new JSONArray();
+                mLogcatChunk.setLength(0);
+            } else if (CrashUtils.sEndofCrashPattern.matcher(line).matches()) {
+                CrashUtils.addAllCrashes(mLogcatChunk.toString(), mCrashes);
+                mLogcatChunk.setLength(0);
+            } else if (CrashUtils.sUploadRequestPattern.matcher(line).matches()) {
+                upload(mDevice, mTestName, mCrashes);
+            }
+        }
+
+        @Override
+        public void processNewLines(String[] lines) {
+            if (!isCancelled()) {
+                for (String line : lines) {
+                    processLogLine(line);
+                }
+            }
+        }
+
+        @Override
+        public boolean isCancelled() {
+            return mBackgroundThread == null || mBackgroundThread.isCancelled();
+        }
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceFileCollector.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceFileCollector.java
index b91ac27..139e204 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceFileCollector.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceFileCollector.java
@@ -17,27 +17,28 @@
 package com.android.compatibility.common.tradefed.targetprep;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
 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 java.nio.file.Paths;
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.util.Map;
+import java.nio.file.Paths;
 import java.util.HashMap;
+import java.util.Map;
 
-/**
- * An {@link PreconditionPreparer} that collects one device file.
- */
+/** An {@link PreconditionPreparer} that collects one device file. */
+@OptionClass(alias = "device-file-collector")
 public class DeviceFileCollector extends PreconditionPreparer {
 
-    @Option(name = CompatibilityTest.SKIP_DEVICE_INFO_OPTION,
-            shortName = 'd',
-            description = "Whether device info collection should be skipped")
+    @Option(
+        name = DeviceInfoCollector.SKIP_DEVICE_INFO_OPTION,
+        shortName = 'd',
+        description = "Whether device info collection should be skipped"
+    )
     private boolean mSkipDeviceInfo = false;
 
     @Option(name = "src-file", description = "The file path to copy to the results dir")
@@ -101,7 +102,7 @@
                 return;
             }
         } catch (FileNotFoundException fnfe) {
-            fnfe.printStackTrace();
+            CLog.e(fnfe);
         }
     }
 
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
index 4aa9000..fdff02c 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
@@ -20,6 +20,7 @@
 import com.android.compatibility.common.util.DevicePropertyInfo;
 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.ITestLogger;
@@ -35,9 +36,8 @@
 import java.io.IOException;
 import java.util.Map.Entry;
 
-/**
- * An {@link ApkInstrumentationPreparer} that collects device info.
- */
+/** An {@link ApkInstrumentationPreparer} that collects device info. */
+@OptionClass(alias = "device-info-collector")
 public class DeviceInfoCollector extends ApkInstrumentationPreparer implements ITestLoggerReceiver {
 
     public static final String DEVICE_INFO_DIR = "device_info_dir";
@@ -52,6 +52,7 @@
     private static final String BRAND = "ro.product.brand";
     private static final String DEVICE = "ro.product.device";
     private static final String FINGERPRINT = "ro.build.fingerprint";
+    private static final String VENDOR_FINGERPRINT = "ro.vendor.build.fingerprint";
     private static final String ID = "ro.build.id";
     private static final String MANUFACTURER = "ro.product.manufacturer";
     private static final String MODEL = "ro.product.model";
@@ -93,16 +94,41 @@
     @Override
     public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
             BuildError, DeviceNotAvailableException {
-        DevicePropertyInfo devicePropertyInfo = new DevicePropertyInfo(ABI, ABI2, ABIS, ABIS_32,
-                ABIS_64, BOARD, BRAND, DEVICE, FINGERPRINT, ID, MANUFACTURER, MODEL, PRODUCT,
-                REFERENCE_FINGERPRINT, SERIAL, TAGS, TYPE, VERSION_BASE_OS, VERSION_RELEASE,
-                VERSION_SDK, VERSION_SECURITY_PATCH, VERSION_INCREMENTAL);
+        if (buildInfo.getFile(DEVICE_INFO_DIR) != null) {
+            CLog.i("Device info already collected, skipping DeviceInfoCollector.");
+            return;
+        }
+        DevicePropertyInfo devicePropertyInfo =
+                new DevicePropertyInfo(
+                        ABI,
+                        ABI2,
+                        ABIS,
+                        ABIS_32,
+                        ABIS_64,
+                        BOARD,
+                        BRAND,
+                        DEVICE,
+                        FINGERPRINT,
+                        VENDOR_FINGERPRINT,
+                        ID,
+                        MANUFACTURER,
+                        MODEL,
+                        PRODUCT,
+                        REFERENCE_FINGERPRINT,
+                        SERIAL,
+                        TAGS,
+                        TYPE,
+                        VERSION_BASE_OS,
+                        VERSION_RELEASE,
+                        VERSION_SDK,
+                        VERSION_SECURITY_PATCH,
+                        VERSION_INCREMENTAL);
 
         // add device properties to the result with a prefix tag for each key
         for (Entry<String, String> entry :
                 devicePropertyInfo.getPropertytMapWithPrefix(PREFIX_TAG).entrySet()) {
-            buildInfo.addBuildAttribute(
-                    entry.getKey(), nullToEmpty(device.getProperty(entry.getValue())));
+            String property = nullToEmpty(device.getProperty(entry.getValue()));
+            buildInfo.addBuildAttribute(entry.getKey(), property);
         }
         if (mSkipDeviceInfo) {
             return;
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java
index 1df6840..d004fb4 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java
@@ -19,17 +19,19 @@
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.util.DynamicConfig;
 import com.android.compatibility.common.util.DynamicConfigHandler;
-import com.android.ddmlib.Log;
 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;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.targetprep.BaseTargetPreparer;
 import com.android.tradefed.targetprep.BuildError;
 import com.android.tradefed.targetprep.ITargetCleaner;
 import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.testtype.IInvocationContextReceiver;
+import com.android.tradefed.testtype.suite.TestSuiteInfo;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.StreamUtil;
 
@@ -41,18 +43,22 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.util.List;
 
-/**
- * Pushes dynamic config files from config repository
- */
-@OptionClass(alias="dynamic-config-pusher")
-public class DynamicConfigPusher extends BaseTargetPreparer implements ITargetCleaner {
+/** Pushes dynamic config files from config repository */
+@OptionClass(alias = "dynamic-config-pusher")
+public class DynamicConfigPusher extends BaseTargetPreparer
+        implements ITargetCleaner, IInvocationContextReceiver {
     public enum TestTarget {
         DEVICE,
         HOST
     }
 
-    private static final String LOG_TAG = DynamicConfigPusher.class.getSimpleName();
+    /* API Key for compatibility test project, used for dynamic configuration. */
+    private static final String API_KEY = "AIzaSyAbwX5JRlmsLeygY2WWihpIJPXFLueOQ3U";
+
+    @Option(name = "api-key", description = "API key for for dynamic configuration.")
+    private String mApiKey = API_KEY;
 
     @Option(name = "cleanup", description = "Whether to remove config files from the test " +
             "target after test completion.")
@@ -60,11 +66,12 @@
 
     @Option(name = "config-url", description = "The url path of the dynamic config. If set, " +
             "will override the default config location defined in CompatibilityBuildProvider.")
-    private String mConfigUrl;
+    private String mConfigUrl = "https://androidpartner.googleapis.com/v1/dynamicconfig/" +
+            "suites/{suite-name}/modules/{module}/version/{version}?key={api-key}";
 
     @Option(name="config-filename", description = "The module name for module-level " +
-            "configurations, or the suite name for suite-level configurations", mandatory = true)
-    private String mModuleName;
+            "configurations, or the suite name for suite-level configurations")
+    private String mModuleName = null;
 
     @Option(name = "target", description = "The test target, \"device\" or \"host\"",
             mandatory = true)
@@ -86,12 +93,25 @@
                 + "logged under the module name.")
     private String mResourceFileName = null;
 
+    @Option(name = "dynamic-config-name",
+            description = "The dynamic config name for module-level configurations, or the "
+                + "suite name for suite-level configurations.")
+    private String mDynamicConfigName = null;
+
     private String mDeviceFilePushed;
 
+    private IInvocationContext mModuleContext = null;
+
     void setModuleName(String moduleName) {
         mModuleName = moduleName;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public void setInvocationContext(IInvocationContext invocationContext) {
+        mModuleContext = invocationContext;
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -103,27 +123,32 @@
 
         File localConfigFile = getLocalConfigFile(buildHelper, device);
 
+        String suiteName =
+                (mModuleContext != null) ? getSuiteName() : TestSuiteInfo.getInstance().getName();
+        // Ensure mModuleName is set.
+        if (mModuleName == null) {
+            mModuleName = suiteName.toLowerCase();
+            CLog.w("Option config-filename isn't set. Using suite-name '%s'", mModuleName);
+            if (buildHelper.getDynamicConfigFiles().get(mModuleName) != null) {
+                CLog.i("Dynamic config file already collected, skipping DynamicConfigPusher.");
+                return;
+            }
+        }
         if (mVersion == null) {
             mVersion = buildHelper.getSuiteVersion();
         }
 
         String apfeConfigInJson = null;
-        String originUrl = (mConfigUrl != null) ? mConfigUrl : buildHelper.getDynamicConfigUrl();
-
-        if (originUrl != null) {
-            String requestUrl = originUrl;
-            try {
-                requestUrl = originUrl
-                        .replace("{module}", mModuleName).replace("{version}", mVersion);
-                java.net.URL request = new URL(requestUrl);
-                apfeConfigInJson = StreamUtil.getStringFromStream(request.openStream());
-            } catch (IOException e) {
-                LogUtil.printLog(Log.LogLevel.WARN, LOG_TAG,
-                        "Cannot download and parse json config from URL " + requestUrl);
-            }
-        } else {
-            LogUtil.printLog(Log.LogLevel.INFO, LOG_TAG,
-                    "Dynamic config override URL is not set, using local configuration values");
+        String requestUrl = null;
+        try {
+            requestUrl = mConfigUrl.replace("{suite-name}", suiteName)
+                    .replace("{module}", mModuleName)
+                    .replace("{version}", mVersion)
+                    .replace("{api-key}", mApiKey);
+            java.net.URL request = new URL(requestUrl);
+            apfeConfigInJson = StreamUtil.getStringFromStream(request.openStream());
+        } catch (IOException e) {
+            CLog.w(e);
         }
 
         // Use DynamicConfigHandler to merge local and service configuration into one file
@@ -152,10 +177,37 @@
         // Remove any file we have pushed to the device, host file will be moved to the result
         // directory by ResultReporter upon invocation completion.
         if (mDeviceFilePushed != null && !(e instanceof DeviceNotAvailableException) && mCleanup) {
-            device.executeShellCommand("rm -r " + mDeviceFilePushed);
+            device.deleteFile(mDeviceFilePushed);
         }
     }
 
+    /**
+     * Return the the first element of test-suite-tag from configuration if it's not empty,
+     * otherwise, return the name from test-suite-info.properties.
+     */
+    @VisibleForTesting
+    String getSuiteName() {
+        List<String> testSuiteTags = mModuleContext.getConfigurationDescriptor().getSuiteTags();
+        String suiteName = null;
+        if (!testSuiteTags.isEmpty()) {
+            if (testSuiteTags.size() >= 2) {
+                CLog.i("More than 2 test-suite-tag are defined. test-suite-tag: " + testSuiteTags);
+            }
+            suiteName = testSuiteTags.get(0).toUpperCase();
+            CLog.i(
+                    "Replacing {suite-name} placeholder with %s from test suite tags in dynamic "
+                            + "config url.",
+                    suiteName);
+        } else {
+            suiteName = TestSuiteInfo.getInstance().getName();
+            CLog.i(
+                    "Replacing {suite-name} placeholder with %s from TestSuiteInfo in dynamic "
+                            + "config url.",
+                    suiteName);
+        }
+        return suiteName;
+    }
+
     @VisibleForTesting
     final File getLocalConfigFile(CompatibilityBuildHelper buildHelper, ITestDevice device)
             throws TargetSetupError {
@@ -178,7 +230,8 @@
 
         // If not from resources look at local path.
         try {
-            localConfigFile = buildHelper.getTestFile(String.format("%s.dynamic", mModuleName));
+            String lookupName = (mDynamicConfigName != null) ? mDynamicConfigName : mModuleName;
+            localConfigFile = buildHelper.getTestFile(String.format("%s.dynamic", lookupName));
         } catch (FileNotFoundException e) {
             throw new TargetSetupError("Cannot get local dynamic config file from test directory",
                     e, device.getDeviceDescriptor());
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/FilePusher.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/FilePusher.java
index 9970c4f..87beb75 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/FilePusher.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/FilePusher.java
@@ -15,78 +15,28 @@
  */
 package com.android.compatibility.common.tradefed.targetprep;
 
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.ddmlib.Log.LogLevel;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.OptionClass;
-import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.targetprep.PushFilePreparer;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IAbiReceiver;
 
 import java.io.File;
-import java.io.FileNotFoundException;
 
-/**
- * Pushes specified testing artifacts from Compatibility repository.
- */
-@OptionClass(alias="file-pusher")
-public class FilePusher extends PushFilePreparer implements IAbiReceiver {
+/** Pushes specified testing artifacts from Compatibility repository. */
+@OptionClass(alias = "file-pusher")
+public final class FilePusher extends PushFilePreparer {
 
     @Option(name = "append-bitness",
             description = "Append the ABI's bitness to the filename.")
     private boolean mAppendBitness = false;
 
-    private CompatibilityBuildHelper mBuildHelper = null;
-
-    private IAbi mAbi;
-
-    private void setBuildHelper(IBuildInfo buildInfo) {
-        if (mBuildHelper == null) {
-            mBuildHelper = new CompatibilityBuildHelper(buildInfo);
-        }
-    }
-
-    protected File getTestsDir(IBuildInfo buildInfo) throws FileNotFoundException {
-        setBuildHelper(buildInfo);
-        return mBuildHelper.getTestsDir();
-    }
-
-    protected File getTestFile(IBuildInfo buildInfo, String filename) throws FileNotFoundException {
-        setBuildHelper(buildInfo);
-        return mBuildHelper.getTestFile(filename);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void setAbi(IAbi abi) {
-        mAbi = abi;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public IAbi getAbi() {
-        return mAbi;
-    }
-
     /**
      * {@inheritDoc}
      */
     @Override
     public File resolveRelativeFilePath(IBuildInfo buildInfo, String fileName) {
-        try {
-            File f = getTestFile(buildInfo,
-                    String.format("%s%s", fileName, mAppendBitness ? mAbi.getBitness() : ""));
-            CLog.logAndDisplay(LogLevel.INFO, "Copying from %s", f.getAbsolutePath());
-            return f;
-        } catch (FileNotFoundException e) {
-            e.printStackTrace();
-        }
-        return null;
+        return super.resolveRelativeFilePath(
+                buildInfo,
+                String.format("%s%s", fileName, mAppendBitness ? getAbi().getBitness() : ""));
     }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/LocationCheck.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/LocationCheck.java
index 548dd6f..25351a1 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/LocationCheck.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/LocationCheck.java
@@ -19,6 +19,7 @@
 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.BuildError;
 import com.android.tradefed.targetprep.TargetSetupError;
 
@@ -50,7 +51,7 @@
             return; // skip this precondition if required location feature is not present
         }
 
-        logInfo("Verifying location setting");
+        CLog.i("Verifying location setting");
         mSettingName = LOCATION_SETTING;
         mSettingType = SettingsPreparer.SettingType.SECURE;
         mExpectedSettingValues.add(NETWORK);
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/MediaPreparer.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/MediaPreparer.java
index 67dd528..427a4bc 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/MediaPreparer.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/MediaPreparer.java
@@ -15,6 +15,7 @@
  */
 package com.android.compatibility.common.tradefed.targetprep;
 
+import com.android.annotations.VisibleForTesting;
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.tradefed.util.DynamicConfigFileReader;
 import com.android.ddmlib.IDevice;
@@ -25,9 +26,11 @@
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.targetprep.BaseTargetPreparer;
 import com.android.tradefed.targetprep.BuildError;
 import com.android.tradefed.targetprep.TargetSetupError;
 import com.android.tradefed.testtype.AndroidJUnitTest;
@@ -47,33 +50,43 @@
 import java.util.regex.Pattern;
 import java.util.zip.ZipFile;
 
-/**
- * Ensures that the appropriate media files exist on the device
- */
-@OptionClass(alias="media-preparer")
-public class MediaPreparer extends PreconditionPreparer {
+/** Ensures that the appropriate media files exist on the device */
+@OptionClass(alias = "media-preparer")
+public class MediaPreparer extends BaseTargetPreparer {
 
-    @Option(name = "local-media-path",
-            description = "Absolute path of the media files directory, containing" +
-            "'bbb_short' and 'bbb_full' directories")
-    protected String mLocalMediaPath = null;
+    @Option(
+        name = "local-media-path",
+        description =
+                "Absolute path of the media files directory, containing"
+                        + "'bbb_short' and 'bbb_full' directories"
+    )
+    private String mLocalMediaPath = null;
 
-    @Option(name = "skip-media-download",
-            description = "Whether to skip the media files precondition")
-    protected boolean mSkipMediaDownload = false;
+    @Option(
+        name = "skip-media-download",
+        description = "Whether to skip the media files precondition"
+    )
+    private boolean mSkipMediaDownload = false;
 
-    @Option(name = "media-download-only",
-            description = "Only download media files; do not run instrumentation or copy files")
-    protected boolean mMediaDownloadOnly = false;
+    /** @deprecated do not use it. */
+    @Deprecated
+    @Option(
+        name = "media-download-only",
+        description =
+                "Deprecated: Only download media files; do not run instrumentation or copy files"
+    )
+    private boolean mMediaDownloadOnly = false;
 
-    @Option(name = "images-only",
-            description = "Only push images files to the device")
-    protected boolean mImagesOnly = false;
+    @Option(name = "images-only", description = "Only push images files to the device")
+    private boolean mImagesOnly = false;
 
-    @Option(name = "push-all",
-            description = "Push everything downloaded to the device,"
-            + " use 'media-folder-name' to specify the destination dir name.")
-    protected boolean mPushAll = false;
+    @Option(
+        name = "push-all",
+        description =
+                "Push everything downloaded to the device,"
+                        + " use 'media-folder-name' to specify the destination dir name."
+    )
+    private boolean mPushAll = false;
 
     @Option(name = "dynamic-config-module",
             description = "For a target preparer, the 'module' of the configuration" +
@@ -174,6 +187,11 @@
         public String toString() {
             return String.format("%dx%d", width, height);
         }
+
+        /** Returns the width of the resolution. */
+        public int getWidth() {
+            return width;
+        }
     }
 
     public static File getDefaultMediaDir() {
@@ -189,6 +207,7 @@
      *
      * This method is exposed for unit testing.
      */
+    @VisibleForTesting
     protected boolean mediaFilesExistOnDevice(ITestDevice device)
             throws DeviceNotAvailableException {
         if (mPushAll) {
@@ -236,45 +255,56 @@
      * this file to the same location on the host. Only an issue in Android O and above,
      * where MediaPreparer is used for multiple, shardable modules.
      */
-    private synchronized File downloadMediaToHost(ITestDevice device, IBuildInfo buildInfo)
+    private File downloadMediaToHost(ITestDevice device, IBuildInfo buildInfo)
             throws TargetSetupError {
-        // Retrieve default directory for storing media files
-        File mediaFolder = getMediaDir();
-        if (mediaFolder.exists() && mediaFolder.list().length > 0) {
-            // Folder has already been created and populated by previous MediaPreparer runs,
-            // assume all necessary media files exist inside.
+        // Make sure the synchronization is on the class and not the object
+        synchronized (MediaPreparer.class) {
+            // Retrieve default directory for storing media files
+            File mediaFolder = getMediaDir();
+            if (mediaFolder.exists() && mediaFolder.list().length > 0) {
+                // Folder has already been created and populated by previous MediaPreparer runs,
+                // assume all necessary media files exist inside.
+                return mediaFolder;
+            }
+            mediaFolder.mkdirs();
+            URL url;
+            try {
+                // Get download URL from dynamic configuration service
+                String mediaUrlString =
+                        DynamicConfigFileReader.getValueFromConfig(
+                                buildInfo, mDynamicConfigModule, MEDIA_FILES_URL_KEY);
+                url = new URL(mediaUrlString);
+            } catch (IOException | XmlPullParserException e) {
+                throw new TargetSetupError(
+                        "Trouble finding media file download location with "
+                                + "dynamic configuration",
+                        e,
+                        device.getDeviceDescriptor());
+            }
+            File mediaFolderZip = new File(mediaFolder.getAbsolutePath() + ".zip");
+            try {
+                LogUtil.printLog(
+                        Log.LogLevel.INFO,
+                        LOG_TAG,
+                        String.format("Downloading media files from %s", url.toString()));
+                URLConnection conn = url.openConnection();
+                InputStream in = conn.getInputStream();
+                mediaFolderZip.createNewFile();
+                FileUtil.writeToFile(in, mediaFolderZip);
+                LogUtil.printLog(Log.LogLevel.INFO, LOG_TAG, "Unzipping media files");
+                ZipUtil.extractZip(new ZipFile(mediaFolderZip), mediaFolder);
+            } catch (IOException e) {
+                FileUtil.recursiveDelete(mediaFolder);
+                throw new TargetSetupError(
+                        "Failed to download and open media files on host, the"
+                                + " device requires these media files for compatibility tests",
+                        e,
+                        device.getDeviceDescriptor());
+            } finally {
+                FileUtil.deleteFile(mediaFolderZip);
+            }
             return mediaFolder;
         }
-        mediaFolder.mkdirs();
-        URL url;
-        try {
-            // Get download URL from dynamic configuration service
-            String mediaUrlString = DynamicConfigFileReader.getValueFromConfig(
-                    buildInfo, mDynamicConfigModule, MEDIA_FILES_URL_KEY);
-            url = new URL(mediaUrlString);
-        } catch (IOException | XmlPullParserException e) {
-            throw new TargetSetupError("Trouble finding media file download location with " +
-                    "dynamic configuration", e, device.getDeviceDescriptor());
-        }
-        File mediaFolderZip = new File(mediaFolder.getAbsolutePath() + ".zip");
-        try {
-            LogUtil.printLog(Log.LogLevel.INFO, LOG_TAG,
-                    String.format("Downloading media files from %s", url.toString()));
-            URLConnection conn = url.openConnection();
-            InputStream in = conn.getInputStream();
-            mediaFolderZip.createNewFile();
-            FileUtil.writeToFile(in, mediaFolderZip);
-            LogUtil.printLog(Log.LogLevel.INFO, LOG_TAG, "Unzipping media files");
-            ZipUtil.extractZip(new ZipFile(mediaFolderZip), mediaFolder);
-        } catch (IOException e) {
-            FileUtil.recursiveDelete(mediaFolder);
-            throw new TargetSetupError("Failed to download and open media files on host, the"
-                    + " device requires these media files for compatibility tests", e,
-                    device.getDeviceDescriptor());
-        } finally {
-            FileUtil.deleteFile(mediaFolderZip);
-        }
-        return mediaFolder;
     }
 
     /*
@@ -301,14 +331,14 @@
     protected void copyVideoFiles(ITestDevice device) throws DeviceNotAvailableException {
         for (Resolution resolution : RESOLUTIONS) {
             if (resolution.width > mMaxRes.width) {
-                logInfo("Media file copying complete");
+                CLog.i("Media file copying complete");
                 return;
             }
             String deviceShortFilePath = mBaseDeviceShortDir + resolution.toString();
             String deviceFullFilePath = mBaseDeviceFullDir + resolution.toString();
             if (!device.doesFileExist(deviceShortFilePath) ||
                     !device.doesFileExist(deviceFullFilePath)) {
-                logInfo("Copying files of resolution %s to device", resolution.toString());
+                CLog.i("Copying files of resolution %s to device", resolution.toString());
                 String localShortDirName = "bbb_short/" + resolution.toString();
                 String localFullDirName = "bbb_full/" + resolution.toString();
                 File localShortDir = new File(mLocalMediaPath, localShortDirName);
@@ -328,7 +358,7 @@
     // copy image files to the device
     protected void copyImagesFiles(ITestDevice device) throws DeviceNotAvailableException {
         if (!device.doesFileExist(mBaseDeviceImagesDir)) {
-            logInfo("Copying images files to device");
+            CLog.i("Copying images files to device");
             device.pushDir(new File(mLocalMediaPath, "images"), mBaseDeviceImagesDir);
         }
     }
@@ -336,7 +366,7 @@
     // copy everything from the host directory to the device
     protected void copyAll(ITestDevice device) throws DeviceNotAvailableException {
         if (!device.doesFileExist(mBaseDeviceModuleDir)) {
-            logInfo("Copying files to device");
+            CLog.i("Copying files to device");
             device.pushDir(new File(mLocalMediaPath), mBaseDeviceModuleDir);
         }
     }
@@ -351,27 +381,28 @@
     }
 
     @Override
-    public void run(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
-            BuildError, DeviceNotAvailableException {
+    public void setUp(ITestDevice device, IBuildInfo buildInfo)
+            throws TargetSetupError, BuildError, DeviceNotAvailableException {
         if (mImagesOnly && mPushAll) {
             throw new TargetSetupError(
-                    "'images-only' and 'push-all' cannot be set to true together.");
+                    "'images-only' and 'push-all' cannot be set to true together.",
+                    device.getDeviceDescriptor());
         }
         if (mSkipMediaDownload) {
-            logInfo("Skipping media preparation");
+            CLog.i("Skipping media preparation");
             return; // skip this precondition
         }
-        if (!mMediaDownloadOnly) {
-            setMountPoint(device);
-            if (!mImagesOnly && !mPushAll) {
-                setMaxRes(device, buildInfo); // max resolution only applies to video files
-            }
-            if (mediaFilesExistOnDevice(device)) {
-                // if files already on device, do nothing
-                logInfo("Media files found on the device");
-                return;
-            }
+
+        setMountPoint(device);
+        if (!mImagesOnly && !mPushAll) {
+            setMaxRes(device, buildInfo); // max resolution only applies to video files
         }
+        if (mediaFilesExistOnDevice(device)) {
+            // if files already on device, do nothing
+            CLog.i("Media files found on the device");
+            return;
+        }
+
         if (mLocalMediaPath == null) {
             // Option 'local-media-path' has not been defined
             // Get directory to store media files on this host
@@ -379,10 +410,8 @@
             // set mLocalMediaPath to extraction location of media files
             updateLocalMediaPath(device, mediaFolder);
         }
-        logInfo("Media files located on host at: %s", mLocalMediaPath);
-        if (!mMediaDownloadOnly) {
-            copyMediaFiles(device);
-        }
+        CLog.i("Media files located on host at: %s", mLocalMediaPath);
+        copyMediaFiles(device);
     }
 
     // Initialize maximum resolution of media files to copy
@@ -399,14 +428,15 @@
             }
         } catch (FileNotFoundException e) {
             mMaxRes = DEFAULT_MAX_RESOLUTION;
-            logWarning("Cound not find %s to determine maximum resolution, copying up to %s",
+            CLog.w(
+                    "Cound not find %s to determine maximum resolution, copying up to %s",
                     APP_APK, DEFAULT_MAX_RESOLUTION.toString());
             return;
         }
         if (device.getAppPackageInfo(APP_PKG_NAME) != null) {
             device.uninstallPackage(APP_PKG_NAME);
         }
-        logInfo("Instrumenting package %s:", APP_PKG_NAME);
+        CLog.i("Instrumenting package %s:", APP_PKG_NAME);
         AndroidJUnitTest instrTest = new AndroidJUnitTest();
         instrTest.setDevice(device);
         instrTest.setInstallFile(apkFile);
@@ -414,11 +444,12 @@
         instrTest.run(listener);
         if (mFailureStackTrace != null) {
             mMaxRes = DEFAULT_MAX_RESOLUTION;
-            logWarning("Retrieving maximum resolution failed with trace:\n%s", mFailureStackTrace);
-            logWarning("Copying up to %s", DEFAULT_MAX_RESOLUTION.toString());
+            CLog.w("Retrieving maximum resolution failed with trace:\n%s", mFailureStackTrace);
+            CLog.w("Copying up to %s", DEFAULT_MAX_RESOLUTION.toString());
         } else if (mMaxRes == null) {
             mMaxRes = DEFAULT_MAX_RESOLUTION;
-            logWarning("Failed to pull resolution capabilities from device, copying up to %s",
+            CLog.w(
+                    "Failed to pull resolution capabilities from device, copying up to %s",
                     DEFAULT_MAX_RESOLUTION.toString());
         }
     }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/PackageDisabler.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/PackageDisabler.java
index da7ce22..40fbf30 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/PackageDisabler.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/PackageDisabler.java
@@ -21,6 +21,7 @@
 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.BuildError;
 import com.android.tradefed.targetprep.TargetSetupError;
 
@@ -38,7 +39,7 @@
             throws TargetSetupError, BuildError, DeviceNotAvailableException {
 
         if (device.getAppPackageInfo(mPackageName) != null) {
-            logInfo("Package %s installed, disabling ...", mPackageName);
+            CLog.i("Package %s installed, disabling ...", mPackageName);
             device.executeShellCommand("pm disable-user " + mPackageName);
         }
     }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/PreconditionPreparer.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/PreconditionPreparer.java
index d47b56c..26c119a 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/PreconditionPreparer.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/PreconditionPreparer.java
@@ -16,7 +16,6 @@
 
 package com.android.compatibility.common.tradefed.targetprep;
 
-import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
 import com.android.ddmlib.Log;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.ConfigurationException;
@@ -26,6 +25,7 @@
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.BaseTargetPreparer;
 import com.android.tradefed.targetprep.BuildError;
 import com.android.tradefed.targetprep.ITargetPreparer;
 import com.android.tradefed.targetprep.TargetSetupError;
@@ -34,19 +34,27 @@
 import java.util.List;
 
 /**
- * An {@link ITargetPreparer} that performs checks and/or tasks to ensure the
- * the device is ready to run the test suite.
+ * An {@link ITargetPreparer} that performs checks and/or tasks to ensure the the device is ready to
+ * run the test suite.
  */
-public abstract class PreconditionPreparer implements ITargetPreparer {
+public abstract class PreconditionPreparer extends BaseTargetPreparer {
 
-    @Option(name = CompatibilityTest.SKIP_PRECONDITIONS_OPTION,
-            shortName = 'o',
-            description = "Whether preconditions should be skipped")
+    public static final String SKIP_PRECONDITIONS_OPTION = "skip-preconditions";
+    public static final String PRECONDITION_ARG_OPTION = "precondition-arg";
+
+    @Option(
+        name = SKIP_PRECONDITIONS_OPTION,
+        shortName = 'o',
+        description = "Whether preconditions should be skipped"
+    )
     private boolean mSkipPreconditions = false;
 
-    @Option(name = CompatibilityTest.PRECONDITION_ARG_OPTION,
-            description = "the arguments to pass to a precondition. The expected format is"
-                    + "\"<arg-name>:<arg-value>\"")
+    @Option(
+        name = PRECONDITION_ARG_OPTION,
+        description =
+                "the arguments to pass to a precondition. The expected format is"
+                        + "\"<arg-name>:<arg-value>\""
+    )
     private List<String> mPreconditionArgs = new ArrayList<>();
 
     protected final String mLogTag = getClass().getSimpleName();
@@ -54,16 +62,17 @@
     @Override
     public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
             BuildError, DeviceNotAvailableException {
-        if (!mSkipPreconditions) {
-            for (String preconditionArg : mPreconditionArgs) {
-                String[] parts = preconditionArg.split(":");
-                String argName = parts[0];
-                // If arg-value is not supplied, set to "true"
-                String argValue = (parts.length > 1) ? parts[1] : Boolean.toString(true);
-                setOption(argName, argValue);
-            }
-            run(device, buildInfo);
+        if (mSkipPreconditions) {
+            return;
         }
+        for (String preconditionArg : mPreconditionArgs) {
+            String[] parts = preconditionArg.split(":");
+            String argName = parts[0];
+            // If arg-value is not supplied, set to "true"
+            String argValue = (parts.length > 1) ? parts[1] : Boolean.toString(true);
+            setOption(argName, argValue);
+        }
+        run(device, buildInfo);
     }
 
     private void setOption(String option, String value) {
@@ -76,37 +85,54 @@
         }
     }
 
+    /**
+     * All PreconditionPreparer implementations share a base setup and can implement their own
+     * specific run logic.
+     */
     public abstract void run(ITestDevice device, IBuildInfo buildInfo)
             throws TargetSetupError, BuildError, DeviceNotAvailableException;
 
+    /** @deprecated Use {@link CLog} instead. */
+    @Deprecated
     protected void logInfo(String info) {
         LogUtil.printLog(Log.LogLevel.INFO, mLogTag, info);
     }
 
+    /** @deprecated Use {@link CLog} instead. */
+    @Deprecated
     protected void logInfo(String infoFormat, Object... args) {
         LogUtil.printLog(Log.LogLevel.INFO, mLogTag, String.format(infoFormat, args));
     }
 
+    /** @deprecated Use {@link CLog} instead. */
+    @Deprecated
     protected void logWarning(String warning) {
         LogUtil.printLog(Log.LogLevel.WARN, mLogTag, warning);
     }
 
+    /** @deprecated Use {@link CLog} instead. */
+    @Deprecated
     protected void logWarning(String warningFormat, Object... args) {
         LogUtil.printLog(Log.LogLevel.WARN, mLogTag, String.format(warningFormat, args));
     }
 
+    /** @deprecated Use {@link CLog} instead. */
+    @Deprecated
     protected void logError(String error) {
         LogUtil.printLog(Log.LogLevel.ERROR, mLogTag, error);
     }
 
+    /** @deprecated Use {@link CLog} instead. */
+    @Deprecated
     protected void logError(String errorFormat, Object... args) {
         LogUtil.printLog(Log.LogLevel.ERROR, mLogTag, String.format(errorFormat, args));
     }
 
+    /** @deprecated Use {@link CLog} instead. */
+    @Deprecated
     protected void logError(Throwable t) {
         if (t != null) {
             Log.e(mLogTag, t);
         }
     }
-
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/PropertyCheck.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/PropertyCheck.java
index 192c095..7030a29 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/PropertyCheck.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/PropertyCheck.java
@@ -20,6 +20,7 @@
 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.BuildError;
 import com.android.tradefed.targetprep.TargetSetupError;
 
@@ -29,17 +30,25 @@
 @OptionClass(alias="property-check")
 public class PropertyCheck extends PreconditionPreparer {
 
-    @Option(name = "property-name", description = "The name of the property to check",
-            mandatory = true)
-    protected String mPropertyName = null;
+    @Option(
+        name = "property-name",
+        description = "The name of the property to check",
+        mandatory = true
+    )
+    private String mPropertyName = null;
 
-    @Option(name = "expected-value", description = "The expected value of the property",
-            mandatory = true)
-    protected String mExpectedPropertyValue = null;
+    @Option(
+        name = "expected-value",
+        description = "The expected value of the property",
+        mandatory = true
+    )
+    private String mExpectedPropertyValue = null;
 
-    @Option(name = "throw-error",
-            description = "Whether to throw an error for an unexpected property value")
-    protected boolean mThrowError = false;
+    @Option(
+        name = "throw-error",
+        description = "Whether to throw an error for an unexpected property value"
+    )
+    private boolean mThrowError = false;
 
     @Override
     public void run(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
@@ -47,7 +56,8 @@
 
         String propertyValue = device.getProperty(mPropertyName);
         if (propertyValue == null) {
-            logWarning("Property \"%s\" not found on device, cannot verify value \"%s\" ",
+            CLog.w(
+                    "Property \"%s\" not found on device, cannot verify value \"%s\" ",
                     mPropertyName, mExpectedPropertyValue);
             return;
         }
@@ -59,7 +69,7 @@
             if(mThrowError) {
                 throw new TargetSetupError(msg, device.getDeviceDescriptor());
             } else {
-                logWarning(msg);
+                CLog.w(msg);
             }
         }
     }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ResultFilePuller.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ResultFilePuller.java
deleted file mode 100644
index 432f223..0000000
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ResultFilePuller.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2015 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.compatibility.common.tradefed.targetprep;
-
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-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.BuildError;
-import com.android.tradefed.targetprep.ITargetCleaner;
-import com.android.tradefed.targetprep.TargetSetupError;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Pulls files from the device after a test run and puts them into the result folder.
- */
-@OptionClass(alias="result-file-puller")
-public class ResultFilePuller implements ITargetCleaner {
-
-    @Option(name="clear", description = "Whether to clear the src files and dirs before running the test")
-    private boolean mClearSrc = true;
-
-    @Option(name="src-file", description = "The file to copy to the results dir")
-    private List<String> mSrcFiles = new ArrayList<>();
-
-    @Option(name="src-dir", description = "The directory to copy to the results dir")
-    private List<String> mSrcDirs = new ArrayList<>();
-
-    @Option(name = "dest-dir", description = "The directory under the result to store the files")
-    private String mDestDir;
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, BuildError,
-            DeviceNotAvailableException {
-        if (mClearSrc) {
-            for (String file : mSrcFiles) {
-                delete(device, file);
-            }
-            for (String dir : mSrcDirs) {
-                delete(device, dir);
-            }
-        }
-    }
-
-    private void delete(ITestDevice device, String file) throws DeviceNotAvailableException {
-        device.executeShellCommand(String.format("rm -rf %s", file));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
-            throws DeviceNotAvailableException {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo);
-        try {
-            File resultDir = buildHelper.getResultDir();
-            if (mDestDir != null) {
-                resultDir = new File(resultDir, mDestDir);
-            }
-            resultDir.mkdirs();
-            if (!resultDir.isDirectory()) {
-                CLog.e("%s is not a directory", resultDir.getAbsolutePath());
-                return;
-            }
-            String resultPath = resultDir.getAbsolutePath();
-            for (String file : mSrcFiles) {
-                pull(device, file, resultPath);
-            }
-            for (String dir : mSrcDirs) {
-                pull(device, dir, resultPath);
-            }
-        } catch (FileNotFoundException fnfe) {
-            fnfe.printStackTrace();
-        }
-    }
-
-    private void pull(ITestDevice device, String src, String dest) {
-        String command = String.format("adb -s %s pull %s %s", device.getSerialNumber(), src, dest);
-        try {
-            Process p = Runtime.getRuntime().exec(new String[] {"/bin/bash", "-c", command});
-            if (p.waitFor() != 0) {
-                CLog.e("Failed to run %s", command);
-            }
-        } catch (Exception e) {
-            CLog.e("Caught exception during pull.");
-            CLog.e(e);
-        }
-    }
-}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/WifiCheck.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/WifiCheck.java
index 7bcf880..b065475 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/WifiCheck.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/WifiCheck.java
@@ -20,19 +20,20 @@
 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.BuildError;
 import com.android.tradefed.targetprep.TargetSetupError;
 
 /**
- * This preparer ensures that the device is connected to a network.
- * The options "wifi-ssid" and "wifi-psk" allow users of the preparer to attempt connection
- * to a network. If the options are provided, the preparer disconnects any existing network
- * connection, and attempts to connect with the options provided.
+ * This preparer ensures that the device is connected to a network. The options "wifi-ssid" and
+ * "wifi-psk" allow users of the preparer to attempt connection to a network. If the options are
+ * provided, the preparer disconnects any existing network connection, and attempts to connect with
+ * the options provided.
  *
- * @throws TargetSetupError if device is not connected to a network and no options are given, or
- * if the device fails to connect to the network specified in the options
+ * @throw TargetSetupError if device is not connected to a network and no options are given, or if
+ *     the device fails to connect to the network specified in the options
  */
-@OptionClass(alias="wifi-check")
+@OptionClass(alias = "wifi-check")
 public class WifiCheck extends PreconditionPreparer {
 
     private static final String WIFI_FEATURE = "android.hardware.wifi";
@@ -62,18 +63,21 @@
 
         if (mWifiSsid == null) { // no connection to create, check for existing connectivity
             if (!device.checkConnectivity()) {
-                logError("Device has no network connection, no ssid provided, some modules " +
-                        "of CTS require an active network connection");
+                CLog.e(
+                        "Device has no network connection, no ssid provided, some modules "
+                                + "of CTS require an active network connection");
                 return;
             }
         } else { // network provided in options, attempt to create new connection
-            logInfo("Attempting connection to \"%s\"", mWifiSsid);
+            CLog.i("Attempting connection to \"%s\"", mWifiSsid);
             if (!device.connectToWifiNetwork(mWifiSsid, mWifiPsk)) { // attempt connection
-                logError("Unable to connect to network \"%s\", some modules of CTS" +
-                        "require an active network connection", mWifiSsid);
+                CLog.e(
+                        "Unable to connect to network \"%s\", some modules of CTS"
+                                + "require an active network connection",
+                        mWifiSsid);
                 return;
             }
         }
-        logInfo("Wifi is connected");
+        CLog.i("Wifi is connected");
     }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/BusinessLogicHostTestBase.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/BusinessLogicHostTestBase.java
index 46ca0e5..f24a021 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/BusinessLogicHostTestBase.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/BusinessLogicHostTestBase.java
@@ -19,10 +19,6 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.rules.TestName;
-
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.util.BusinessLogic;
 import com.android.compatibility.common.util.BusinessLogicExecutor;
@@ -30,7 +26,10 @@
 import com.android.compatibility.common.util.BusinessLogicHostExecutor;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-import com.android.tradefed.testtype.suite.TestSuiteInfo;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestName;
 
 import java.io.File;
 
@@ -73,8 +72,19 @@
     }
 
     protected void loadBusinessLogic() {
+        File businessLogicFile = null;
         CompatibilityBuildHelper helper = new CompatibilityBuildHelper(getBuild());
-        File businessLogicFile = helper.getBusinessLogicHostFile();
+        // Check if business logic file has been already collected by suite level business logic
+        // preparer.
+        if (helper.hasBusinessLogicHostFile()) {
+            businessLogicFile = helper.getBusinessLogicHostFile();
+        }
+        else {
+            String bitness = (getAbi() != null) ? getAbi().getBitness() : "";
+            String moduleName = getInvocationContext().getConfigurationDescriptor().
+                getModuleName();
+            businessLogicFile = helper.getBusinessLogicHostFile(bitness + moduleName);
+        }
         if (businessLogicFile != null && businessLogicFile.canRead()) {
             mBusinessLogic = BusinessLogicFactory.createFromFile(businessLogicFile);
         } else {
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
index 60208b1..d99a5ae 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -55,7 +55,6 @@
 import com.android.tradefed.testtype.IInvocationContextReceiver;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.IShardableTest;
-import com.android.tradefed.testtype.IStrictShardableTest;
 import com.android.tradefed.testtype.ITestCollector;
 import com.android.tradefed.testtype.suite.TestSuiteInfo;
 import com.android.tradefed.util.AbiFormatter;
@@ -88,7 +87,7 @@
 @Deprecated
 @OptionClass(alias = "compatibility")
 public class CompatibilityTest implements IDeviceTest, IShardableTest, IBuildReceiver,
-        IStrictShardableTest, ISystemStatusCheckerReceiver, ITestCollector,
+        ISystemStatusCheckerReceiver, ITestCollector,
         IInvocationContextReceiver {
 
     public static final String INCLUDE_FILTER_OPTION = "include-filter";
@@ -477,6 +476,10 @@
                 moduleContext.addInvocationAttribute(IModuleDef.MODULE_NAME, module.getName());
                 moduleContext.addInvocationAttribute(IModuleDef.MODULE_ABI,
                         module.getAbi().getName());
+                // This format is not always true but for the deprecated runner this is best effort.
+                moduleContext.addInvocationAttribute(
+                        IModuleDef.MODULE_ID,
+                        String.format("%s %s", module.getAbi().getName(), module.getName()));
                 mInvocationContext.setModuleInvocationContext(moduleContext);
                 // Populate the module context with devices and builds
                 for (String deviceName : mInvocationContext.getDeviceConfigNames()) {
@@ -620,7 +623,7 @@
      * Exposed for testing.
      */
     protected Set<String> getAbisForBuildTargetArch() {
-        return AbiUtils.getAbisForArch(TestSuiteInfo.getInstance().getTargetArch());
+        return AbiUtils.getAbisForArch(TestSuiteInfo.getInstance().getTargetArchs().get(0));
     }
 
     /**
@@ -836,11 +839,7 @@
         return shardQueue;
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public IRemoteTest getTestShard(int shardCount, int shardIndex) {
+    private IRemoteTest getTestShard(int shardCount, int shardIndex) {
         CompatibilityTest test = new CompatibilityTest(shardCount, mModuleRepo, shardIndex);
         OptionCopier.copyOptionsNoThrow(this, test);
         // Set the shard count because the copy option on the previous line
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
index 51b33a1..6d13902 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
@@ -41,6 +41,7 @@
     // literals and making it easier to converge IModuleDef and ModuleDefinition later
     public static String MODULE_NAME = ModuleDefinition.MODULE_NAME;
     public static String MODULE_ABI = ModuleDefinition.MODULE_ABI;
+    public static String MODULE_ID = ModuleDefinition.MODULE_ID;
 
     /**
      * @return The name of this module.
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ISubPlan.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ISubPlan.java
index eda40b2..29876a0 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ISubPlan.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ISubPlan.java
@@ -21,7 +21,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.Set;
 
 
 /**
@@ -36,18 +35,6 @@
     public void parse(InputStream xmlInputStream) throws ParseException;
 
     /**
-     * Retrieve the set of include filters previously added or parsed from XML.
-     * @return a set of include filter strings
-     */
-    public Set<String> getIncludeFilters();
-
-    /**
-     * Retrieve the set of exclude filters previously added or parsed from XML.
-     * @return a set of exclude filter strings
-     */
-    public Set<String> getExcludeFilters();
-
-    /**
      * Serialize the existing filters into a stream of XML, and write to an output stream.
      * @param xmlOutputStream the {@link OutputStream} to receive subplan XML
      */
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
index eb67cc8..d0aef67 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
@@ -28,9 +28,7 @@
 import com.android.tradefed.config.IConfigurationFactory;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IBuildReceiver;
 import com.android.tradefed.testtype.IRemoteTest;
-import com.android.tradefed.testtype.IStrictShardableTest;
 import com.android.tradefed.testtype.ITestFileFilterReceiver;
 import com.android.tradefed.testtype.ITestFilterReceiver;
 import com.android.tradefed.util.AbiUtils;
@@ -253,9 +251,6 @@
                         prepareTestClass(name, abi, config, test);
                     }
                     List<IRemoteTest> shardedTests = tests;
-                    if (mTotalShards > 1) {
-                         shardedTests = splitShardableTests(tests, buildInfo);
-                    }
                     if (shardedTests.size() > 1) {
                         shardedTestCounts.put(id, shardedTests.size());
                     }
@@ -314,23 +309,6 @@
         }
     }
 
-    private List<IRemoteTest> splitShardableTests(List<IRemoteTest> tests, IBuildInfo buildInfo) {
-        ArrayList<IRemoteTest> shardedList = new ArrayList<>(tests.size());
-        for (IRemoteTest test : tests) {
-            if (test instanceof IBuildReceiver) {
-                ((IBuildReceiver)test).setBuild(buildInfo);
-            }
-            if (mShardIndex != null && test instanceof IStrictShardableTest) {
-                for (int i = 0; i < mTotalShards; i++) {
-                    shardedList.add(((IStrictShardableTest)test).getTestShard(mTotalShards, i));
-                }
-            } else {
-                shardedList.add(test);
-            }
-        }
-        return shardedList;
-    }
-
     private void addFilters(Set<String> stringFilters,
             Map<String, List<TestFilter>> filters, Set<IAbi> abis) {
         for (String filterString : stringFilters) {
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/SubPlan.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/SubPlan.java
index c2c7155..46e6326 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/SubPlan.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/SubPlan.java
@@ -107,6 +107,22 @@
      * {@inheritDoc}
      */
     @Override
+    public void clearExcludeFilters() {
+        mExcludes.clear();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clearIncludeFilters() {
+        mIncludes.clear();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public void serialize(OutputStream stream) throws IOException {
         KXmlSerializer serializer = new KXmlSerializer();
         serializer.setOutput(stream, ENCODING);
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTest.java
index 7d6db59..d8f310c 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTest.java
@@ -39,6 +39,8 @@
 import com.android.tradefed.testtype.IInvocationContextReceiver;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.IShardableTest;
+import com.android.tradefed.testtype.suite.BaseTestSuite;
+import com.android.tradefed.util.MultiMap;
 
 import com.google.common.annotations.VisibleForTesting;
 
@@ -131,6 +133,38 @@
             importance = Importance.IF_UNSET)
     protected RetryType mRetryType = null;
 
+    @Option(
+        name = BaseTestSuite.CONFIG_PATTERNS_OPTION,
+        description =
+                "The pattern(s) of the configurations that should be loaded from a directory."
+                        + " If none is explicitly specified, .*.xml and .*.config will be used."
+                        + " Can be repeated."
+    )
+    private List<String> mConfigPatterns = new ArrayList<>();
+
+    @Option(
+        name = "module-metadata-include-filter",
+        description =
+                "Include modules for execution based on matching of metadata fields: for any of "
+                        + "the specified filter name and value, if a module has a metadata field "
+                        + "with the same name and value, it will be included. When both module "
+                        + "inclusion and exclusion rules are applied, inclusion rules will be "
+                        + "evaluated first. Using this together with test filter inclusion rules "
+                        + "may result in no tests to execute if the rules don't overlap."
+    )
+    private MultiMap<String, String> mModuleMetadataIncludeFilter = new MultiMap<>();
+
+    @Option(
+        name = "module-metadata-exclude-filter",
+        description =
+                "Exclude modules for execution based on matching of metadata fields: for any of "
+                        + "the specified filter name and value, if a module has a metadata field "
+                        + "with the same name and value, it will be excluded. When both module "
+                        + "inclusion and exclusion rules are applied, inclusion rules will be "
+                        + "evaluated first."
+    )
+    private MultiMap<String, String> mModuleMetadataExcludeFilter = new MultiMap<>();
+
     private List<ISystemStatusChecker> mStatusCheckers;
     private IBuildInfo mBuildInfo;
     private ITestDevice mDevice;
@@ -229,9 +263,11 @@
         test.setSystemStatusChecker(mStatusCheckers);
         test.setInvocationContext(mContext);
         test.setConfiguration(mMainConfiguration);
+        test.addConfigPatterns(mConfigPatterns);
+        test.addModuleMetadataIncludeFilters(mModuleMetadataIncludeFilter);
+        test.addModuleMetadataExcludeFilters(mModuleMetadataExcludeFilter);
         // reset the retry id - Ensure that retry of retry does not throw
         test.resetRetryId();
-        test.isRetry();
         // clean the helper
         helper.tearDown();
         return test;
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
index 1f8b024..802980a 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
@@ -64,8 +64,6 @@
     private String mSubPlan;
 
     private CompatibilityBuildHelper mBuildHelper;
-    /** Tag if the current instance is running as a retry from RetryFactory */
-    private boolean mIsRetry = false;
 
     /**
      * Ctor that sets some default for Compatibility runs.
@@ -73,7 +71,6 @@
     public CompatibilityTestSuite() {
         try {
             OptionSetter setter = new OptionSetter(this);
-            setter.setOptionValue("config-patterns", ".*\\.config");
             setter.setOptionValue("skip-loading-config-jar", "true");
         } catch (ConfigurationException e) {
             // Should not happen
@@ -148,31 +145,18 @@
     }
 
     /**
-     * Mark the instance of CompatibilityTestSuite as a retry.
-     */
-    public final void isRetry() {
-        mIsRetry = true;
-    }
-
-    /**
      * {@inheritDoc}
      */
     @Override
     public LinkedHashMap<String, IConfiguration> loadingStrategy(
-            Set<IAbi> abis, File testsDir, String suitePrefix, String suiteTag) {
+            Set<IAbi> abis, List<File> testsDirs, String suitePrefix, String suiteTag) {
         LinkedHashMap<String, IConfiguration> loadedConfigs =
-                super.loadingStrategy(abis, testsDir, suitePrefix, suiteTag);
+                super.loadingStrategy(abis, testsDirs, suitePrefix, suiteTag);
         // Add an extra check in CTS since we never expect the config folder to be empty.
         if (loadedConfigs.size() == 0) {
-            if (mIsRetry) {
-                // Only log if it's a retry
-                CLog.logAndDisplay(LogLevel.DEBUG,
-                        "No module that needed to run in retry were found. nothing to do.");
-            } else {
-                throw new IllegalArgumentException(
-                        String.format("No config files found in %s or in resources.",
-                                testsDir.getAbsolutePath()));
-            }
+            // Only log if nothing to run.
+            CLog.logAndDisplay(LogLevel.DEBUG,
+                    "No module that needed to run were found. nothing to do.");
         }
         return loadedConfigs;
     }
diff --git a/common/host-side/tradefed/tests/Android.bp b/common/host-side/tradefed/tests/Android.bp
new file mode 100644
index 0000000..57a8fb9
--- /dev/null
+++ b/common/host-side/tradefed/tests/Android.bp
@@ -0,0 +1,40 @@
+// Copyright (C) 2015 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.
+
+java_test_host {
+    name: "compatibility-tradefed-tests",
+    defaults: ["tradefed_errorprone_defaults"],
+
+    srcs: ["src/**/*.java"],
+    java_resource_dirs: ["res"],
+
+    libs: [
+        "tradefed",
+        "compatibility-mock-tradefed",
+        "junit-host",
+        "compatibility-host-util",
+	"easymock",
+	"objenesis-host",
+	"mockito-host",
+    ],
+}
+
+
+tradefed_binary_host {
+    name: "compatibility-mock-tradefed",
+    short_name: "TESTS",
+    full_name: "Compatibility Tests",
+    version: "1",
+    static_libs: ["cts-tradefed-harness"],
+}
diff --git a/common/host-side/tradefed/tests/Android.mk b/common/host-side/tradefed/tests/Android.mk
deleted file mode 100644
index c952159..0000000
--- a/common/host-side/tradefed/tests/Android.mk
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright (C) 2015 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.
-
-# Make a mock compatibility suite to test
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-include cts/error_prone_rules.mk
-
-LOCAL_SUITE_BUILD_NUMBER := 2
-LOCAL_SUITE_TARGET_ARCH := $(TARGET_ARCH)
-LOCAL_SUITE_NAME := TESTS
-LOCAL_SUITE_FULLNAME := "Compatibility Tests"
-LOCAL_SUITE_VERSION := 1
-
-LOCAL_MODULE := compatibility-mock-tradefed
-LOCAL_STATIC_JAVA_LIBRARIES := cts-tradefed-harness
-include cts/error_prone_rules.mk
-include $(BUILD_COMPATIBILITY_SUITE)
-
-# Make the tests
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_RESOURCE_DIRS := res
-
-include cts/error_prone_rules.mk
-LOCAL_MODULE := compatibility-tradefed-tests
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_STATIC_JAVA_LIBRARIES := easymock objenesis-host mockito-host
-
-LOCAL_JAVA_LIBRARIES := tradefed compatibility-mock-tradefed junit-host compatibility-host-util
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/common/host-side/tradefed/tests/res/testtype/testJar2.jar b/common/host-side/tradefed/tests/res/testtype/testJar2.jar
deleted file mode 100644
index a4c3179..0000000
--- a/common/host-side/tradefed/tests/res/testtype/testJar2.jar
+++ /dev/null
Binary files differ
diff --git a/common/host-side/tradefed/tests/run_tests.sh b/common/host-side/tradefed/tests/run_tests.sh
deleted file mode 100755
index 3cec79c..0000000
--- a/common/host-side/tradefed/tests/run_tests.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/bash
-
-# Copyright (C) 2015 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.
-
-HARNESS_DIR=$(dirname ${0})/../../../..
-source ${HARNESS_DIR}/test_defs.sh
-
-JARS="
-    compatibility-common-util-hostsidelib\
-    compatibility-common-util-tests\
-    compatibility-host-util\
-    compatibility-host-util-tests\
-    compatibility-mock-tradefed\
-    compatibility-tradefed-tests"
-
-run_tests "com.android.compatibility.common.tradefed.UnitTests" "${JARS}" "${@}"
-
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
index 10b57b9..2a0e462 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
@@ -21,6 +21,7 @@
 import com.android.compatibility.common.tradefed.config.ConfigurationFactoryTest;
 import com.android.compatibility.common.tradefed.presubmit.ApkPackageNameCheck;
 import com.android.compatibility.common.tradefed.presubmit.CtsConfigLoadingTest;
+import com.android.compatibility.common.tradefed.presubmit.DupFileTest;
 import com.android.compatibility.common.tradefed.presubmit.IntegrationTest;
 import com.android.compatibility.common.tradefed.presubmit.PresubmitSetupValidation;
 import com.android.compatibility.common.tradefed.presubmit.ValidateTestsAbi;
@@ -31,6 +32,8 @@
 import com.android.compatibility.common.tradefed.result.ResultReporterTest;
 import com.android.compatibility.common.tradefed.result.SubPlanHelperTest;
 import com.android.compatibility.common.tradefed.result.suite.CertificationChecksumHelperTest;
+import com.android.compatibility.common.tradefed.result.suite.PreviousResultLoaderTest;
+import com.android.compatibility.common.tradefed.result.suite.PreviousSessionFileCopierTest;
 import com.android.compatibility.common.tradefed.targetprep.BusinessLogicPreparerTest;
 import com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusherTest;
 import com.android.compatibility.common.tradefed.targetprep.MediaPreparerTest;
@@ -54,8 +57,8 @@
 
 /**
  * A test suite for all compatibility tradefed unit tests.
- * <p/>
- * All tests listed here should be self-contained, and do not require any external dependencies.
+ *
+ * <p>All tests listed here should be self-contained, and do not require any external dependencies.
  */
 @RunWith(Suite.class)
 @SuiteClasses({
@@ -72,6 +75,7 @@
     // presubmit
     ApkPackageNameCheck.class,
     CtsConfigLoadingTest.class,
+    DupFileTest.class,
     IntegrationTest.class,
     PresubmitSetupValidation.class,
     ValidateTestsAbi.class,
@@ -86,6 +90,8 @@
 
     // result.suite
     CertificationChecksumHelperTest.class,
+    PreviousResultLoaderTest.class,
+    PreviousSessionFileCopierTest.class,
 
     // targetprep
     BusinessLogicPreparerTest.class,
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java
index 832bbe4..43284e3 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java
@@ -38,6 +38,7 @@
     private static final String BASE_DIR_NAME = "android-tests";
     private static final String TESTCASES = "testcases";
     private static final String COMMAND_LINE_ARGS = "cts -m CtsModuleTestCases";
+    private static final String BUSINESS_LOGIC_HOST_FILE = "BUSINESS_LOGIC_HOST_FILE";
 
     private File mRoot = null;
     private File mBase = null;
@@ -216,6 +217,159 @@
     }
 
     /**
+     * Test setting business logic host file. When sharding, the contents are the same.
+     */
+    public void testSetBusinessLogicHostFile() throws Exception {
+        File tmpBLFile = FileUtil.createTempFile("businesslogic-test-file", ".bl");
+        FileUtil.writeToFile("test string", tmpBLFile);
+        try {
+            mHelper.setBusinessLogicHostFile(tmpBLFile);
+            File currentBLFile = mHelper.getBusinessLogicHostFile();
+            assertNotNull(currentBLFile);
+            assertEquals(tmpBLFile, currentBLFile);
+            // In case of sharding the underlying build info will be cloned, and old build cleaned.
+            IBuildInfo clone = mBuild.clone();
+            try {
+                CompatibilityBuildHelper helperShard = new CompatibilityBuildHelper(clone);
+                File newBLFile = helperShard.getBusinessLogicHostFile();
+                assertNotNull(newBLFile);
+                // content has also followed.
+                assertEquals("test string", FileUtil.readStringFromFile(newBLFile));
+            } finally {
+                clone.cleanUp();
+            }
+        } finally {
+            FileUtil.deleteFile(tmpBLFile);
+        }
+    }
+
+    /**
+     * Test setting business logic host file with name. When sharding, the contents are the same.
+     */
+    public void testSetBusinessLogicHostFileWithModuleId() throws Exception {
+        File tmpBLFile = FileUtil.createTempFile("businesslogic-test-file", ".bl");
+        FileUtil.writeToFile("test string", tmpBLFile);
+        try {
+            String moduleId = "64MODULE1";
+            mHelper.setBusinessLogicHostFile(tmpBLFile, moduleId);
+            File currentBLFile = mHelper.getBusinessLogicHostFile(moduleId);
+            assertNotNull(currentBLFile);
+            assertEquals(tmpBLFile, currentBLFile);
+            // In case of sharding the underlying build info will be cloned, and old build cleaned.
+            IBuildInfo clone = mBuild.clone();
+            try {
+                CompatibilityBuildHelper helperShard = new CompatibilityBuildHelper(clone);
+                File newBLFile = helperShard.getBusinessLogicHostFile(moduleId);
+                assertNotNull(newBLFile);
+                // content has also followed.
+                assertEquals("test string", FileUtil.readStringFromFile(newBLFile));
+            } finally {
+                clone.cleanUp();
+            }
+        } finally {
+            FileUtil.deleteFile(tmpBLFile);
+        }
+    }
+
+    /**
+     * Test checking business logic host file. When sharding, files still exist.
+     */
+    public void testHasBusinessLogicHostFile() throws Exception {
+        File tmpBLFile = FileUtil.createTempFile("businesslogic-test-file", ".bl");
+        try {
+            mBuild.setFile(BUSINESS_LOGIC_HOST_FILE, tmpBLFile, tmpBLFile.getName());
+            assertTrue(mHelper.hasBusinessLogicHostFile());
+            // In case of sharding the underlying build info will be cloned, and old build cleaned.
+            IBuildInfo clone = mBuild.clone();
+            try {
+                CompatibilityBuildHelper helperShard = new CompatibilityBuildHelper(clone);
+                assertTrue(helperShard.hasBusinessLogicHostFile());
+            } finally {
+                clone.cleanUp();
+            }
+        } finally {
+            FileUtil.deleteFile(tmpBLFile);
+        }
+    }
+
+    /**
+     * Test checking business logic host file with name. When sharding, files still exist.
+     */
+    public void testHasBusinessLogicHostFileModuleId() throws Exception {
+        File tmpBLFile = FileUtil.createTempFile("businesslogic-test-file", ".bl");
+        try {
+            String moduleId = "64MODULE1";
+            mBuild.setFile(BUSINESS_LOGIC_HOST_FILE + moduleId, tmpBLFile, tmpBLFile.getName());
+            assertTrue(mHelper.hasBusinessLogicHostFile(moduleId));
+            // In case of sharding the underlying build info will be cloned, and old build cleaned.
+            IBuildInfo clone = mBuild.clone();
+            try {
+                CompatibilityBuildHelper helperShard = new CompatibilityBuildHelper(clone);
+                assertTrue(helperShard.hasBusinessLogicHostFile(moduleId));
+            } finally {
+                clone.cleanUp();
+            }
+        } finally {
+            FileUtil.deleteFile(tmpBLFile);
+        }
+    }
+
+    /**
+     * Test getting business logic host file. When sharding, the contents are the same.
+     */
+    public void testGetBusinessLogicHostFile() throws Exception {
+        File tmpBLFile = FileUtil.createTempFile("businesslogic-test-file", ".bl");
+        FileUtil.writeToFile("test string", tmpBLFile);
+        try {
+            mBuild.setFile(BUSINESS_LOGIC_HOST_FILE, tmpBLFile, tmpBLFile.getName());
+            File currentBLFile = mHelper.getBusinessLogicHostFile();
+            assertNotNull(currentBLFile);
+            assertEquals(tmpBLFile, currentBLFile);
+            // In case of sharding the underlying build info will be cloned, and old build cleaned.
+            IBuildInfo clone = mBuild.clone();
+            try {
+                CompatibilityBuildHelper helperShard = new CompatibilityBuildHelper(clone);
+                File newBLFile = helperShard.getBusinessLogicHostFile();
+                assertNotNull(newBLFile);
+                // content has also followed.
+                assertEquals("test string", FileUtil.readStringFromFile(newBLFile));
+            } finally {
+                clone.cleanUp();
+            }
+        } finally {
+            FileUtil.deleteFile(tmpBLFile);
+        }
+    }
+
+    /**
+     * Test getting business logic host file with name. When sharding, the contents are the same.
+     */
+    public void testGetBusinessLogicHostFileWithModuleId() throws Exception {
+        File tmpBLFile = FileUtil.createTempFile("businesslogic-test-file", ".bl");
+        FileUtil.writeToFile("test string", tmpBLFile);
+        try {
+            String moduleId = "64MODULE1";
+            mBuild.setFile(BUSINESS_LOGIC_HOST_FILE + moduleId, tmpBLFile, tmpBLFile.getName());
+            File currentBLFile = mHelper.getBusinessLogicHostFile(moduleId);
+            assertNotNull(currentBLFile);
+            assertEquals(tmpBLFile, currentBLFile);
+            // In case of sharding the underlying build info will be cloned, and old build cleaned.
+            IBuildInfo clone = mBuild.clone();
+            try {
+                CompatibilityBuildHelper helperShard = new CompatibilityBuildHelper(clone);
+                File newBLFile = helperShard.getBusinessLogicHostFile(moduleId);
+                assertNotNull(newBLFile);
+                // content has also followed.
+                assertEquals("test string", FileUtil.readStringFromFile(newBLFile));
+            } finally {
+                clone.cleanUp();
+            }
+        } finally {
+            FileUtil.deleteFile(tmpBLFile);
+        }
+    }
+
+    /**
      * Sets the *_ROOT property of the build's installation location.
      *
      * @param value the value to set, or null to clear the property.
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ApkPackageNameCheck.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ApkPackageNameCheck.java
index 92f3f05..e870577 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ApkPackageNameCheck.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ApkPackageNameCheck.java
@@ -19,10 +19,13 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tradefed.config.ConfigurationException;
 import com.android.tradefed.config.ConfigurationFactory;
 import com.android.tradefed.config.IConfiguration;
 import com.android.tradefed.targetprep.ITargetPreparer;
 import com.android.tradefed.targetprep.TestAppInstallSetup;
+import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.InstrumentationTest;
 import com.android.tradefed.util.AaptParser;
 
 import org.junit.Test;
@@ -81,6 +84,7 @@
                     .createConfigurationFromArgs(new String[] {config.getAbsolutePath()});
             // For each config, we check all the apk it's going to install
             List<String> apkNames = new ArrayList<>();
+            List<String> packageListNames = new ArrayList<>();
             for (ITargetPreparer prep : c.getTargetPreparers()) {
                 if (prep instanceof TestAppInstallSetup) {
                     apkNames.addAll(((TestAppInstallSetup) prep).getTestsFileName());
@@ -97,6 +101,7 @@
                 assertNotNull(res);
                 String packageName = res.getPackageName();
                 String put = packageNames.put(packageName, apkName);
+                packageListNames.add(packageName);
                 // The package already exists and it's a different apk
                 if (put != null && !apkName.equals(put) && !EXCEPTION_LIST.contains(packageName)) {
                     fail(String.format("Module %s: Package name '%s' from apk '%s' was already "
@@ -104,6 +109,22 @@
                             config.getName(), packageName, apkName, put));
                 }
             }
+
+            // Catch a test trying to run something it doesn't install.
+            List<IRemoteTest> tests = c.getTests();
+            for (IRemoteTest test : tests) {
+                if (test instanceof InstrumentationTest) {
+                    InstrumentationTest instrumentationTest = (InstrumentationTest) test;
+                    if (instrumentationTest.getPackageName() != null) {
+                        if (!packageListNames.contains(instrumentationTest.getPackageName())) {
+                            throw new ConfigurationException(
+                                    String.format("Module %s requests to run '%s' but it's not "
+                                        + "part of any apks.",
+                                        config.getName(), instrumentationTest.getPackageName()));
+                        }
+                    }
+                }
+            }
         }
     }
 }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java
index 46c4022..a353f44 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java
@@ -20,17 +20,21 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.tradefed.targetprep.ApkInstaller;
+import com.android.compatibility.common.tradefed.targetprep.PreconditionPreparer;
 import com.android.compatibility.common.tradefed.testtype.JarHostTest;
 import com.android.tradefed.build.FolderBuildInfo;
 import com.android.tradefed.config.ConfigurationDescriptor;
 import com.android.tradefed.config.ConfigurationException;
 import com.android.tradefed.config.ConfigurationFactory;
 import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.invoker.shard.token.TokenProperty;
 import com.android.tradefed.targetprep.ITargetPreparer;
 import com.android.tradefed.testtype.AndroidJUnitTest;
 import com.android.tradefed.testtype.HostTest;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.ITestFilterReceiver;
+import com.android.tradefed.testtype.suite.ITestSuite;
+import com.android.tradefed.testtype.suite.params.ModuleParameters;
 
 import org.junit.Assert;
 import org.junit.Test;
@@ -62,6 +66,7 @@
             "bionic",
             "bluetooth",
             "camera",
+            "deviceinfo",
             "deqp",
             "devtools",
             "framework",
@@ -87,6 +92,24 @@
             "vr",
             "webview"
     ));
+    private static final Set<String> KNOWN_MISC_MODULES =
+            new HashSet<>(
+                    Arrays.asList(
+                            // Modifications to the list below must be approved by someone in
+                            // test/suite_harness/OWNERS.
+                            "CtsSliceTestCases.config",
+                            "CtsSampleDeviceTestCases.config",
+                            "CtsUsbTests.config",
+                            "CtsGpuToolsHostTestCases.config",
+                            "CtsEdiHostTestCases.config",
+                            "CtsClassLoaderFactoryPathClassLoaderTestCases.config",
+                            "CtsSampleHostTestCases.config",
+                            "CtsHardwareTestCases.config",
+                            "CtsMonkeyTestCases.config",
+                            "CtsAndroidAppTestCases.config",
+                            "CtsClassLoaderFactoryInMemoryDexClassLoaderTestCases.config",
+                            "CtsAppComponentFactoryTestCases.config",
+                            "CtsSeccompHostTestCases.config"));
 
     /**
      * List of the officially supported runners in CTS, they meet all the interfaces criteria as
@@ -163,6 +186,13 @@
                                     + "common.tradefed.targetprep.ApkInstaller, options will be "
                                     + "the same.", config));
                 }
+                if (prep.getClass().isAssignableFrom(PreconditionPreparer.class)) {
+                    throw new ConfigurationException(
+                            String.format(
+                                    "%s: includes a PreconditionPreparer (%s) which is not allowed"
+                                            + " in modules.",
+                                    config.getName(), prep.getClass()));
+                }
             }
             // We can ensure that Host side tests are not empty.
             for (IRemoteTest test : c.getTests()) {
@@ -189,11 +219,11 @@
                             "Test in module %s must implement ITestFilterReceiver.",
                             config.getName()));
                 }
-                // Ensure that the device runner is the AJUR one if explicitly specified
+                // Ensure that the device runner is the AJUR one if explicitly specified.
                 if (test instanceof AndroidJUnitTest) {
                     AndroidJUnitTest instru = (AndroidJUnitTest) test;
                     if (instru.getRunnerName() != null &&
-                        !ALLOWED_INSTRUMENTATION_RUNNER_NAME.contains(instru.getRunnerName())) {
+                            !ALLOWED_INSTRUMENTATION_RUNNER_NAME.contains(instru.getRunnerName())) {
                         // Some runner are exempt
                         if (!RUNNER_EXCEPTION.contains(instru.getRunnerName())) {
                             throw new ConfigurationException(
@@ -221,6 +251,21 @@
                     + "field \"%s\", supported ones are: %s\nconfig: %s",
                     cmp, KNOWN_COMPONENTS, config), KNOWN_COMPONENTS.contains(cmp));
 
+            if ("misc".equals(cmp)) {
+                String configFileName = config.getName();
+                Assert.assertTrue(
+                        String.format(
+                                "Adding new module %s to \"misc\" component is restricted, "
+                                        + "please pick a component that your module fits in",
+                                configFileName),
+                        KNOWN_MISC_MODULES.contains(configFileName));
+            }
+
+            // Check that specified parameters are expected
+            checkModuleParameters(config.getName(), cd.getMetaData(ITestSuite.PARAMETER_KEY));
+            // Check that specified tokens are expected
+            checkTokens(config.getName(), cd.getMetaData(ITestSuite.TOKEN_KEY));
+
             // Ensure each CTS module is tagged with <option name="test-suite-tag" value="cts" />
             Assert.assertTrue(String.format(
                     "Module config %s does not contains "
@@ -238,6 +283,43 @@
                     }
                 }
             }
+            // Ensure options have been set
+            c.validateOptions();
+        }
+    }
+
+    /**
+     * Test that all parameter metadata can be resolved.
+     */
+    private void checkModuleParameters(String configName, List<String> parameters)
+            throws ConfigurationException {
+        if (parameters == null) {
+            return;
+        }
+        for (String param : parameters) {
+            try {
+                ModuleParameters.valueOf(param.toUpperCase());
+            } catch (IllegalArgumentException e) {
+                throw new ConfigurationException(
+                        String.format("Config: %s includes an unknown parameter '%s'.",
+                                configName, param));
+            }
+        }
+    }
+
+    /** Test that all tokens can be resolved. */
+    private void checkTokens(String configName, List<String> tokens) throws ConfigurationException {
+        if (tokens == null) {
+            return;
+        }
+        for (String token : tokens) {
+            try {
+                TokenProperty.valueOf(token.toUpperCase());
+            } catch (IllegalArgumentException e) {
+                throw new ConfigurationException(
+                        String.format(
+                                "Config: %s includes an unknown token '%s'.", configName, token));
+            }
         }
     }
 }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/DupFileTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/DupFileTest.java
new file mode 100644
index 0000000..aef768b
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/DupFileTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2018 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.compatibility.common.tradefed.presubmit;
+
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.config.ConfigurationException;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * Test to check for duplicate files in different jars and prevent the same dependencies of being
+ * included several time (which might result in version conflicts).
+ */
+@RunWith(JUnit4.class)
+public class DupFileTest {
+
+    // We ignore directories part of the common java and google packages.
+    private static final String[] IGNORE_DIRS = new String[] {"android/", "javax/annotation/"};
+
+    /** test if there are duplicate files in different jars. */
+    @Test
+    public void testDupFilesExist() throws Exception {
+        // Get list of jars.
+        List<File> jars = getListOfBuiltJars();
+
+        // Create map of files to jars.
+        Map<String, List<String>> filesToJars = getMapOfFilesAndJars(jars);
+
+        // Check if there are any files with the same name in diff jars.
+        int dupedFiles = 0;
+        StringBuilder dupedFilesSummary = new StringBuilder();
+        for (Map.Entry<String, List<String>> entry : filesToJars.entrySet()) {
+            String file = entry.getKey();
+            List<String> jarFiles = entry.getValue();
+
+            if (jarFiles.size() != 1) {
+                dupedFiles++;
+                dupedFilesSummary.append(file + ": " + jarFiles.toString() + "\n");
+            }
+        }
+
+        if (dupedFiles != 0) {
+            fail(
+                    String.format(
+                            "%d files are duplicated in different jars:\n%s",
+                            dupedFiles, dupedFilesSummary.toString()));
+        }
+    }
+
+    /** Create map of file to jars */
+    private Map<String, List<String>> getMapOfFilesAndJars(List<File> jars) throws IOException {
+        Map<String, List<String>> map = new LinkedHashMap<String, List<String>>();
+        JarFile jarFile;
+        List<String> jarFileList;
+        // Map all the files from all the jars.
+        for (File jar : jars) {
+            jarFile = new JarFile(jar);
+            jarFileList = getListOfFiles(jarFile);
+            jarFile.close();
+
+            // Add in the jar file to the map.
+            for (String file : jarFileList) {
+                if (!map.containsKey(file)) {
+                    map.put(file, new LinkedList<String>());
+                }
+
+                map.get(file).add(jar.getName());
+            }
+        }
+        return map;
+    }
+
+    /** Get the list of jars specified in the path. */
+    private List<File> getListOfBuiltJars() throws ConfigurationException {
+        String classpathStr = System.getProperty("java.class.path");
+        if (classpathStr == null) {
+            throw new ConfigurationException(
+                    "Could not find the classpath property: java.class.path");
+        }
+        List<File> listOfJars = new ArrayList<File>();
+        for (String jar : classpathStr.split(":")) {
+            File jarFile = new File(jar);
+            if (jarFile.exists()) {
+                listOfJars.add(jarFile);
+            }
+        }
+        return listOfJars;
+    }
+
+    /** Return the list of files in the jar. */
+    private List<String> getListOfFiles(JarFile jar) {
+        List<String> files = new ArrayList<String>();
+        Enumeration<JarEntry> e = jar.entries();
+        while (e.hasMoreElements()) {
+            JarEntry entry = e.nextElement();
+            String filename = entry.getName();
+            if (checkThisFile(filename)) {
+                files.add(filename);
+            }
+        }
+        return files;
+    }
+
+    /** Check if we should add this file to list of files. We only want to check for classes. */
+    private Boolean checkThisFile(String filename) {
+        if (!filename.endsWith(".class")) {
+            return false;
+        }
+
+        for (String skipDir : IGNORE_DIRS) {
+            if (filename.startsWith(skipDir)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/IntegrationTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/IntegrationTest.java
index 594ace1..63e6fab 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/IntegrationTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/IntegrationTest.java
@@ -16,7 +16,6 @@
 package com.android.compatibility.common.tradefed.presubmit;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.tradefed.result.ResultReporter;
@@ -40,7 +39,6 @@
 import com.android.tradefed.testtype.IDeviceTest;
 import com.android.tradefed.testtype.IInvocationContextReceiver;
 import com.android.tradefed.testtype.IRemoteTest;
-import com.android.tradefed.testtype.suite.TestSuiteInfo;
 import com.android.tradefed.util.AbiUtils;
 import com.android.tradefed.util.FileUtil;
 
@@ -57,11 +55,9 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Integration tests between {@link CompatibilityTest} and {@link ResultReporter} to ensure proper
@@ -546,160 +542,4 @@
             }
         }
     }
-
-    /**
-     * Simple tests running in one module that should be marked complete when each shard run a test
-     * from the module. Each Module is going to run 1 pass 1 fail. 2 modules and 2 shards.
-     * Using the {@link CompatibilityTest#split()}.
-     */
-    @Test
-    public void testSingleModuleRun_sharded() throws Exception {
-        final String moduleName = "module_sharded";
-        Set<String> abis = AbiUtils.getAbisForArch(TestSuiteInfo.getInstance().getTargetArch());
-        Iterator<String> ite = abis.iterator();
-        final String abi1 = ite.next();
-        final String abi2 = ite.next();
-        createConfig(mTestDir, moduleName, TEST_STUB_SHARDABLE, true, true, true, false);
-        EasyMock.expect(mMockDevice.getProperty("ro.product.cpu.abilist")).andReturn(
-                String.format("%s,%s", abi1, abi2));
-        mMockBuildInfo.addBuildAttribute(EasyMock.eq(CompatibilityBuildHelper.MODULE_IDS),
-                EasyMock.anyObject());
-        EasyMock.expectLastCall();
-
-        EasyMock.replay(mMockDevice, mMockBuildInfo);
-
-        OptionSetter setter = new OptionSetter(mTest);
-        setter.setOptionValue("shards", "2");
-        List<IRemoteTest> tests = (List<IRemoteTest>) mTest.split();
-        // We expect 2 shards
-        assertEquals(2, tests.size());
-
-        List<ShardThread> threads = new ArrayList<>();
-        // Run all shards
-        for (IRemoteTest test : tests) {
-            ShardThread st = new ShardThread(test, mReporter, mMockBuildInfo, mMockDevice,
-                    mContext);
-            threads.add(st);
-            st.start();
-        }
-        for (ShardThread thread : threads) {
-            thread.join(5000);
-        }
-        // Allow some time for ResultReport to finalize the results coming from the threads.
-        boolean finalized = mReporter.waitForFinalized(2, TimeUnit.MINUTES);
-        assertTrue(finalized);
-        EasyMock.verify(mMockDevice, mMockBuildInfo);
-        // Check aggregated results to make sure it's consistent.
-        IInvocationResult result = mReporter.getResult();
-        assertEquals(4, result.countResults(TestStatus.PASS));
-        assertEquals(4, result.countResults(TestStatus.FAIL));
-        assertEquals(2, result.getModules().size());
-        //assertEquals(2, result.getModuleCompleteCount());
-    }
-
-    /**
-     * Simple tests running in one module that should be marked incomplete when shards do not
-     * complete. Each shard is going to run 1 pass 1 fail 1 not_executed.
-     * Using the {@link CompatibilityTest#split()}.
-     */
-    @Test
-    public void testSingleModuleRun_sharded_incomplete() throws Exception {
-        final String moduleName = "module_sharded_incomplete";
-        Set<String> abis = AbiUtils.getAbisForArch(TestSuiteInfo.getInstance().getTargetArch());
-        Iterator<String> ite = abis.iterator();
-        final String abi1 = ite.next();
-        final String abi2 = ite.next();
-        createConfig(mTestDir, moduleName, TEST_STUB_SHARDABLE, true, false, true, false);
-        EasyMock.expect(mMockDevice.getProperty("ro.product.cpu.abilist")).andReturn(
-                String.format("%s,%s", abi1, abi2));
-        mMockBuildInfo.addBuildAttribute(EasyMock.eq(CompatibilityBuildHelper.MODULE_IDS),
-                EasyMock.anyObject());
-        EasyMock.expectLastCall();
-
-        EasyMock.replay(mMockDevice, mMockBuildInfo);
-        OptionSetter setter = new OptionSetter(mTest);
-        setter.setOptionValue("shards", "2");
-        List<IRemoteTest> tests = (List<IRemoteTest>) mTest.split();
-        // We expect 2 shards
-        assertEquals(2, tests.size());
-
-        List<ShardThread> threads = new ArrayList<>();
-        // Run all shards
-        for (IRemoteTest test : tests) {
-            ShardThread st = new ShardThread(test, mReporter, mMockBuildInfo, mMockDevice,
-                    mContext);
-            threads.add(st);
-            st.start();
-        }
-        for (ShardThread thread : threads) {
-            thread.join(5000);
-        }
-        // Allow some time for ResultReport to finalize the results coming from the threads.
-        boolean finalized = mReporter.waitForFinalized(2, TimeUnit.MINUTES);
-        assertTrue(finalized);
-        EasyMock.verify(mMockDevice, mMockBuildInfo);
-        // Check aggregated results to make sure it's consistent.
-        IInvocationResult result = mReporter.getResult();
-        assertEquals(4, result.countResults(TestStatus.PASS));
-        assertEquals(4, result.countResults(TestStatus.FAIL));
-        assertEquals(2, result.getModules().size());
-        assertEquals(0, result.getModuleCompleteCount());
-    }
-
-    /**
-     * Simple tests running in one module that should be marked complete when each shard run a test
-     * from the module.
-     * We are going to run only one of the shard since IStrictShardable allows it.
-     * Using the {@link CompatibilityTest#getTestShard(int, int)}.
-     * FIXME: Fix expectation of this test.
-     */
-    @Test
-    public void testSingleModuleRun_sharded_getTestShard() throws Exception {
-        final String moduleName = "module_sharded_getTestShard";
-        Set<String> abis = AbiUtils.getAbisForArch(TestSuiteInfo.getInstance().getTargetArch());
-        Iterator<String> ite = abis.iterator();
-        final String abi1 = ite.next();
-        final String abi2 = ite.next();
-        createConfig(mTestDir, moduleName, TEST_STUB_SHARDABLE, true, true, true, false);
-        EasyMock.expect(mMockDevice.getProperty("ro.product.cpu.abilist")).andReturn(
-                String.format("%s,%s", abi1, abi2));
-
-        String expectedAdd = AbiUtils.createId(abi1, moduleName) + ","
-                + AbiUtils.createId(abi2, moduleName);
-        mMockBuildInfo.addBuildAttribute(EasyMock.eq(CompatibilityBuildHelper.MODULE_IDS),
-                EasyMock.anyObject());
-        EasyMock.expectLastCall();
-        mAttributes.put(CompatibilityBuildHelper.MODULE_IDS, expectedAdd);
-
-        EasyMock.replay(mMockDevice, mMockBuildInfo);
-
-        List<IRemoteTest> tests = new ArrayList<>();
-        tests.add(mTest.getTestShard(3, 0));
-        // We are only running one of the shards since they should be independent.
-        assertEquals(1, tests.size());
-
-        ((IBuildReceiver)tests.get(0)).setBuild(mMockBuildInfo);
-        ((IDeviceTest)tests.get(0)).setDevice(mMockDevice);
-        ((IInvocationContextReceiver)tests.get(0)).setInvocationContext(mContext);
-        mReporter.invocationStarted(mContext);
-        try {
-            tests.get(0).run(mReporter);
-        } catch (DeviceNotAvailableException e) {
-            throw new RuntimeException(e);
-        } finally {
-            mReporter.invocationEnded(500);
-        }
-        EasyMock.verify(mMockDevice, mMockBuildInfo);
-
-        IInvocationResult result = mReporter.getResult();
-        assertEquals(2, result.countResults(TestStatus.PASS));
-        assertEquals(2, result.countResults(TestStatus.FAIL));
-        // FIXME: Only one module should be expected since within the one shard requested to run
-        // only one module existed.
-        assertEquals(2, result.getModules().size());
-        // FIXME: The module for the shard should be completed since all tests run.
-        // TestRunHandler in this case create an expectation of 3 testRunStarted just because of
-        // the number of shards.
-        assertEquals(0, result.getModuleCompleteCount());
-    }
 }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java
index 297ccfc..9f2aa1b 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java
@@ -115,7 +115,7 @@
             if (!result.getNativeCode().isEmpty()) {
                 List<String> supportedAbiApk = result.getNativeCode();
                 Set<String> buildTarget = AbiUtils.getAbisForArch(
-                        TestSuiteInfo.getInstance().getTargetArch());
+                        TestSuiteInfo.getInstance().getTargetArchs().get(0));
                 // first check, all the abis are supported
                 for (String abi : supportedAbiApk) {
                     if (!buildTarget.contains(abi)) {
@@ -176,7 +176,7 @@
         // characters of their name with be the bitness (32 or 64).
         Collections.sort(orderedList);
         Set<String> buildTarget = AbiUtils.getAbisForArch(
-                TestSuiteInfo.getInstance().getTargetArch());
+                TestSuiteInfo.getInstance().getTargetArchs().get(0));
         // We expect one binary per abi of CTS, they should be appended with 32 or 64
         for (int i = 0; i < orderedList.size(); i=i + buildTarget.size()) {
             List<String> subSet = orderedList.subList(i, i + buildTarget.size());
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
index ca620ae..a179978 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
@@ -532,8 +532,8 @@
         assertEquals("Expected 1 module", 1, modules.size());
         IModuleResult module = modules.get(0);
 
-        // Ensure module is seens as done but failed
-        assertTrue(module.isDone());
+        // Ensure module is seen as not done and failed
+        assertFalse(module.isDone());
         assertTrue(module.isFailed());
 
         assertEquals("Incorrect ID", ID, module.getId());
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/SubPlanHelperTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/SubPlanHelperTest.java
index 8912411..005e180 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/SubPlanHelperTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/SubPlanHelperTest.java
@@ -89,7 +89,7 @@
         mResultsDir = FileUtil.createTempDir("results");
         mResultDir = FileUtil.createTempDir("12345", mResultsDir);
         mSubPlansDir = FileUtil.createTempDir("subplans");
-        mBuildHelper = new SpctMockCompatibilityBuildHelper(new BuildInfo("0", "", ""));
+        mBuildHelper = new SpctMockCompatibilityBuildHelper(new BuildInfo("0", ""));
         populateResults();
 
         mSubPlanHelper = new SubPlanHelper();
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/suite/PreviousResultLoaderTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/suite/PreviousResultLoaderTest.java
new file mode 100644
index 0000000..89c21c7
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/suite/PreviousResultLoaderTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2018 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.compatibility.common.tradefed.result.suite;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.tradefed.targetprep.BuildFingerPrintPreparer;
+import com.android.tradefed.build.DeviceBuildInfo;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.IBuildProvider;
+import com.android.tradefed.config.Configuration;
+import com.android.tradefed.config.ConfigurationDef;
+import com.android.tradefed.config.ConfigurationDescriptor;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.invoker.TestInvocation;
+import com.android.tradefed.result.proto.TestRecordProto.TestRecord;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.util.FileUtil;
+
+import com.google.protobuf.Any;
+
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+
+/**
+ * Unit tests for {@link PreviousResultLoader}.
+ */
+@RunWith(JUnit4.class)
+public class PreviousResultLoaderTest {
+
+    private PreviousResultLoader mLoader;
+    private IInvocationContext mContext;
+    private File mRootDir;
+    private File mProtoFile;
+
+    private ITestDevice mMockDevice;
+    private IBuildProvider mMockProvider;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockProvider = EasyMock.createMock(IBuildProvider.class);
+        mLoader = new PreviousResultLoader();
+        mLoader.setProvider(mMockProvider);
+        OptionSetter setter = new OptionSetter(mLoader);
+        setter.setOptionValue("retry", "0");
+        mContext = new InvocationContext();
+        mContext.setConfigurationDescriptor(new ConfigurationDescriptor());
+        mContext.addInvocationAttribute(TestInvocation.COMMAND_ARGS_KEY,
+                "cts -m CtsGesture --skip-all-system-status-check");
+        mMockDevice = EasyMock.createMock(ITestDevice.class);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        FileUtil.recursiveDelete(mRootDir);
+    }
+
+    /**
+     * Test the loader properly fails when the results are not loaded.
+     */
+    @Test
+    public void testReloadTests_failed() throws Exception {
+        EasyMock.expect(mMockProvider.getBuild()).andReturn(createFakeBuild(""));
+        // Delete the proto file
+        mProtoFile.delete();
+        try {
+            EasyMock.replay(mMockProvider);
+            mLoader.init();
+            fail("Should have thrown an exception.");
+        } catch (RuntimeException expected) {
+            // expected
+            assertEquals(
+                    String.format("java.io.FileNotFoundException: %s (No such file or directory)",
+                            mProtoFile.getAbsolutePath()), expected.getMessage());
+        }
+        EasyMock.verify(mMockProvider);
+    }
+
+    /**
+     * Test that the loader can provide the results information back.
+     */
+    @Test
+    public void testReloadTests() throws Exception {
+        EasyMock.expect(mMockProvider.getBuild()).andReturn(createFakeBuild(createBasicResults()));
+        mContext.addAllocatedDevice(ConfigurationDef.DEFAULT_DEVICE_NAME, mMockDevice);
+
+        EasyMock.replay(mMockDevice, mMockProvider);
+        mLoader.init();
+        assertEquals("cts -m CtsGesture --skip-all-system-status-check", mLoader.getCommandLine());
+        mLoader.loadPreviousRecord();
+        IConfiguration config = new Configuration("name", "desc");
+        assertEquals(0, config.getTargetPreparers().size());
+        mLoader.customizeConfiguration(config);
+        // A special preparer was added for fingerprint
+        assertEquals(1, config.getTargetPreparers().size());
+        ITargetPreparer preparer = config.getTargetPreparers().get(0);
+        assertTrue(preparer instanceof BuildFingerPrintPreparer);
+        assertEquals("testfingerprint",
+                ((BuildFingerPrintPreparer) preparer).getExpectedFingerprint());
+        EasyMock.verify(mMockDevice, mMockProvider);
+    }
+
+    private IBuildInfo createFakeBuild(String resultContent) throws Exception {
+        DeviceBuildInfo build = new DeviceBuildInfo();
+        build.addBuildAttribute(CompatibilityBuildHelper.SUITE_NAME, "CTS");
+        mRootDir = FileUtil.createTempDir("cts-root-dir");
+        new File(mRootDir, "android-cts/results").mkdirs();
+        build.addBuildAttribute(CompatibilityBuildHelper.ROOT_DIR, mRootDir.getAbsolutePath());
+        // Create fake result dir
+        long time = System.currentTimeMillis();
+        build.addBuildAttribute(CompatibilityBuildHelper.START_TIME_MS, Long.toString(time));
+        new CompatibilityBuildHelper(build).getResultDir().mkdirs();
+        // Populate a test_results.xml
+        File testResult = new File(new CompatibilityBuildHelper(build).getResultDir(),
+                "test_result.xml");
+        testResult.createNewFile();
+        // Populate a proto result
+        mProtoFile = new File(new CompatibilityBuildHelper(build).getResultDir(),
+                CompatibilityProtoResultReporter.PROTO_FILE_NAME);
+        TestRecord.Builder builder = TestRecord.newBuilder();
+        builder.setDescription(Any.pack(mContext.toProto()));
+        builder.build().writeDelimitedTo(new FileOutputStream(mProtoFile));
+        FileUtil.writeToFile(resultContent, testResult);
+        return build;
+    }
+
+    private String createBasicResults() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("<?xml version='1.0' encoding='UTF-8' standalone='no' ?>\n");
+        sb.append("<?xml-stylesheet type=\"text/xsl\" href=\"compatibility_result.xsl\"?>\n");
+        sb.append("<Result start=\"1530218251501\" end=\"1530218261061\" "
+                + "start_display=\"Thu Jun 28 13:37:31 PDT 2018\" "
+                + "end_display=\"Thu Jun 28 13:37:41 PDT 2018\" "
+                + "command_line_args=\"cts -m CtsGesture --skip-all-system-status-check\" "
+                + "suite_name=\"CTS\" suite_version=\"9.0_r1\" "
+                + "suite_plan=\"cts\" suite_build_number=\"8888\" report_version=\"5.0\" "
+                + "devices=\"HT6570300047\"  >\n");
+        sb.append(
+                "  <Build command_line_args=\"cts -m CtsGesture --skip-all-system-status-check\""
+                        + " build_vendor_fingerprint=\"vendorFingerprint\" "
+                        + " build_reference_fingerprint=\"\" "
+                        + " build_fingerprint=\"testfingerprint\"/>\n");
+        // Summary
+        sb.append("  <Summary pass=\"0\" failed=\"0\" modules_done=\"2\" modules_total=\"2\" />\n");
+        // Each module results
+        sb.append("  <Module name=\"CtsGestureTestCases\" abi=\"arm64-v8a\" runtime=\"2776\" "
+                + "done=\"true\" pass=\"0\" total_tests=\"0\" />\n");
+        sb.append("  <Module name=\"CtsGestureTestCases\" abi=\"armeabi-v7a\" runtime=\"2776\" "
+                + "done=\"true\" pass=\"0\" total_tests=\"0\" />\n");
+        // End
+        sb.append("</Result>");
+        return sb.toString();
+    }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/suite/PreviousSessionFileCopierTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/suite/PreviousSessionFileCopierTest.java
new file mode 100644
index 0000000..918acd3
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/suite/PreviousSessionFileCopierTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2018 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.compatibility.common.tradefed.result.suite;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.BuildInfo;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.ConfigurationDef;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.util.FileUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+/** Unit tests for {@link PreviousSessionFileCopier}. */
+@RunWith(JUnit4.class)
+public class PreviousSessionFileCopierTest {
+
+    private PreviousSessionFileCopier mCopier;
+    private File mPreviousDir;
+    private File mCurrentDir;
+    private IInvocationContext mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mCurrentDir = FileUtil.createTempDir("current-copier-tests");
+        mCopier =
+                new PreviousSessionFileCopier() {
+                    @Override
+                    protected CompatibilityBuildHelper createCompatibilityHelper(IBuildInfo info) {
+                        return new CompatibilityBuildHelper(info) {
+                            @Override
+                            public File getResultDir() throws FileNotFoundException {
+                                return mCurrentDir;
+                            }
+                        };
+                    }
+                };
+        mPreviousDir = FileUtil.createTempDir("previous-copier-tests");
+        mContext = new InvocationContext();
+        mContext.addDeviceBuildInfo(ConfigurationDef.DEFAULT_DEVICE_NAME, new BuildInfo());
+        mCopier.setPreviousSessionDir(mPreviousDir);
+    }
+
+    @After
+    public void tearDown() {
+        FileUtil.recursiveDelete(mPreviousDir);
+    }
+
+    @Test
+    public void testCopy() throws Exception {
+        new File(mPreviousDir, "newFile").createNewFile();
+        assertEquals(0, mCurrentDir.listFiles().length);
+        mCopier.invocationStarted(mContext);
+        mCopier.invocationEnded(500L);
+        assertEquals(1, mCurrentDir.listFiles().length);
+    }
+
+    @Test
+    public void testCopy_fileExists() throws Exception {
+        File original = new File(mCurrentDir, "newFile");
+        original.createNewFile();
+        FileUtil.writeToFile("CURRENT", original);
+
+        File previous = new File(mPreviousDir, "newFile");
+        previous.createNewFile();
+        FileUtil.writeToFile("PREVIOUS", previous);
+
+        assertEquals(1, mCurrentDir.listFiles().length);
+        mCopier.invocationStarted(mContext);
+        mCopier.invocationEnded(500L);
+        assertEquals(1, mCurrentDir.listFiles().length);
+        // File are not overriden
+        assertEquals("CURRENT", FileUtil.readStringFromFile(original));
+    }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/BusinessLogicPreparerTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/BusinessLogicPreparerTest.java
index 7dca26f..cbffc2f 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/BusinessLogicPreparerTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/BusinessLogicPreparerTest.java
@@ -142,6 +142,7 @@
         OptionSetter setter = new OptionSetter(mPreparer);
         setter.setOptionValue("business-logic-url", serviceUrl);
         setter.setOptionValue("business-logic-api-key", "fakeApiKey");
+        setter.setOptionValue("version", "fakeVersion");
     }
 
     @After
@@ -157,7 +158,7 @@
         mMockBuildInfo.setFile(DeviceInfoCollector.DEVICE_INFO_DIR, jsonPath, "v1");
         // Setup BuildInfo attributes.
         mMockBuildInfo.addBuildAttribute(CompatibilityBuildHelper.SUITE_VERSION, "v1");
-        testBuildRequestString(15, attributes);
+        testBuildRequestString(16, attributes);
     }
 
     @Test
@@ -165,7 +166,7 @@
         Map<String, String> attributes = new HashMap<>();
         // Setup BuildInfo attributes.
         attributes.put(CompatibilityBuildHelper.SUITE_VERSION, "v1");
-        testBuildRequestString(13, attributes);
+        testBuildRequestString(14, attributes);
     }
 
     private void testBuildRequestString(int expectedParams, Map<String, String> attributes) throws Exception {
@@ -181,6 +182,7 @@
         mMockBuildInfo.setFile(CONFIG_VERSION + "tests", configFile, CONFIG_VERSION + "tests");
         mMockBuildInfo.setFile(CONFIG_VERSION + "gts", configFile, CONFIG_VERSION + "gts");
         mMockBuildInfo.setFile(CONFIG_VERSION + "cts", configFile, CONFIG_VERSION + "cts");
+        mMockBuildInfo.setFile(CONFIG_VERSION + "ats", configFile, CONFIG_VERSION + "ats");
         when(mMockDevice.executeShellCommand(LIST_FEATURE_QUERY)).thenReturn(FEATURES);
         // In getBusinessLogicProperties.
         when(mMockDevice.executeShellCommand(GOOGLE_SETTINGS_QUERY)).thenReturn(PARTNER_CONTENT);
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusherTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusherTest.java
index 152c695..bed34a5 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusherTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusherTest.java
@@ -23,8 +23,12 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.VersionedFile;
+import com.android.tradefed.config.ConfigurationDescriptor;
 import com.android.tradefed.config.OptionSetter;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
 import com.android.tradefed.targetprep.TargetSetupError;
 import com.android.tradefed.util.FileUtil;
 
@@ -37,7 +41,10 @@
 
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.Map;
 
 /**
@@ -50,9 +57,12 @@
     private ITestDevice mMockDevice;
     private CompatibilityBuildHelper mMockBuildHelper;
     private IBuildInfo mMockBuildInfo;
+    private IInvocationContext mModuleContext;
 
     @Before
     public void setUp() {
+        mModuleContext = new InvocationContext();
+        mModuleContext.setConfigurationDescriptor(new ConfigurationDescriptor());
         mPreparer = new DynamicConfigPusher();
         mMockDevice = EasyMock.createMock(ITestDevice.class);
         mMockBuildInfo = EasyMock.createMock(IBuildInfo.class);
@@ -61,6 +71,57 @@
     }
 
     /**
+     * Test getSuiteName from /test-suite-info.properties.
+     */
+    @Test
+    public void testGetSuiteName_fromTestSuiteInfo() throws Exception {
+        mPreparer = new DynamicConfigPusher();
+        mPreparer.setInvocationContext(mModuleContext);
+        EasyMock.replay(mMockDevice, mMockBuildInfo);
+        assertNotNull(mPreparer.getSuiteName());
+        EasyMock.verify(mMockDevice, mMockBuildInfo);
+    }
+
+    /**
+     * Test getSuiteName from test-suite-tag.
+     */
+    @Test
+    public void testGetSuiteName_fromTestSuiteTag() throws Exception {
+        mPreparer = new DynamicConfigPusher();
+        mModuleContext
+                .getConfigurationDescriptor()
+                .setSuiteTags(Arrays.asList("cts", "cts-instant", "gts"));
+        mPreparer.setInvocationContext(mModuleContext);
+        EasyMock.replay(mMockDevice, mMockBuildInfo);
+        assertNotNull(mPreparer.getSuiteName());
+        EasyMock.verify(mMockDevice, mMockBuildInfo);
+    }
+
+    /**
+     * Test that when we look up resources locally, we search them from the build helper.
+     */
+    @Test
+    public void testLocalRead_fromDynamicConfigName() throws Exception {
+        OptionSetter setter = new OptionSetter(mPreparer);
+        setter.setOptionValue("config-filename", "config-test-name");
+        setter.setOptionValue("dynamic-config-name", "dynamic-config-test-name");
+        setter.setOptionValue("extract-from-resource", "false");
+
+        File check = new File("anyfilewilldo");
+        mMockBuildHelper = new CompatibilityBuildHelper(mMockBuildInfo) {
+            @Override
+            public File getTestFile(String filename) throws FileNotFoundException {
+                return check;
+            }
+        };
+
+        EasyMock.replay(mMockDevice, mMockBuildInfo);
+        File res = mPreparer.getLocalConfigFile(mMockBuildHelper, mMockDevice);
+        assertEquals(check, res);
+        EasyMock.verify(mMockDevice, mMockBuildInfo);
+    }
+
+    /**
      * Test that when we look up resources locally, we search them from the build helper.
      */
     @Test
@@ -197,10 +258,13 @@
         Map<String, String> attributes = new HashMap<>();
         attributes.put(CompatibilityBuildHelper.SUITE_VERSION, "v1");
         EasyMock.expect(mMockBuildInfo.getBuildAttributes()).andStubReturn(attributes);
+        Collection<VersionedFile> versionedFiles = new LinkedList<VersionedFile>();
+        EasyMock.expect(mMockBuildInfo.getFiles()).andStubReturn(versionedFiles);
         Capture<File> capture = new Capture<>();
         mMockBuildInfo.setFile(EasyMock.contains("moduleName"), EasyMock.capture(capture),
                 EasyMock.eq("DYNAMIC_CONFIG_FILE:moduleName"));
 
+        mPreparer.setInvocationContext(mModuleContext);
         EasyMock.replay(mMockDevice, mMockBuildInfo);
         mPreparer.setUp(mMockDevice, mMockBuildInfo);
         EasyMock.verify(mMockDevice, mMockBuildInfo);
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/MediaPreparerTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/MediaPreparerTest.java
index 005ca43..3bfed47 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/MediaPreparerTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/MediaPreparerTest.java
@@ -16,6 +16,11 @@
 
 package com.android.compatibility.common.tradefed.targetprep;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import com.android.ddmlib.IDevice;
 import com.android.tradefed.build.BuildInfo;
 import com.android.tradefed.build.IBuildInfo;
@@ -23,54 +28,64 @@
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.targetprep.TargetSetupError;
 
-import junit.framework.TestCase;
-
 import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
-public class MediaPreparerTest extends TestCase {
+/** Unit tests for {@link MediaPreparer}. */
+@RunWith(JUnit4.class)
+public class MediaPreparerTest {
 
     private MediaPreparer mMediaPreparer;
     private IBuildInfo mMockBuildInfo;
     private ITestDevice mMockDevice;
     private OptionSetter mOptionSetter;
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
         mMediaPreparer = new MediaPreparer();
         mMockDevice = EasyMock.createMock(ITestDevice.class);
         mMockBuildInfo = new BuildInfo("0", "");
         mOptionSetter = new OptionSetter(mMediaPreparer);
     }
 
+    @Test
     public void testSetMountPoint() throws Exception {
         EasyMock.expect(mMockDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE)).andReturn(
                 "/sdcard").once();
         EasyMock.replay(mMockDevice);
         mMediaPreparer.setMountPoint(mMockDevice);
+        EasyMock.verify(mMockDevice);
         assertEquals(mMediaPreparer.mBaseDeviceShortDir, "/sdcard/test/bbb_short/");
         assertEquals(mMediaPreparer.mBaseDeviceFullDir, "/sdcard/test/bbb_full/");
     }
 
+    @Test
     public void testDefaultModuleDirMountPoint() throws Exception {
         EasyMock.expect(mMockDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE)).andReturn(
                 "/sdcard").once();
         EasyMock.replay(mMockDevice);
         mMediaPreparer.setMountPoint(mMockDevice);
+        EasyMock.verify(mMockDevice);
         assertEquals(mMediaPreparer.mBaseDeviceModuleDir, "/sdcard/test/android-cts-media/");
         assertEquals(mMediaPreparer.getMediaDir().getName(), "android-cts-media");
     }
 
+    @Test
     public void testSetModuleDirMountPoint() throws Exception {
         mOptionSetter.setOptionValue("media-folder-name", "unittest");
         EasyMock.expect(mMockDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE)).andReturn(
                 "/sdcard").once();
         EasyMock.replay(mMockDevice);
         mMediaPreparer.setMountPoint(mMockDevice);
+        EasyMock.verify(mMockDevice);
         assertEquals(mMediaPreparer.mBaseDeviceModuleDir, "/sdcard/test/unittest/");
         assertEquals(mMediaPreparer.getMediaDir().getName(), "unittest");
     }
 
+    @Test
     public void testCopyMediaFiles() throws Exception {
         mMediaPreparer.mMaxRes = MediaPreparer.DEFAULT_MAX_RESOLUTION;
         mMediaPreparer.mBaseDeviceShortDir = "/sdcard/test/bbb_short/";
@@ -78,6 +93,10 @@
         mMediaPreparer.mBaseDeviceImagesDir = "/sdcard/test/images";
         mMediaPreparer.mBaseDeviceModuleDir = "/sdcard/test/android-cts-media/";
         for (MediaPreparer.Resolution resolution : MediaPreparer.RESOLUTIONS) {
+            if (resolution.getWidth() > MediaPreparer.DEFAULT_MAX_RESOLUTION.getWidth()) {
+                // Stop when we reach the default max resolution
+                continue;
+            }
             String shortFile = String.format("%s%s", mMediaPreparer.mBaseDeviceShortDir,
                     resolution.toString());
             String fullFile = String.format("%s%s", mMediaPreparer.mBaseDeviceFullDir,
@@ -91,8 +110,10 @@
                 .andReturn(false).anyTimes();
         EasyMock.replay(mMockDevice);
         mMediaPreparer.copyMediaFiles(mMockDevice);
+        EasyMock.verify(mMockDevice);
     }
 
+    @Test
     public void testMediaFilesExistOnDeviceTrue() throws Exception {
         mMediaPreparer.mMaxRes = MediaPreparer.DEFAULT_MAX_RESOLUTION;
         mMediaPreparer.mBaseDeviceShortDir = "/sdcard/test/bbb_short/";
@@ -110,8 +131,10 @@
                 .andReturn(true).anyTimes();
         EasyMock.replay(mMockDevice);
         assertTrue(mMediaPreparer.mediaFilesExistOnDevice(mMockDevice));
+        EasyMock.verify(mMockDevice);
     }
 
+    @Test
     public void testMediaFilesExistOnDeviceTrueWithPushAll() throws Exception {
         mOptionSetter.setOptionValue("push-all", "true");
         mMediaPreparer.mBaseDeviceModuleDir = "/sdcard/test/android-cts-media/";
@@ -119,8 +142,10 @@
                 .andReturn(true).anyTimes();
         EasyMock.replay(mMockDevice);
         assertTrue(mMediaPreparer.mediaFilesExistOnDevice(mMockDevice));
+        EasyMock.verify(mMockDevice);
     }
 
+    @Test
     public void testMediaFilesExistOnDeviceFalse() throws Exception {
         mMediaPreparer.mMaxRes = MediaPreparer.DEFAULT_MAX_RESOLUTION;
         mMediaPreparer.mBaseDeviceShortDir = "/sdcard/test/bbb_short/";
@@ -128,14 +153,18 @@
         EasyMock.expect(mMockDevice.doesFileExist(firstFileChecked)).andReturn(false).once();
         EasyMock.replay(mMockDevice);
         assertFalse(mMediaPreparer.mediaFilesExistOnDevice(mMockDevice));
+        EasyMock.verify(mMockDevice);
     }
 
+    @Test
     public void testSkipMediaDownload() throws Exception {
         mOptionSetter.setOptionValue("skip-media-download", "true");
-        EasyMock.replay();
-        mMediaPreparer.run(mMockDevice, mMockBuildInfo);
+        EasyMock.replay(mMockDevice);
+        mMediaPreparer.setUp(mMockDevice, mMockBuildInfo);
+        EasyMock.verify(mMockDevice);
     }
 
+    @Test
     public void testPushAll() throws Exception {
         mOptionSetter.setOptionValue("push-all", "true");
         mOptionSetter.setOptionValue("media-folder-name", "unittest");
@@ -153,18 +182,52 @@
                 .andReturn(false).anyTimes();
         EasyMock.replay(mMockDevice);
         mMediaPreparer.copyMediaFiles(mMockDevice);
+        EasyMock.verify(mMockDevice);
     }
 
+    @Test
     public void testWithBothPushAllAndImagesOnly() throws Exception {
         mOptionSetter.setOptionValue("push-all", "true");
         mOptionSetter.setOptionValue("images-only", "true");
+
+        EasyMock.expect(mMockDevice.getDeviceDescriptor()).andReturn(null);
+
         EasyMock.replay(mMockDevice);
         try {
-            mMediaPreparer.run(mMockDevice, mMockBuildInfo);
+            mMediaPreparer.setUp(mMockDevice, mMockBuildInfo);
             fail("TargetSetupError expected");
         } catch (TargetSetupError e) {
             // Expected
         }
+        EasyMock.verify(mMockDevice);
     }
 
+    /** Test that if we decide to run and files are on the device, we don't download again. */
+    @Test
+    public void testMediaDownloadOnly_existsOnDevice() throws Exception {
+        mOptionSetter.setOptionValue("local-media-path", "/fake/media/dir");
+        mMediaPreparer.mBaseDeviceShortDir = "/sdcard/test/bbb_short/";
+        mMediaPreparer.mBaseDeviceFullDir = "/sdcard/test/bbb_full/";
+
+        EasyMock.expect(mMockDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE))
+                .andReturn("/sdcard")
+                .once();
+        for (MediaPreparer.Resolution resolution : MediaPreparer.RESOLUTIONS) {
+            if (resolution.getWidth() > MediaPreparer.DEFAULT_MAX_RESOLUTION.getWidth()) {
+                // Stop when we reach the default max resolution
+                continue;
+            }
+            String shortFile =
+                    String.format(
+                            "%s%s", mMediaPreparer.mBaseDeviceShortDir, resolution.toString());
+            String fullFile =
+                    String.format("%s%s", mMediaPreparer.mBaseDeviceFullDir, resolution.toString());
+            EasyMock.expect(mMockDevice.doesFileExist(shortFile)).andReturn(true).once();
+            EasyMock.expect(mMockDevice.doesFileExist(fullFile)).andReturn(true).once();
+        }
+        EasyMock.expect(mMockDevice.doesFileExist("/sdcard/test/images/")).andReturn(true).once();
+        EasyMock.replay(mMockDevice);
+        mMediaPreparer.setUp(mMockDevice, mMockBuildInfo);
+        EasyMock.verify(mMockDevice);
+    }
 }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/PropertyCheckTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/PropertyCheckTest.java
index 617fde6..9b34646 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/PropertyCheckTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/PropertyCheckTest.java
@@ -42,7 +42,7 @@
         super.setUp();
         mPropertyCheck = new PropertyCheck();
         mMockDevice = EasyMock.createMock(ITestDevice.class);
-        mMockBuildInfo = new DeviceBuildInfo("0", "", "");
+        mMockBuildInfo = new DeviceBuildInfo("0", "");
         mOptionSetter = new OptionSetter(mPropertyCheck);
         EasyMock.expect(mMockDevice.getProperty(PROPERTY)).andReturn(ACTUAL_VALUE).anyTimes();
         EasyMock.expect(mMockDevice.getDeviceDescriptor()).andReturn(null).anyTimes();
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/SettingsPreparerTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/SettingsPreparerTest.java
index 7fbb39e..99f2601 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/SettingsPreparerTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/SettingsPreparerTest.java
@@ -46,7 +46,7 @@
         mSettingsPreparer = new SettingsPreparer();
         mMockDevice = EasyMock.createMock(ITestDevice.class);
         EasyMock.expect(mMockDevice.getDeviceDescriptor()).andReturn(null).anyTimes();
-        mMockBuildInfo = new BuildInfo("0", "", "");
+        mMockBuildInfo = new BuildInfo("0", "");
         mOptionSetter = new OptionSetter(mSettingsPreparer);
         mOptionSetter.setOptionValue("device-setting", "stay_on_while_plugged_in");
         mOptionSetter.setOptionValue("setting-type", "global");
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java
index 9c44b65..ec12810 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java
@@ -19,10 +19,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import com.android.tradefed.build.BuildInfo;
 import com.android.tradefed.build.DeviceBuildInfo;
 import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
 import com.android.tradefed.result.ITestInvocationListener;
@@ -59,7 +57,6 @@
 public class JarHostTestTest {
 
     private static final String TEST_JAR1 = "/testtype/testJar1.jar";
-    private static final String TEST_JAR2 = "/testtype/testJar2.jar";
     private JarHostTest mTest;
     private DeviceBuildInfo mStubBuildInfo;
     private File mTestDir = null;
@@ -172,69 +169,6 @@
     }
 
     /**
-     * Test that {@link JarHostTest#getTestShard(int, int)} can split classes coming from a jar.
-     */
-    @Test
-    public void testGetTestShard_withJar() throws Exception {
-        File testJar = getJarResource(TEST_JAR2, mTestDir);
-        mTest = new JarHostTestLoader(mTestDir, testJar);
-        mTest.setBuild(mStubBuildInfo);
-        ITestDevice device = EasyMock.createNiceMock(ITestDevice.class);
-        mTest.setDevice(device);
-        OptionSetter setter = new OptionSetter(mTest);
-        setter.setOptionValue("enable-pretty-logs", "false");
-        setter.setOptionValue("jar", testJar.getName());
-        // full class count without sharding
-        assertEquals(238, mTest.countTestCases());
-
-        // only one shard
-        IRemoteTest oneShard = mTest.getTestShard(1, 0);
-        assertTrue(oneShard instanceof JarHostTest);
-        ((JarHostTest)oneShard).setBuild(new BuildInfo());
-        ((JarHostTest)oneShard).setDevice(device);
-        assertEquals(238, ((JarHostTest)oneShard).countTestCases());
-
-        // 5 shards total the number of tests.
-        int total = 0;
-        IRemoteTest shard1 = mTest.getTestShard(5, 0);
-        assertTrue(shard1 instanceof JarHostTest);
-        ((JarHostTest)shard1).setBuild(new BuildInfo());
-        ((JarHostTest)shard1).setDevice(device);
-        assertEquals(58, ((JarHostTest)shard1).countTestCases());
-        total += ((JarHostTest)shard1).countTestCases();
-
-        IRemoteTest shard2 = mTest.getTestShard(5, 1);
-        assertTrue(shard2 instanceof JarHostTest);
-        ((JarHostTest)shard2).setBuild(new BuildInfo());
-        ((JarHostTest)shard2).setDevice(device);
-        assertEquals(60, ((JarHostTest)shard2).countTestCases());
-        total += ((JarHostTest)shard2).countTestCases();
-
-        IRemoteTest shard3 = mTest.getTestShard(5, 2);
-        assertTrue(shard3 instanceof JarHostTest);
-        ((JarHostTest)shard3).setBuild(new BuildInfo());
-        ((JarHostTest)shard3).setDevice(device);
-        assertEquals(60, ((JarHostTest)shard3).countTestCases());
-        total += ((JarHostTest)shard3).countTestCases();
-
-        IRemoteTest shard4 = mTest.getTestShard(5, 3);
-        assertTrue(shard4 instanceof JarHostTest);
-        ((JarHostTest)shard4).setBuild(new BuildInfo());
-        ((JarHostTest)shard4).setDevice(device);
-        assertEquals(30, ((JarHostTest)shard4).countTestCases());
-        total += ((JarHostTest)shard4).countTestCases();
-
-        IRemoteTest shard5 = mTest.getTestShard(5, 4);
-        assertTrue(shard5 instanceof JarHostTest);
-        ((JarHostTest)shard5).setBuild(new BuildInfo());
-        ((JarHostTest)shard5).setDevice(device);
-        assertEquals(30, ((JarHostTest)shard5).countTestCases());
-        total += ((JarHostTest)shard5).countTestCases();
-
-        assertEquals(238, total);
-    }
-
-    /**
      * Testable version of {@link JarHostTest} that allows adding jar to classpath for testing
      * purpose.
      */
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java
index 4a8e741..d3cbaa2 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java
@@ -36,6 +36,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
@@ -88,6 +89,16 @@
         }
 
         @Override
+        public void clearIncludeFilters() {
+            mIncludeFilters.clear();
+        }
+
+        @Override
+        public Set<String> getIncludeFilters() {
+            return new HashSet<>(mIncludeFilters);
+        }
+
+        @Override
         public void addExcludeFilter(String filter) {
             mExcludeFilters.add(filter);
         }
@@ -98,6 +109,16 @@
         }
 
         @Override
+        public void clearExcludeFilters() {
+            mExcludeFilters.clear();
+        }
+
+        @Override
+        public Set<String> getExcludeFilters() {
+            return new HashSet<>(mExcludeFilters);
+        }
+
+        @Override
         public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
             // Do nothing
         }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
index be246ae..eddda50 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
@@ -31,7 +31,6 @@
 import com.android.tradefed.testtype.IAbiReceiver;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.IRuntimeHintProvider;
-import com.android.tradefed.testtype.IStrictShardableTest;
 import com.android.tradefed.testtype.ITestCollector;
 import com.android.tradefed.testtype.ITestFilterReceiver;
 import com.android.tradefed.util.AbiUtils;
@@ -395,31 +394,6 @@
         assertEquals("Incorrect module arg", "foobar", stub.mBlah);
     }
 
-    public void testSplit() throws Exception {
-        createConfig(mTestsDir, "sharded_1", null, SHARDABLE_TEST_STUB);
-        createConfig(mTestsDir, "sharded_2", null, SHARDABLE_TEST_STUB);
-        createConfig(mTestsDir, "sharded_3", null, SHARDABLE_TEST_STUB);
-        Set<IAbi> abis = new HashSet<>();
-        abis.add(new Abi(ABI_64, "64"));
-        ArrayList<String> emptyList = new ArrayList<>();
-
-        mRepo.initialize(3, 0, mTestsDir, abis, DEVICE_TOKENS, emptyList, emptyList, INCLUDES,
-                         EXCLUDES, METADATA_INCLUDES, METADATA_EXCLUDES, mMockBuildInfo);
-
-        List<IModuleDef> modules = new ArrayList<>();
-        modules.addAll(mRepo.getNonTokenModules());
-        modules.addAll(mRepo.getTokenModules());
-
-        int shardableCount = 0;
-        for (IModuleDef def : modules) {
-            IRemoteTest test = def.getTest();
-            if (test instanceof IStrictShardableTest) {
-                shardableCount++;
-            }
-        }
-        assertEquals("Shards wrong", 9, shardableCount);
-    }
-
     public void testGetModuleIds() {
         mRepo.initialize(3, null, mTestsDir, ABIS, DEVICE_TOKENS, TEST_ARGS, MODULE_ARGS, INCLUDES,
                 EXCLUDES, METADATA_INCLUDES, METADATA_EXCLUDES, mMockBuildInfo);
@@ -459,6 +433,18 @@
         public void setAbi(IAbi arg0) {}
         @Override
         public IAbi getAbi() {return null;}
+        @Override
+        public Set<String> getIncludeFilters() {
+            return null;
+        }
+        @Override
+        public Set<String> getExcludeFilters() {
+            return null;
+        }
+        @Override
+        public void clearIncludeFilters() {}
+        @Override
+        public void clearExcludeFilters() {}
     }
 
     /**
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ShardableTestStub.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ShardableTestStub.java
index 7b43972..293d0d2 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ShardableTestStub.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ShardableTestStub.java
@@ -25,7 +25,6 @@
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.IRuntimeHintProvider;
 import com.android.tradefed.testtype.IShardableTest;
-import com.android.tradefed.testtype.IStrictShardableTest;
 import com.android.tradefed.testtype.ITestCollector;
 import com.android.tradefed.testtype.ITestFilterReceiver;
 
@@ -34,8 +33,7 @@
 import java.util.Set;
 
 public class ShardableTestStub implements IRemoteTest, IShardableTest, IBuildReceiver,
-        IAbiReceiver, IRuntimeHintProvider, ITestCollector, ITestFilterReceiver,
-        IStrictShardableTest {
+        IAbiReceiver, IRuntimeHintProvider, ITestCollector, ITestFilterReceiver {
 
     @Option(name = "module")
     String mModule;
@@ -76,14 +74,6 @@
         return mShards;
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public IRemoteTest getTestShard(int shardCount, int shardIndex) {
-        return new ShardableTestStub();
-    }
-
     @Override
     public void setAbi(IAbi abi) {
         // Do nothing
@@ -123,4 +113,20 @@
     public void addAllExcludeFilters(Set<String> filters) {
 
     }
+
+    @Override
+    public Set<String> getIncludeFilters() {
+        return null;
+    }
+
+    @Override
+    public Set<String> getExcludeFilters() {
+        return null;
+    }
+
+    @Override
+    public void clearIncludeFilters() {}
+
+    @Override
+    public void clearExcludeFilters() {}
 }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/SimpleTestStub.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/SimpleTestStub.java
index 7a62c8f..effea21 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/SimpleTestStub.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/SimpleTestStub.java
@@ -93,4 +93,20 @@
     public void addAllExcludeFilters(Set<String> filters) {
 
     }
+
+    @Override
+    public Set<String> getIncludeFilters() {
+        return null;
+    }
+
+    @Override
+    public Set<String> getExcludeFilters() {
+        return null;
+    }
+
+    @Override
+    public void clearIncludeFilters() {}
+
+    @Override
+    public void clearExcludeFilters() {}
 }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStub.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStub.java
index 0ba3a4c..f04a1be 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStub.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStub.java
@@ -166,4 +166,20 @@
     public void addAllExcludeFilters(Set<String> filters) {
 
     }
+
+    @Override
+    public Set<String> getIncludeFilters() {
+        return null;
+    }
+
+    @Override
+    public Set<String> getExcludeFilters() {
+        return null;
+    }
+
+    @Override
+    public void clearIncludeFilters() {}
+
+    @Override
+    public void clearExcludeFilters() {}
 }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStubShardable.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStubShardable.java
deleted file mode 100644
index d3d7b41..0000000
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStubShardable.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.tradefed.testtype;
-
-import com.android.tradefed.config.OptionCopier;
-import com.android.tradefed.result.TestDescription;
-import com.android.tradefed.testtype.IRemoteTest;
-import com.android.tradefed.testtype.IStrictShardableTest;
-
-import java.util.ArrayList;
-
-/**
- * A test Stub that can be used to fake some runs.
- */
-public class TestStubShardable extends TestStub implements IStrictShardableTest {
-
-    @Override
-    public IRemoteTest getTestShard(int shardCount, int shardIndex) {
-        TestStubShardable test = new TestStubShardable();
-        OptionCopier.copyOptionsNoThrow(this, test);
-        test.mShardedTestToRun = new ArrayList<>();
-        TestDescription tid = new TestDescription("TestStub", "test" + shardIndex);
-        test.mShardedTestToRun.add(tid);
-        if (mIsComplete == false) {
-            TestDescription tid2 = new TestDescription("TestStub", "test" + shardIndex + 100);
-            test.mShardedTestToRun.add(tid2);
-            test.mIsComplete = false;
-        }
-        test.mShardIndex = shardIndex;
-        return test;
-    }
-}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTestTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTestTest.java
index 39e24bd..3c3e264 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTestTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTestTest.java
@@ -187,9 +187,10 @@
         mFactory.setInvocationContext(mMockContext);
 
         mMockListener.testModuleStarted(EasyMock.anyObject());
-        mMockListener.testRunStarted("module1", 0);
-        mMockListener.testRunEnded(EasyMock.anyLong(),
-                (HashMap<String, Metric>) EasyMock.anyObject());
+        mMockListener.testRunStarted(
+                EasyMock.eq("module1"), EasyMock.eq(0), EasyMock.eq(0), EasyMock.anyLong());
+        mMockListener.testRunEnded(
+                EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
         mMockListener.testModuleEnded();
 
         EasyMock.replay(mMockListener, mMockInfo, mMockDevice);
diff --git a/common/host-side/util/.classpath b/common/host-side/util/.classpath
index 28bdded..3cf19b6 100644
--- a/common/host-side/util/.classpath
+++ b/common/host-side/util/.classpath
@@ -10,8 +10,8 @@
 	<classpathentry combineaccessrules="false" kind="src" path="/tradefederation"/>
 	<classpathentry kind="var" path="TRADEFED_ROOT/out/host/common/obj/JAVA_LIBRARIES/json-prebuilt_intermediates/javalib.jar"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/cts-common-util"/>
-	<classpathentry kind="var" path="TRADEFED_ROOT/out/host/common/obj/JAVA_LIBRARIES/jsonlib_intermediates/classes.jar"/>
-	<classpathentry kind="var" path="TRADEFED_ROOT/external/mockito/lib/byte-buddy-1.7.9.jar"/>
-	<classpathentry kind="var" path="TRADEFED_ROOT/external/mockito/lib/byte-buddy-agent-1.7.9.jar"/>
+	<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/external/mockito/mockito-byte-buddy-agent/linux_glibc_common/combined/mockito-byte-buddy-agent.jar"/>
+	<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/external/mockito/mockito-byte-buddy/linux_glibc_common/combined/mockito-byte-buddy.jar"/>
+	<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/cts/libs/json/json/linux_glibc_common/javac/json.jar"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/common/host-side/util/Android.bp b/common/host-side/util/Android.bp
new file mode 100644
index 0000000..ae7db70
--- /dev/null
+++ b/common/host-side/util/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2015 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.
+
+java_library_host {
+    name: "compatibility-host-util",
+    defaults: ["cts_error_prone_rules"],
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "compatibility-common-util-hostsidelib",
+        "json",
+    ],
+
+    libs: [
+        "json-prebuilt",
+        "tradefed",
+    ],
+}
diff --git a/common/host-side/util/Android.mk b/common/host-side/util/Android.mk
deleted file mode 100644
index 77a5b5d..0000000
--- a/common/host-side/util/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-common-util-hostsidelib jsonlib
-
-LOCAL_COMPATIBILITY_SUITE := general-tests
-
-LOCAL_JAVA_LIBRARIES := json-prebuilt tradefed
-
-LOCAL_MODULE := compatibility-host-util
-
-LOCAL_MODULE_TAGS := optional
-include cts/error_prone_rules.mk
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/BackupHostSideUtils.java b/common/host-side/util/src/com/android/compatibility/common/util/BackupHostSideUtils.java
new file mode 100644
index 0000000..37679c4
--- /dev/null
+++ b/common/host-side/util/src/com/android/compatibility/common/util/BackupHostSideUtils.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 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.compatibility.common.util;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.INativeDevice;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+/** Host-side specific utilities for backup and restore tests. */
+public class BackupHostSideUtils {
+    /** Create a new {@link BackupUtils} instance. */
+    public static BackupUtils createBackupUtils(INativeDevice device) {
+        return new BackupUtils() {
+            @Override
+            protected InputStream executeShellCommand(String command) throws IOException {
+                try {
+                    String result = device.executeShellCommand(command);
+                    return new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8));
+                } catch (DeviceNotAvailableException e) {
+                    throw new IOException(e);
+                }
+            }
+        };
+    }
+}
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/CpuFeatures.java b/common/host-side/util/src/com/android/compatibility/common/util/CpuFeatures.java
index 123a320..896381a 100644
--- a/common/host-side/util/src/com/android/compatibility/common/util/CpuFeatures.java
+++ b/common/host-side/util/src/com/android/compatibility/common/util/CpuFeatures.java
@@ -69,6 +69,6 @@
         int deviceMajor = Integer.parseInt(kernelVersion[0]);
         int deviceMinor = Integer.parseInt(kernelVersion[1]);
 
-        return (major < deviceMajor) || ((major == deviceMajor) && (minor < deviceMinor));
+        return (major > deviceMajor) || ((major == deviceMajor) && (minor > deviceMinor));
     }
 }
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/DeviceInfo.java b/common/host-side/util/src/com/android/compatibility/common/util/DeviceInfo.java
index da040cb..79008ee 100644
--- a/common/host-side/util/src/com/android/compatibility/common/util/DeviceInfo.java
+++ b/common/host-side/util/src/com/android/compatibility/common/util/DeviceInfo.java
@@ -17,7 +17,6 @@
 
 import static org.junit.Assert.fail;
 
-import com.android.compatibility.common.util.HostInfoStore;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.FileInputStreamSource;
 import com.android.tradefed.result.LogDataType;
@@ -55,9 +54,8 @@
         FileInputStreamSource source = null;
         try {
             jsonFile = FileUtil.createTempFile(getClass().getSimpleName(), FILE_SUFFIX);
-            try (HostInfoStore store = new HostInfoStore(jsonFile)) {
-                store.open();
-                collectDeviceInfo(store);
+            try {
+                collectDeviceInfo(jsonFile);
             } finally {
                 // If file is empty throw exception so it is not copied to the results.
                 if (jsonFile != null && jsonFile.exists() &&
@@ -81,4 +79,15 @@
      * Method to collect device information.
      */
     protected abstract void collectDeviceInfo(HostInfoStore store) throws Exception;
+
+    /**
+     * Method to collect device information; this method should write JSON to the specified file
+     * directly.
+     */
+    protected void collectDeviceInfo(File jsonFile) throws Exception {
+        try (HostInfoStore store = new HostInfoStore(jsonFile)) {
+            store.open();
+            collectDeviceInfo(store);
+        }
+    }
 }
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/HostSideTestUtils.java b/common/host-side/util/src/com/android/compatibility/common/util/HostSideTestUtils.java
new file mode 100644
index 0000000..248768b
--- /dev/null
+++ b/common/host-side/util/src/com/android/compatibility/common/util/HostSideTestUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 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.compatibility.common.util;
+
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.util.RunUtil;
+
+/** Utility class for host-side tests. */
+public class HostSideTestUtils {
+    @FunctionalInterface
+    public interface BooleanSupplierWithThrow<E extends Throwable> {
+        boolean getAsBoolean() throws E;
+    }
+
+    /** Wait until {@code predicate} is satisfied, or fail, with a given timeout. */
+    public static <E extends Throwable> void waitUntil(
+            String message, long timeoutSeconds, BooleanSupplierWithThrow<E> predicate) throws E {
+        int sleep = 125;
+        final long timeout = System.currentTimeMillis() + timeoutSeconds * 1000;
+        while (System.currentTimeMillis() < timeout) {
+            if (predicate.getAsBoolean()) {
+                return;
+            }
+            RunUtil.getDefault().sleep(sleep);
+        }
+        fail(message);
+    }
+}
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/MonitoringUtils.java b/common/host-side/util/src/com/android/compatibility/common/util/MonitoringUtils.java
index ac16ac0..0c49ee6 100644
--- a/common/host-side/util/src/com/android/compatibility/common/util/MonitoringUtils.java
+++ b/common/host-side/util/src/com/android/compatibility/common/util/MonitoringUtils.java
@@ -21,8 +21,6 @@
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.util.RunUtil;
 
 /**
@@ -38,11 +36,13 @@
         long start = System.currentTimeMillis();
         while (System.currentTimeMillis() - start < CONNECTIVITY_CHECK_TIME_MS) {
             if (device.checkConnectivity()) {
-                CLog.i("Connectivity: passed check.");
+                CLog.i("Wifi Connectivity: passed check.");
                 return true;
             } else {
-                CLog.logAndDisplay(LogLevel.INFO,
-                        "Connectivity check failed on %s, retrying in %dms",
+                CLog.logAndDisplay(
+                        LogLevel.INFO,
+                        "Wifi Connectivity check failed on %s. (Is your device connected to Wifi?)"
+                                + ", retrying in %dms",
                         device.getSerialNumber(),
                         CONNECTIVITY_CHECK_INTERVAL_MS);
                 RunUtil.getDefault().sleep(CONNECTIVITY_CHECK_INTERVAL_MS);
@@ -54,11 +54,8 @@
     public static void checkDeviceConnectivity(ITestDevice device, ITestInvocationListener listener,
             String tag) throws DeviceNotAvailableException {
         if (!checkDeviceConnectivity(device)) {
-            CLog.w("Connectivity: check failed.");
-            InputStreamSource bugSource = device.getBugreport();
-            listener.testLog(String.format("bugreport-connectivity-%s", tag),
-                    LogDataType.TEXT, bugSource);
-            bugSource.cancel();
+            CLog.w("Wifi Connectivity: check failed. (Is your device connected to Wifi?)");
+            device.logBugreport(String.format("bugreport-connectivity-%s", tag), listener);
         }
     }
 }
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/PropertyUtil.java b/common/host-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
index 5c6cc71..1dd78aa 100644
--- a/common/host-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
+++ b/common/host-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
@@ -38,6 +38,10 @@
     private static final String BUILD_TYPE_PROPERTY = "ro.build.type";
     private static final String MANUFACTURER_PROPERTY = "ro.product.manufacturer";
     private static final String TAG_DEV_KEYS = "dev-keys";
+    private static final String VNDK_VERSION = "ro.vndk.version";
+
+    /** Value to be returned by getPropertyInt() if property is not found */
+    public static final int INT_VALUE_IF_UNSET = -1;
 
     public static final String GOOGLE_SETTINGS_QUERY =
             "content query --uri content://com.google.settings/partner";
@@ -75,6 +79,20 @@
     }
 
     /**
+     * Return whether the SDK version of the vendor partiton is newer than the given API level.
+     * If the property is set to non-integer value, this means the vendor partition is using
+     * current API level and true is returned.
+     */
+    public static boolean isVendorApiLevelNewerThan(ITestDevice device, int apiLevel)
+            throws DeviceNotAvailableException {
+        int vendorApiLevel = getPropertyInt(device, VNDK_VERSION);
+        if (vendorApiLevel == INT_VALUE_IF_UNSET) {
+            return true;
+        }
+        return vendorApiLevel > apiLevel;
+    }
+
+    /**
      * Return the manufacturer of this product. If unset, return null.
      */
     public static String getManufacturer(ITestDevice device) throws DeviceNotAvailableException {
@@ -129,4 +147,20 @@
         String value = device.getProperty(property);
         return (value == null) ? false : value.matches(regex);
     }
+
+    /**
+     * Retrieves the desired integer property, returning INT_VALUE_IF_UNSET if not found.
+     */
+    public static int getPropertyInt(ITestDevice device, String property)
+            throws DeviceNotAvailableException {
+        String value = device.getProperty(property);
+        if (value == null) {
+            return INT_VALUE_IF_UNSET;
+        }
+        try {
+            return Integer.parseInt(value);
+        } catch (NumberFormatException e) {
+            return INT_VALUE_IF_UNSET;
+        }
+    }
 }
diff --git a/common/host-side/util/tests/Android.bp b/common/host-side/util/tests/Android.bp
new file mode 100644
index 0000000..ad9725b
--- /dev/null
+++ b/common/host-side/util/tests/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2015 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.
+
+java_test_host {
+    name: "compatibility-host-util-tests",
+    defaults: ["cts_error_prone_rules"],
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "objenesis-host",
+        "mockito-host",
+    ],
+
+    libs: [
+        "compatibility-host-util",
+        "easymock",
+        "junit",
+        "json-prebuilt",
+        "tradefed",
+    ],
+}
diff --git a/common/host-side/util/tests/Android.mk b/common/host-side/util/tests/Android.mk
deleted file mode 100644
index 6074164..0000000
--- a/common/host-side/util/tests/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_STATIC_JAVA_LIBRARIES := objenesis-host mockito-host
-LOCAL_JAVA_LIBRARIES := \
-    compatibility-host-util \
-    easymock \
-    junit-host \
-    json-prebuilt \
-    tradefed
-
-LOCAL_MODULE := compatibility-host-util-tests
-
-LOCAL_MODULE_TAGS := optional
-include cts/error_prone_rules.mk
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/common/host-side/util/tests/run_tests.sh b/common/host-side/util/tests/run_tests.sh
deleted file mode 100755
index 183cd73..0000000
--- a/common/host-side/util/tests/run_tests.sh
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/bash
-
-# Copyright (C) 2015 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.
-
-# Helper script for running unit tests for compatibility libraries
-
-HARNESS_DIR=$(dirname ${0})/../../../..
-source ${HARNESS_DIR}/test_defs.sh
-
-JARS="
-    compatibility-common-util-hostsidelib\
-    compatibility-common-util-tests\
-    compatibility-host-util\
-    compatibility-host-util-tests\
-    compatibility-mock-tradefed\
-    compatibility-tradefed-tests"
-
-run_tests "com.android.compatibility.common.util.HostUnitTests" "${JARS}" "${@}"
-
diff --git a/common/util/.classpath b/common/util/.classpath
index 599ee5d..806795a 100644
--- a/common/util/.classpath
+++ b/common/util/.classpath
@@ -5,8 +5,8 @@
 	<classpathentry kind="src" path="tests/src"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
 	<classpathentry kind="var" path="TRADEFED_ROOT/out/host/common/obj/JAVA_LIBRARIES/json-prebuilt_intermediates/javalib.jar"/>
-	<classpathentry kind="var" path="TRADEFED_ROOT/out/host/common/obj/JAVA_LIBRARIES/guavalib_intermediates/classes.jar"/>
-	<classpathentry kind="var" path="TRADEFED_ROOT/out/host/common/obj/JAVA_LIBRARIES/kxml2-2.3.0_intermediates/javalib.jar"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/tradefederation"/>
+	<classpathentry kind="var" path="TRADEFED_ROOT/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar"/>
+	<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/external/guava/guava/linux_glibc_common/combined/guava.jar"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/common/util/Android.bp b/common/util/Android.bp
new file mode 100644
index 0000000..bc46cdb
--- /dev/null
+++ b/common/util/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2018 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.
+
+// Build the common utility library for use device-side
+java_library_static {
+    name: "compatibility-common-util-devicesidelib",
+    sdk_version: "current",
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "guava",
+        "junit",
+    ],
+}
+
+// Build the common utility library for use host-side
+java_library_host {
+    name: "compatibility-common-util-hostsidelib",
+    defaults: ["cts_error_prone_rules"],
+
+    srcs: ["src/**/*.java"],
+
+    libs: [
+        "junit",
+	"guava",
+	"json-prebuilt",
+	"platform-test-annotations",
+	"kxml2-2.3.0",
+    ],
+}
diff --git a/common/util/Android.mk b/common/util/Android.mk
deleted file mode 100644
index d5c5f5a..0000000
--- a/common/util/Android.mk
+++ /dev/null
@@ -1,55 +0,0 @@
-# Copyright (C) 2014 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.
-
-###############################################################################
-# Build the common utility library for use device-side
-###############################################################################
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE := compatibility-common-util-devicesidelib
-
-LOCAL_STATIC_JAVA_LIBRARIES := guava junit
-
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-###############################################################################
-# Build the common utility library for use host-side
-###############################################################################
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE := compatibility-common-util-hostsidelib
-
-LOCAL_STATIC_JAVA_LIBRARIES :=  guavalib \
-                                json-prebuilt \
-                                junit-host \
-                                kxml2-2.3.0 \
-                                platform-test-annotations-host
-include cts/error_prone_rules.mk
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/common/util/src/com/android/compatibility/common/util/BackupUtils.java b/common/util/src/com/android/compatibility/common/util/BackupUtils.java
new file mode 100644
index 0000000..e55c800
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/BackupUtils.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2018 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.compatibility.common.util;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Scanner;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility class for backup and restore.
+ */
+public abstract class BackupUtils {
+    private static final String LOCAL_TRANSPORT_NAME = "com.android.localtransport/.LocalTransport";
+    private static final String LOCAL_TRANSPORT_NAME_PRE_Q =
+            "android/com.android.internal.backup.LocalTransport";
+    private static final String LOCAL_TRANSPORT_PACKAGE = "com.android.localtransport";
+    public static final String LOCAL_TRANSPORT_TOKEN = "1";
+
+    private static final int BACKUP_PROVISIONING_TIMEOUT_SECONDS = 30;
+    private static final int BACKUP_PROVISIONING_POLL_INTERVAL_SECONDS = 1;
+    private static final int BACKUP_SERVICE_INIT_TIMEOUT_SECS = 30;
+
+    private static final Pattern BACKUP_MANAGER_CURRENTLY_ENABLE_STATUS_PATTERN =
+            Pattern.compile("^Backup Manager currently (enabled|disabled)$");
+    private static final String MATCH_LINE_BACKUP_MANAGER_IS_NOT_PENDING_INIT =
+            "(?s)" + "^Backup Manager is .* not pending init.*";  // DOTALL
+
+    private static final String BACKUP_DUMPSYS_CURRENT_TOKEN_FIELD = "Current:";
+
+    /**
+     * Kicks off adb shell {@param command} and return an {@link InputStream} with the command
+     * output stream.
+     */
+    protected abstract InputStream executeShellCommand(String command) throws IOException;
+
+    public void executeShellCommandSync(String command) throws IOException {
+        StreamUtil.drainAndClose(new InputStreamReader(executeShellCommand(command)));
+    }
+
+    public String getShellCommandOutput(String command) throws IOException {
+        return StreamUtil.readInputStream(executeShellCommand(command));
+    }
+
+    /** Executes shell command "bmgr backupnow <package>" and assert success. */
+    public void backupNowAndAssertSuccess(String packageName) throws IOException {
+        assertBackupIsSuccessful(packageName, backupNow(packageName));
+    }
+
+    /** Executes "bmgr --user <id> backupnow <package>" and assert success. */
+    public void backupNowAndAssertSuccessForUser(String packageName, int userId)
+            throws IOException {
+        assertBackupIsSuccessful(packageName, backupNowForUser(packageName, userId));
+    }
+
+    public void backupNowAndAssertBackupNotAllowed(String packageName) throws IOException {
+        assertBackupNotAllowed(packageName, getBackupNowOutput(packageName));
+    }
+
+    /** Executes shell command "bmgr backupnow <package>" and waits for completion. */
+    public void backupNowSync(String packageName) throws IOException {
+        StreamUtil.drainAndClose(new InputStreamReader(backupNow(packageName)));
+    }
+
+    public String getBackupNowOutput(String packageName) throws IOException {
+        return StreamUtil.readInputStream(backupNow(packageName));
+    }
+
+    /** Executes shell command "bmgr restore <token> <package>" and assert success. */
+    public void restoreAndAssertSuccess(String token, String packageName) throws IOException {
+        assertRestoreIsSuccessful(restore(token, packageName));
+    }
+
+    /** Executes shell command "bmgr --user <id> restore <token> <package>" and assert success. */
+    public void restoreAndAssertSuccessForUser(String token, String packageName, int userId)
+            throws IOException {
+        assertRestoreIsSuccessful(restoreForUser(token, packageName, userId));
+    }
+
+    public void restoreSync(String token, String packageName) throws IOException {
+        StreamUtil.drainAndClose(new InputStreamReader(restore(token, packageName)));
+    }
+
+    public String getRestoreOutput(String token, String packageName) throws IOException {
+        return StreamUtil.readInputStream(restore(token, packageName));
+    }
+
+    public boolean isLocalTransportSelected() throws IOException {
+        return getShellCommandOutput("bmgr list transports")
+                .contains("* " + getLocalTransportName());
+    }
+
+    /**
+     * Executes shell command "bmgr --user <id> list transports" to check the currently selected
+     * transport and returns {@code true} if the local transport is the selected one.
+     */
+    public boolean isLocalTransportSelectedForUser(int userId) throws IOException {
+        return getShellCommandOutput(String.format("bmgr --user %d list transports", userId))
+                .contains("* " + getLocalTransportName());
+    }
+
+    public boolean isBackupEnabled() throws IOException {
+        return getShellCommandOutput("bmgr enabled").contains("currently enabled");
+    }
+
+    /**
+     * Executes shell command "bmgr --user <id> enabled" and returns if backup is enabled for the
+     * user {@code userId}.
+     */
+    public boolean isBackupEnabledForUser(int userId) throws IOException {
+        return getShellCommandOutput(String.format("bmgr --user %d enabled", userId))
+                .contains("currently enabled");
+    }
+
+    public void wakeAndUnlockDevice() throws IOException {
+        executeShellCommandSync("input keyevent KEYCODE_WAKEUP");
+        executeShellCommandSync("wm dismiss-keyguard");
+    }
+
+    /**
+     * Returns {@link #LOCAL_TRANSPORT_NAME} if it's available on the device, or {@link
+     * #LOCAL_TRANSPORT_NAME_PRE_Q} otherwise.
+     */
+    public String getLocalTransportName() throws IOException {
+        return getShellCommandOutput("pm list packages").contains(LOCAL_TRANSPORT_PACKAGE)
+                ? LOCAL_TRANSPORT_NAME
+                : LOCAL_TRANSPORT_NAME_PRE_Q;
+    }
+
+    /** Executes "bmgr backupnow <package>" and returns an {@link InputStream} for its output. */
+    private InputStream backupNow(String packageName) throws IOException {
+        return executeShellCommand("bmgr backupnow " + packageName);
+    }
+
+    /**
+     * Executes "bmgr --user <id> backupnow <package>" and returns an {@link InputStream} for its
+     * output.
+     */
+    private InputStream backupNowForUser(String packageName, int userId) throws IOException {
+        return executeShellCommand(
+                String.format("bmgr --user %d backupnow %s", userId, packageName));
+    }
+
+    /**
+     * Parses the output of "bmgr backupnow" command and checks that {@code packageName} wasn't
+     * allowed to backup.
+     *
+     * Expected format: "Package <packageName> with result:  Backup is not allowed"
+     *
+     * TODO: Read input stream instead of string.
+     */
+    private void assertBackupNotAllowed(String packageName, String backupNowOutput) {
+        Scanner in = new Scanner(backupNowOutput);
+        boolean found = false;
+        while (in.hasNextLine()) {
+            String line = in.nextLine();
+
+            if (line.contains(packageName)) {
+                String result = line.split(":")[1].trim();
+                if ("Backup is not allowed".equals(result)) {
+                    found = true;
+                }
+            }
+        }
+        in.close();
+        assertTrue("Didn't find \'Backup not allowed\' in the output", found);
+    }
+
+    /**
+     * Parses the output of "bmgr backupnow" command checking that the package {@code packageName}
+     * was backed up successfully. Closes the input stream.
+     *
+     * Expected format: "Package <package> with result: Success"
+     */
+    private void assertBackupIsSuccessful(String packageName, InputStream backupNowOutput)
+            throws IOException {
+        BufferedReader reader =
+                new BufferedReader(new InputStreamReader(backupNowOutput, StandardCharsets.UTF_8));
+        try {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (line.contains(packageName)) {
+                    String result = line.split(":")[1].trim().toLowerCase();
+                    if ("success".equals(result)) {
+                        return;
+                    }
+                }
+            }
+            fail("Couldn't find package in output or backup wasn't successful");
+        } finally {
+            StreamUtil.drainAndClose(reader);
+        }
+    }
+
+    /**
+     * Executes "bmgr restore <token> <packageName>" and returns an {@link InputStream} for its
+     * output.
+     */
+    private InputStream restore(String token, String packageName) throws IOException {
+        return executeShellCommand(String.format("bmgr restore %s %s", token, packageName));
+    }
+
+    /**
+     * Executes "bmgr --user <id> restore <token> <packageName>" and returns an {@link InputStream}
+     * for its output.
+     */
+    private InputStream restoreForUser(String token, String packageName, int userId)
+            throws IOException {
+        return executeShellCommand(
+                String.format("bmgr --user %d restore %s %s", userId, token, packageName));
+    }
+
+    /**
+     * Parses the output of "bmgr restore" command and checks that the package under test
+     * was restored successfully. Closes the input stream.
+     *
+     * Expected format: "restoreFinished: 0"
+     */
+    private void assertRestoreIsSuccessful(InputStream restoreOutput) throws IOException {
+        BufferedReader reader =
+                new BufferedReader(new InputStreamReader(restoreOutput, StandardCharsets.UTF_8));
+        try {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (line.contains("restoreFinished: 0")) {
+                    return;
+                }
+            }
+            fail("Restore not successful");
+        } finally {
+            StreamUtil.drainAndClose(reader);
+        }
+    }
+
+    /** Executes "dumpsys backup" and returns an {@link InputStream} for its output. */
+    private InputStream dumpsysBackup() throws IOException {
+        return executeShellCommand("dumpsys backup");
+    }
+
+    /**
+     * Parses the output of "dumpsys backup" command to get token. Closes the input stream finally.
+     *
+     * Expected format: "Current: token"
+     */
+    private String getCurrentTokenOrFail(InputStream dumpsysOutput) throws IOException {
+        BufferedReader reader =
+                new BufferedReader(new InputStreamReader(dumpsysOutput, StandardCharsets.UTF_8));
+        try {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (line.contains(BACKUP_DUMPSYS_CURRENT_TOKEN_FIELD)) {
+                    return line.split(BACKUP_DUMPSYS_CURRENT_TOKEN_FIELD)[1].trim();
+                }
+            }
+            throw new AssertionError("Couldn't find token in output");
+        } finally {
+            StreamUtil.drainAndClose(reader);
+        }
+    }
+
+    /**
+     * Execute shell command and return output from this command.
+     */
+    public String executeShellCommandAndReturnOutput(String command) throws IOException {
+        InputStream in = executeShellCommand(command);
+        BufferedReader br = new BufferedReader(
+                new InputStreamReader(in, StandardCharsets.UTF_8));
+        String str;
+        StringBuilder out = new StringBuilder();
+        while ((str = br.readLine()) != null) {
+            out.append(str).append("\n");
+        }
+        return out.toString();
+    }
+
+    // Copied over from BackupQuotaTest
+    public boolean enableBackup(boolean enable) throws Exception {
+        boolean previouslyEnabled;
+        String output = getLineString(executeShellCommand("bmgr enabled"));
+        Matcher matcher = BACKUP_MANAGER_CURRENTLY_ENABLE_STATUS_PATTERN.matcher(output.trim());
+        if (matcher.find()) {
+            previouslyEnabled = "enabled".equals(matcher.group(1));
+        } else {
+            throw new RuntimeException("non-parsable output setting bmgr enabled: " + output);
+        }
+
+        executeShellCommand("bmgr enable " + enable);
+        return previouslyEnabled;
+    }
+
+    /**
+     * Execute shell command "bmgr --user <id> enable <enable> and return previous enabled state.
+     */
+    public boolean enableBackupForUser(boolean enable, int userId) throws IOException {
+        boolean previouslyEnabled = isBackupEnabledForUser(userId);
+        executeShellCommand(String.format("bmgr --user %d enable %b", userId, enable));
+        return previouslyEnabled;
+    }
+
+    /** Execute shell command "bmgr --user <id> activate <activate>." */
+    public void activateBackupForUser(boolean activate, int userId) throws IOException {
+        executeShellCommandSync(String.format("bmgr --user %d activate %b", userId, activate));
+    }
+
+    /**
+     * Executes shell command "bmgr --user <id> activated" and returns if backup is activated for
+     * the user {@code userId}.
+     */
+    public boolean isBackupActivatedForUser(int userId) throws IOException {
+        return getShellCommandOutput(String.format("bmgr --user %d activated", userId))
+                .contains("currently activated");
+    }
+
+    private String getLineString(InputStream inputStream) throws IOException {
+        BufferedReader reader =
+                new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+        String str;
+        try {
+            str = reader.readLine();
+        } finally {
+            StreamUtil.drainAndClose(reader);
+        }
+        return str;
+    }
+
+    public void waitForBackupInitialization() throws IOException {
+        long tryUntilNanos = System.nanoTime()
+                + TimeUnit.SECONDS.toNanos(BACKUP_PROVISIONING_TIMEOUT_SECONDS);
+        while (System.nanoTime() < tryUntilNanos) {
+            String output = getLineString(executeShellCommand("dumpsys backup"));
+            if (output.matches(MATCH_LINE_BACKUP_MANAGER_IS_NOT_PENDING_INIT)) {
+                return;
+            }
+            try {
+                Thread.sleep(TimeUnit.SECONDS.toMillis(BACKUP_PROVISIONING_POLL_INTERVAL_SECONDS));
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                break;
+            }
+        }
+        throw new IOException("Timed out waiting for backup initialization");
+    }
+
+    public void waitUntilBackupServiceIsRunning(int userId)
+            throws IOException, InterruptedException {
+        waitUntilBackupServiceIsRunning(userId, BACKUP_SERVICE_INIT_TIMEOUT_SECS);
+    }
+
+    @VisibleForTesting
+    void waitUntilBackupServiceIsRunning(int userId, int timeout)
+            throws IOException, InterruptedException {
+        CommonTestUtils.waitUntil(
+                "Backup Manager init timed out",
+                timeout,
+                () -> {
+                    String output = getLineString(executeShellCommand("dumpsys backup users"));
+                    return output.matches(
+                            "Backup Manager is running for users:.* " + userId + "( .*)?");
+                });
+    }
+
+    /**
+     * Executes shell command "bmgr --user <id> list transports" and returns {@code true} if the
+     * user has the {@code transport} available.
+     */
+    public boolean userHasBackupTransport(String transport, int userId) throws IOException {
+        String output =
+                getLineString(
+                        executeShellCommand(
+                                String.format("bmgr --user %d list transports", userId)));
+        for (String t : output.split("\n")) {
+            // Parse out the '*' character used to denote the selected transport.
+            t = t.replace("*", "").trim();
+            if (transport.equals(t)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Executes shell command "bmgr --user <id> transport <transport>" and returns the old
+     * transport.
+     */
+    public String setBackupTransportForUser(String transport, int userId) throws IOException {
+        String output =
+                executeShellCommandAndReturnOutput(
+                        String.format("bmgr --user %d transport %s", userId, transport));
+        Pattern pattern = Pattern.compile("\\(formerly (.*)\\)$");
+        Matcher matcher = pattern.matcher(output);
+        if (matcher.find()) {
+            return matcher.group(1);
+        } else {
+            throw new RuntimeException("Non-parsable output setting bmgr transport: " + output);
+        }
+    }
+}
+
diff --git a/common/util/src/com/android/compatibility/common/util/BusinessLogicFactory.java b/common/util/src/com/android/compatibility/common/util/BusinessLogicFactory.java
index a9df3cf..a89ae17 100644
--- a/common/util/src/com/android/compatibility/common/util/BusinessLogicFactory.java
+++ b/common/util/src/com/android/compatibility/common/util/BusinessLogicFactory.java
@@ -16,17 +16,21 @@
 
 package com.android.compatibility.common.util;
 
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
 import com.android.compatibility.common.util.BusinessLogic.BusinessLogicRule;
 import com.android.compatibility.common.util.BusinessLogic.BusinessLogicRuleAction;
 import com.android.compatibility.common.util.BusinessLogic.BusinessLogicRuleCondition;
 import com.android.compatibility.common.util.BusinessLogic.BusinessLogicRulesList;
 
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -67,16 +71,38 @@
     private static final String TIMESTAMP_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
 
     /**
+     * Create a BusinessLogic instance from a {@link FileInputStream} of business logic data,
+     * formatted in JSON. This format is identical to that which is received from the Android
+     * Partner business logic service.
+     */
+    public static BusinessLogic createFromFile(FileInputStream stream) {
+        try {
+            String businessLogicString = readStream(stream);
+            return createBL(businessLogicString);
+        } catch (IOException e) {
+            throw new RuntimeException("Business Logic failed", e);
+        }
+    }
+
+    /**
      * Create a BusinessLogic instance from a file of business logic data, formatted in JSON.
      * This format is identical to that which is received from the Android Partner business logic
      * service.
      */
     public static BusinessLogic createFromFile(File f) {
+        try {
+            String businessLogicString = readFile(f);
+            return createBL(businessLogicString);
+        } catch (IOException e) {
+            throw new RuntimeException("Business Logic failed", e);
+        }
+    }
+
+    private static BusinessLogic createBL(String businessLogicString) {
         // Populate the map from testname to business rules for this new BusinessLogic instance
         Map<String, List<BusinessLogicRulesList>> rulesMap = new HashMap<>();
         BusinessLogic bl = new BusinessLogic();
         try {
-            String businessLogicString = readFile(f);
             JSONObject root = new JSONObject(businessLogicString);
             JSONArray jsonRulesLists = null;
             if (root.has(AUTHENTICATION_STATUS)){
@@ -106,7 +132,7 @@
                 testRulesLists.add(extractRulesList(jsonRulesList));
                 rulesMap.put(testName, testRulesLists);
             }
-        } catch (IOException | JSONException e) {
+        } catch (JSONException e) {
             throw new RuntimeException("Business Logic failed", e);
         }
         // Return business logic
@@ -229,4 +255,16 @@
             return sb.toString();
         }
     }
+
+    /** Extract string from stream */
+    private static String readStream(FileInputStream stream) throws IOException {
+        int irChar = -1;
+        StringBuilder builder = new StringBuilder();
+        try (Reader ir = new BufferedReader(new InputStreamReader(stream))) {
+            while ((irChar = ir.read()) != -1) {
+                builder.append((char) irChar);
+            }
+        }
+        return builder.toString();
+    }
 }
diff --git a/common/util/src/com/android/compatibility/common/util/CommonTestUtils.java b/common/util/src/com/android/compatibility/common/util/CommonTestUtils.java
new file mode 100644
index 0000000..75fa17a
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/CommonTestUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 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.compatibility.common.util;
+
+import static org.junit.Assert.fail;
+
+// TODO(b/131736394): Remove duplication with HostSideTestUtils.
+/** Utility class for tests. */
+public class CommonTestUtils {
+    @FunctionalInterface
+    public interface BooleanSupplierWithThrow<E extends Throwable> {
+        boolean getAsBoolean() throws E;
+    }
+
+    /** Wait until {@code predicate} is satisfied, or fail, with a given timeout. */
+    public static <E extends Throwable> void waitUntil(
+            String message, long timeoutSeconds, BooleanSupplierWithThrow<E> predicate)
+            throws E, InterruptedException {
+        int sleep = 125;
+        final long timeout = System.currentTimeMillis() + timeoutSeconds * 1000;
+        while (System.currentTimeMillis() < timeout) {
+            if (predicate.getAsBoolean()) {
+                return;
+            }
+            Thread.sleep(sleep);
+        }
+        fail(message);
+    }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/CrashUtils.java b/common/util/src/com/android/compatibility/common/util/CrashUtils.java
new file mode 100644
index 0000000..e058df4
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/CrashUtils.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2019 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.compatibility.common.util;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/** Contains helper functions and shared constants for crash parsing. */
+public class CrashUtils {
+
+    public static final long MIN_CRASH_ADDR = 32768;
+    // Matches the end of a crash
+    public static final Pattern sEndofCrashPattern =
+            Pattern.compile(".*DEBUG\\s+:\\s+backtrace:.*");
+    public static final String DEVICE_PATH = "/data/local/tmp/CrashParserResults/";
+    public static final String LOCK_FILENAME = "lockFile.loc";
+    public static final String UPLOAD_REQUEST = "Please upload a result file to stagefright";
+    public static final Pattern sUploadRequestPattern =
+            Pattern.compile(".*" + UPLOAD_REQUEST + ".*");
+    public static final String NEW_TEST_ALERT = "New test starting with name: ";
+    public static final Pattern sNewTestPattern =
+            Pattern.compile(".*" + NEW_TEST_ALERT + "(\\w+)\\(.*\\).*");
+    public static final String SIGNAL = "signal",
+            NAME = "name",
+            PID = "pid",
+            TID = "tid",
+            FAULT_ADDRESS = "faultaddress";
+    // Matches the smallest blob that has the appropriate header and footer
+    private static final Pattern sCrashBlobPattern =
+            Pattern.compile("DEBUG\\s+:( [*]{3})+.*?DEBUG\\s+:\\s+backtrace:", Pattern.DOTALL);
+    // Matches process id and name line and captures them
+    private static final Pattern sPidtidNamePattern =
+            Pattern.compile("pid: (\\d+), tid: (\\d+), name: ([^\\s]+\\s+)*>>> (.*) <<<");
+    // Matches fault address and signal type line
+    private static final Pattern sFaultLinePattern =
+            Pattern.compile(
+                    "\\w+ \\d+ \\((.*)\\), code -*\\d+ \\(.*\\), fault addr "
+                            + "(?:0x(\\p{XDigit}+)|-+)");
+    // Matches the abort message line if it contains CHECK_
+    private static Pattern sAbortMessageCheckPattern =
+            Pattern.compile("(?i)Abort message.*CHECK_.*");
+
+    /**
+     * Determines if the given input has a {@link com.android.compatibility.common.util.Crash} that
+     * should fail an sts test
+     *
+     * @param processNames list of applicable process names
+     * @param checkMinAddr if the minimum fault address should be respected
+     * @param crashes list of crashes to check
+     * @return if a crash is serious enough to fail an sts test
+     */
+    public static boolean detectCrash(
+            String[] processNames, boolean checkMinAddr, JSONArray crashes) {
+        for (int i = 0; i < crashes.length(); i++) {
+            try {
+                JSONObject crash = crashes.getJSONObject(i);
+                if (!crash.getString(SIGNAL).toLowerCase().matches("sig(segv|bus)")) {
+                    continue;
+                }
+
+                if (checkMinAddr && !crash.isNull(FAULT_ADDRESS)) {
+                    if (crash.getLong(FAULT_ADDRESS) < MIN_CRASH_ADDR) {
+                        continue;
+                    }
+                }
+
+                boolean foundProcess = false;
+                String name = crash.getString(NAME);
+                for (String process : processNames) {
+                    if (name.equals(process)) {
+                        foundProcess = true;
+                        break;
+                    }
+                }
+
+                if (!foundProcess) {
+                    continue;
+                }
+
+                return true; // crash detected
+            } catch (JSONException | NullPointerException e) {
+            }
+        }
+
+        return false;
+    }
+
+    /** Adds all crashes found in the input as JSONObjects to the given JSONArray */
+    public static JSONArray addAllCrashes(String input, JSONArray crashes) {
+        Matcher crashBlobFinder = sCrashBlobPattern.matcher(input);
+        while (crashBlobFinder.find()) {
+            String crashStr = crashBlobFinder.group(0);
+            int tid = 0, pid = 0;
+            Long faultAddress = null;
+            String name = null, signal = null;
+
+            Matcher pidtidNameMatcher = sPidtidNamePattern.matcher(crashStr);
+            if (pidtidNameMatcher.find()) {
+                try {
+                    pid = Integer.parseInt(pidtidNameMatcher.group(1));
+                } catch (NumberFormatException e) {
+                }
+                try {
+                    tid = Integer.parseInt(pidtidNameMatcher.group(2));
+                } catch (NumberFormatException e) {
+                }
+                name = pidtidNameMatcher.group(3).trim();
+            }
+
+            Matcher faultLineMatcher = sFaultLinePattern.matcher(crashStr);
+            if (faultLineMatcher.find()) {
+                signal = faultLineMatcher.group(1);
+                String faultAddrMatch = faultLineMatcher.group(2);
+                if (faultAddrMatch != null) {
+                    try {
+                        faultAddress = Long.parseLong(faultAddrMatch, 16);
+                    } catch (NumberFormatException e) {
+                    }
+                }
+            }
+            if (!sAbortMessageCheckPattern.matcher(crashStr).find()) {
+                try {
+                    JSONObject crash = new JSONObject();
+                    crash.put(PID, pid);
+                    crash.put(TID, tid);
+                    crash.put(NAME, name);
+                    crash.put(FAULT_ADDRESS, faultAddress);
+                    crash.put(SIGNAL, signal);
+                    crashes.put(crash);
+                } catch (JSONException e) {
+
+                }
+            }
+        }
+        return crashes;
+    }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/DevicePropertyInfo.java b/common/util/src/com/android/compatibility/common/util/DevicePropertyInfo.java
index 04ab61e..7435839 100644
--- a/common/util/src/com/android/compatibility/common/util/DevicePropertyInfo.java
+++ b/common/util/src/com/android/compatibility/common/util/DevicePropertyInfo.java
@@ -43,6 +43,7 @@
     private final String mModel;
     private final String mProduct;
     private final String mReferenceFingerprint;
+    private final String mVendorFingerprint;
     private final String mSerial;
     private final String mTags;
     private final String mType;
@@ -52,11 +53,30 @@
     private final String mVersionSecurityPatch;
     private final String mVersionIncremental;
 
-    public DevicePropertyInfo(String abi, String abi2, String abis, String abis32, String abis64,
-            String board, String brand, String device, String fingerprint, String id,
-            String manufacturer, String model, String product, String referenceFigerprint,
-            String serial, String tags, String type, String versionBaseOs, String versionRelease,
-            String versionSdk, String versionSecurityPatch, String versionIncremental) {
+    public DevicePropertyInfo(
+            String abi,
+            String abi2,
+            String abis,
+            String abis32,
+            String abis64,
+            String board,
+            String brand,
+            String device,
+            String fingerprint,
+            String vendorFingerprint,
+            String id,
+            String manufacturer,
+            String model,
+            String product,
+            String referenceFingerprint,
+            String serial,
+            String tags,
+            String type,
+            String versionBaseOs,
+            String versionRelease,
+            String versionSdk,
+            String versionSecurityPatch,
+            String versionIncremental) {
         mAbi = abi;
         mAbi2 = abi2;
         mAbis = abis;
@@ -66,11 +86,12 @@
         mBrand = brand;
         mDevice = device;
         mFingerprint = fingerprint;
+        mVendorFingerprint = vendorFingerprint;
         mId = id;
         mManufacturer = manufacturer;
         mModel = model;
         mProduct = product;
-        mReferenceFingerprint = referenceFigerprint;
+        mReferenceFingerprint = referenceFingerprint;
         mSerial = serial;
         mTags = tags;
         mType = type;
@@ -98,6 +119,7 @@
         propertyMap.put(prefix + "brand", mBrand);
         propertyMap.put(prefix + "device", mDevice);
         propertyMap.put(prefix + "fingerprint", mFingerprint);
+        propertyMap.put(prefix + "vendor_fingerprint", mVendorFingerprint);
         propertyMap.put(prefix + "id", mId);
         propertyMap.put(prefix + "manufacturer", mManufacturer);
         propertyMap.put(prefix + "model", mModel);
diff --git a/common/util/src/com/android/compatibility/common/util/DynamicConfig.java b/common/util/src/com/android/compatibility/common/util/DynamicConfig.java
index 92bd380..797afea 100644
--- a/common/util/src/com/android/compatibility/common/util/DynamicConfig.java
+++ b/common/util/src/com/android/compatibility/common/util/DynamicConfig.java
@@ -55,6 +55,12 @@
         mDynamicConfigMap = createConfigMap(file);
     }
 
+    /** Init using directly a {@link FileInputStream} from the config file. */
+    public void initializeConfig(FileInputStream fileStream)
+            throws XmlPullParserException, IOException {
+        mDynamicConfigMap = createConfigMap(fileStream);
+    }
+
     public String getValue(String key) {
         assertRemoteConfigRequirementMet();
         List<String> singleValue = mDynamicConfigMap.get(key);
@@ -109,10 +115,17 @@
 
     public static Map<String, List<String>> createConfigMap(File file)
             throws XmlPullParserException, IOException {
+        try (FileInputStream stream = new FileInputStream(file)) {
+            return createConfigMap(stream);
+        }
+    }
+
+    public static Map<String, List<String>> createConfigMap(FileInputStream fileStream)
+            throws XmlPullParserException, IOException {
 
         Map<String, List<String>> dynamicConfigMap = new HashMap<String, List<String>>();
         XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
-        parser.setInput(new InputStreamReader(new FileInputStream(file)));
+        parser.setInput(new InputStreamReader(fileStream));
         parser.nextTag();
         parser.require(XmlPullParser.START_TAG, NS, CONFIG_TAG);
 
diff --git a/common/util/src/com/android/compatibility/common/util/InvocationResult.java b/common/util/src/com/android/compatibility/common/util/InvocationResult.java
index c11c27d..45780a9 100644
--- a/common/util/src/com/android/compatibility/common/util/InvocationResult.java
+++ b/common/util/src/com/android/compatibility/common/util/InvocationResult.java
@@ -37,7 +37,6 @@
     private String mBuildFingerprint;
     private String mTestPlan;
     private String mCommandLineArgs;
-    private int mNotExecuted = 0;
     private RetryChecksumStatus mRetryChecksumStatus = RetryChecksumStatus.NotRetry;
     private File mRetryDirectory = null;
 
diff --git a/common/util/src/com/android/compatibility/common/util/LogcatInspector.java b/common/util/src/com/android/compatibility/common/util/LogcatInspector.java
index ed82307..053db34 100644
--- a/common/util/src/com/android/compatibility/common/util/LogcatInspector.java
+++ b/common/util/src/com/android/compatibility/common/util/LogcatInspector.java
@@ -100,12 +100,11 @@
             InputStream logcatStream = executeShellCommand("logcat -v brief -d " + filterSpec);
             BufferedReader logcat = new BufferedReader(new InputStreamReader(logcatStream));
             String line;
-            stringIndex = 0;
             while ((line = logcat.readLine()) != null) {
                 if (line.contains(logcatStrings[stringIndex])) {
                     stringIndex++;
                     if (stringIndex >= logcatStrings.length) {
-                        drainAndClose(logcat);
+                        StreamUtil.drainAndClose(logcat);
                         return stringIndex;
                     }
                 }
@@ -117,14 +116,4 @@
         }
         return stringIndex;
     }
-
-    private static void drainAndClose(BufferedReader reader) {
-        try {
-            while (reader.read() >= 0) {
-                // do nothing.
-            }
-        } catch (IOException ignored) {
-        }
-        Closeables.closeQuietly(reader);
-    }
 }
diff --git a/common/util/src/com/android/compatibility/common/util/ModuleResult.java b/common/util/src/com/android/compatibility/common/util/ModuleResult.java
index cfc5385..abb22f8 100644
--- a/common/util/src/com/android/compatibility/common/util/ModuleResult.java
+++ b/common/util/src/com/android/compatibility/common/util/ModuleResult.java
@@ -53,6 +53,10 @@
      */
     @Override
     public boolean isDone() {
+        // If module is failed, it cannot be marked done.
+        if (isFailed()) {
+            return false;
+        }
         return mDone && !mInProgress && (mActualTestRuns >= mExpectedTestRuns);
     }
 
diff --git a/common/util/src/com/android/compatibility/common/util/ReadElf.java b/common/util/src/com/android/compatibility/common/util/ReadElf.java
index 44c89d6..b681c1c 100644
--- a/common/util/src/com/android/compatibility/common/util/ReadElf.java
+++ b/common/util/src/com/android/compatibility/common/util/ReadElf.java
@@ -566,6 +566,9 @@
     /** Rodata String List */
     private List<String> mRoStrings;
 
+    /** Rodata byte[] */
+    private byte[] mRoData;
+
     public static ReadElf read(File file) throws IOException {
         return new ReadElf(file);
     }
@@ -711,6 +714,7 @@
 
     private ReadElf(File file) throws IOException {
         mHasRodata = false;
+        mRoData = null;
         mPath = file.getPath();
         mFile = new RandomAccessFile(file, "r");
 
@@ -1302,11 +1306,8 @@
     public List<String> getRoStrings() throws IOException {
         if (mRoStrings == null) {
             mRoStrings = new ArrayList<>();
-            if (mHasRodata) {
-                byte[] byteArr = new byte[mRodataSize];
-                mFile.seek(mRodataOffset);
-                mFile.readFully(byteArr);
-
+            byte[] byteArr = getRoData();
+            if (byteArr != null) {
                 int strOffset = 0;
                 for (int i = 0; i < mRodataSize; i++) {
                     if (byteArr[i] == 0) {
@@ -1322,4 +1323,19 @@
         }
         return mRoStrings;
     }
+
+    /**
+     * Gets .rodata section
+     *
+     * @return byte [] of .rodata or null if there is none
+     */
+    public byte[] getRoData() throws IOException {
+        if (mHasRodata && mRoData == null) {
+            mRoData = new byte[mRodataSize];
+            mFile.seek(mRodataOffset);
+            mFile.readFully(mRoData);
+        }
+
+        return mRoData;
+    }
 }
diff --git a/common/util/src/com/android/compatibility/common/util/ResultHandler.java b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
index 2873b34..1be3624 100644
--- a/common/util/src/com/android/compatibility/common/util/ResultHandler.java
+++ b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
@@ -114,6 +114,7 @@
     private static final String SUITE_VERSION_ATTR = "suite_version";
     private static final String SUITE_BUILD_ATTR = "suite_build_number";
     private static final String SUMMARY_TAG = "Summary";
+    private static final String METRIC_TAG = "Metric";
     private static final String TEST_TAG = "Test";
 
     private static final String LATEST_RESULT_DIR = "latest";
@@ -264,6 +265,10 @@
                                 parser.require(XmlPullParser.END_TAG, NS, SCREENSHOT_TAG);
                             } else if (SUMMARY_TAG.equals(parser.getName())) {
                                 test.setReportLog(ReportLog.parse(parser));
+                            } else if (METRIC_TAG.equals(parser.getName())) {
+                                // Ignore the new format in the old parser.
+                                parser.nextText();
+                                parser.require(XmlPullParser.END_TAG, NS, METRIC_TAG);
                             } else {
                                 parser.nextTag();
                             }
@@ -403,13 +408,7 @@
             serializer.attribute(NS, NAME_ATTR, module.getName());
             serializer.attribute(NS, ABI_ATTR, module.getAbi());
             serializer.attribute(NS, RUNTIME_ATTR, String.valueOf(module.getRuntime()));
-
-            boolean done = module.isDone();
-            if (module.isFailed()) {
-                done = false;
-            }
-
-            serializer.attribute(NS, DONE_ATTR, Boolean.toString(done));
+            serializer.attribute(NS, DONE_ATTR, Boolean.toString(module.isDone()));
             serializer.attribute(NS, PASS_ATTR,
                     Integer.toString(module.countResults(TestStatus.PASS)));
             for (ICaseResult cr : module.getResults()) {
@@ -552,8 +551,9 @@
         }
         List<File> allResultDirs = getResultDirectories(resultsDir);
         if (sessionId >= allResultDirs.size()) {
-            throw new IllegalArgumentException(String.format("Invalid session id [%d], results" +
-                    "directory contains only %d results", sessionId, allResultDirs.size()));
+            throw new IllegalArgumentException(String.format("Invalid session id [%d], results " +
+                    "directory (%s) contains only %d results",
+                    sessionId, resultsDir.getAbsolutePath(), allResultDirs.size()));
         }
         return allResultDirs.get(sessionId);
     }
diff --git a/common/util/src/com/android/compatibility/common/util/StreamUtil.java b/common/util/src/com/android/compatibility/common/util/StreamUtil.java
index 4cef5c9..1909c21 100644
--- a/common/util/src/com/android/compatibility/common/util/StreamUtil.java
+++ b/common/util/src/com/android/compatibility/common/util/StreamUtil.java
@@ -16,9 +16,14 @@
 
 package com.android.compatibility.common.util;
 
+import com.google.common.io.Closeables;
+
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
 
 
 public class StreamUtil {
@@ -43,4 +48,25 @@
         }
     }
 
+    /**
+     * Reads {@code inputStream} converting it into a string. Does NOT close it.
+     *
+     * @throws IOException
+     */
+    public static String readInputStream(InputStream inputStream) throws IOException {
+        ByteArrayOutputStream result = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int length;
+        while ((length = inputStream.read(buffer)) != -1) {
+            result.write(buffer, 0, length);
+        }
+        return result.toString(StandardCharsets.UTF_8.name());
+    }
+
+    public static void drainAndClose(Reader reader) {
+        try {
+            while (reader.read() >= 0) {}
+        } catch (IOException ignored) {}
+        Closeables.closeQuietly(reader);
+    }
 }
diff --git a/common/util/tests/Android.bp b/common/util/tests/Android.bp
new file mode 100644
index 0000000..2943a79
--- /dev/null
+++ b/common/util/tests/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2015 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.
+
+java_test_host {
+    name: "compatibility-common-util-tests",
+    defaults: ["cts_error_prone_rules"],
+
+    srcs: ["src/**/*.java"],
+
+    libs: [
+        "junit",
+        "kxml2-2.3.0",
+        "tradefed",
+        "compatibility-common-util-hostsidelib",
+    ],
+
+    // Holds golden sample files in assets for validation
+    java_resource_dirs: ["assets/"],
+}
diff --git a/common/util/tests/Android.mk b/common/util/tests/Android.mk
deleted file mode 100644
index d89db6c..0000000
--- a/common/util/tests/Android.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright (C) 2015 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_LIBRARIES := junit-host kxml2-2.3.0 tradefed compatibility-common-util-hostsidelib
-
-LOCAL_MODULE := compatibility-common-util-tests
-
-# Holds golden sample files in assets for validation
-LOCAL_JAVA_RESOURCE_DIRS := assets/
-
-LOCAL_MODULE_TAGS := optional
-include cts/error_prone_rules.mk
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/common/util/tests/assets/logcat.txt b/common/util/tests/assets/logcat.txt
new file mode 100644
index 0000000..ad778c7
--- /dev/null
+++ b/common/util/tests/assets/logcat.txt
@@ -0,0 +1,274 @@
+--------- beginning of system
+09-03 17:47:59.490  7039  7054 D AtCkpdCmdHandler: De-queing command
+--------- beginning of crash
+09-03 17:48:05.627 11071 11189 F libc    : Fatal signal 11 (SIGSEGV), code 1, fault addr 0xe9380000 in tid 11189 (AudioOut_D)
+09-03 17:48:05.707   359   359 W         : debuggerd: handling request: pid=11071 uid=1041 gid=1005 tid=11189
+09-03 17:48:05.796  7072  7072 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
+09-03 17:48:05.796  7072  7072 F DEBUG   : Build fingerprint: 'google/angler/angler:7.1.1/N4F26T/3687331:userdebug/dev-keys'
+09-03 17:48:05.796  7072  7072 F DEBUG   : Revision: '0'
+09-03 17:48:05.796  7072  7072 F DEBUG   : ABI: 'arm'
+09-03 17:48:05.796  7072  7072 F DEBUG   : pid: 11071, tid: 11189, name: AudioOut_D  >>> /system/bin/audioserver <<<
+09-03 17:48:05.797  7072  7072 F DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xe9380000
+09-03 17:48:05.797  7072  7072 F DEBUG   :     r0 e9e7a240  r1 e9380000  r2 00000170  r3 00000000
+09-03 17:48:05.797  7072  7072 F DEBUG   :     r4 00000002  r5 00000000  r6 ec1e1f25  r7 eb6f8000
+09-03 17:48:05.797  7072  7072 F DEBUG   :     r8 00000000  r9 eb105204  sl 00000000  fp 000003c0
+09-03 17:48:05.797  7072  7072 F DEBUG   :     ip ebd3df18  sp eaf80688  lr ec1e1f41  pc ebd38dd6  cpsr 20000030
+09-03 17:48:05.805  7072  7072 F DEBUG   :
+09-03 17:48:05.805  7072  7072 F DEBUG   : backtrace:
+09-03 17:48:05.806  7072  7072 F DEBUG   :     #00 pc 00002dd6  /system/lib/libaudioutils.so (memcpy_to_float_from_i16+5)
+09-03 17:48:05.806  7072  7072 F DEBUG   :     #01 pc 00040f3d  /system/lib/libaudioflinger.so
+09-03 17:48:05.806  7072  7072 F DEBUG   :     #02 pc 00040799  /system/lib/libaudioflinger.so
+09-03 17:48:05.806  7072  7072 F DEBUG   :     #03 pc 00011178  /system/lib/libaudioflinger.so
+09-03 17:48:05.806  7072  7072 F DEBUG   :     #04 pc 0003180b  /system/lib/libaudioflinger.so
+09-03 17:48:05.806  7072  7072 F DEBUG   :     #05 pc 0002fe57  /system/lib/libaudioflinger.so
+09-03 17:48:05.806  7072  7072 F DEBUG   :     #06 pc 0000e345  /system/lib/libutils.so (_ZN7android6Thread11_threadLoopEPv+140)
+09-03 17:48:05.806  7072  7072 F DEBUG   :     #07 pc 000470b3  /system/lib/libc.so (_ZL15__pthread_startPv+22)
+09-03 17:48:05.806  7072  7072 F DEBUG   :     #08 pc 00019e3d  /system/lib/libc.so (__start_thread+6)
+09-03 17:48:05.967 11272 11568 W NativeCrashListener: Couldn't find ProcessRecord for pid 11071
+09-03 17:48:05.969   359   359 W         : debuggerd: resuming target 11071
+09-03 17:48:05.981 11272 11307 I BootReceiver: Copying /data/tombstones/tombstone_01 to DropBox (SYSTEM_TOMBSTONE)
+09-03 17:48:06.067   394   394 I ServiceManager: service 'media.sound_trigger_hw' died
+06-15 19:57:33.607 12736 12761 D PermissionCache: checking android.permission.MODIFY_AUDIO_SETTINGS for uid=10197 => granted (698 us)
+--------- beginning of crash
+06-15 19:57:33.607 12736 12761 F libc    : Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 12761 (Binder:12736_2)
+06-15 19:57:33.608   379   379 W         : debuggerd: handling request: pid=12736 uid=1041 gid=1005 tid=12761
+06-15 19:57:33.670 26192 26192 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
+06-15 19:57:33.670 26192 26192 F DEBUG   : Build fingerprint: 'google/bullhead/bullhead:7.1.2/N2G48C/4104010:userdebug/dev-keys'
+06-15 19:57:33.670 26192 26192 F DEBUG   : Revision: 'rev_1.0'
+06-15 19:57:33.670 26192 26192 F DEBUG   : ABI: 'arm'
+06-15 19:57:33.670 26192 26192 F DEBUG   : pid: 12736, tid: 12761, name: Binder:12736_2  >>> /system/bin/audioserver <<<
+06-15 19:57:33.670 26192 26192 F DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
+06-15 19:57:33.670 26192 26192 F DEBUG   :     r0 00000000  r1 00000000  r2 0000005f  r3 00000000
+06-15 19:57:33.670 26192 26192 F DEBUG   :     r4 ffffffff  r5 00000000  r6 f14f9000  r7 00000001
+06-15 19:57:33.670 26192 26192 F DEBUG   :     r8 00000004  r9 f3353114  sl f3313900  fp 00000000
+06-15 19:57:33.670 26192 26192 F DEBUG   :     ip f3bd4d88  sp f127d9c8  lr f3b9cbc5  pc f3b65af4  cpsr 60000030
+06-15 19:57:33.676 26192 26192 F DEBUG   :
+06-15 19:57:33.676 26192 26192 F DEBUG   : backtrace:
+06-15 19:57:33.677 26192 26192 F DEBUG   :     #00 pc 00018af4  /system/lib/libc.so (strlen+71)
+06-15 19:57:33.677 26192 26192 F DEBUG   :     #01 pc 0004fbc1  /system/lib/libc.so (__strlen_chk+4)
+06-15 19:57:33.677 26192 26192 F DEBUG   :     #02 pc 0000c599  /system/lib/libutils.so (_ZN7android7String8C2EPKc+12)
+06-15 19:57:33.677 26192 26192 F DEBUG   :     #03 pc 0002fdbf  /system/lib/libaudiopolicymanagerdefault.so (_ZNK7android18HwModuleCollection19getDeviceDescriptorEjPKcS2_b+458)
+06-15 19:57:33.677 26192 26192 F DEBUG   :     #04 pc 0001de47  /system/lib/libaudiopolicymanagerdefault.so (_ZN7android18AudioPolicyManager27setDeviceConnectionStateIntEj24audio_policy_dev_state_tPKcS3_+178)
+06-15 19:57:33.677 26192 26192 F DEBUG   :     #05 pc 0000a009  /system/lib/libaudiopolicyservice.so
+06-15 19:57:33.677 26192 26192 F DEBUG   :     #06 pc 000a01a5  /system/lib/libmedia.so (_ZN7android20BnAudioPolicyService10onTransactEjRKNS_6ParcelEPS1_j+1256)
+06-15 19:57:33.677 26192 26192 F DEBUG   :     #07 pc 000359c3  /system/lib/libbinder.so (_ZN7android7BBinder8transactEjRKNS_6ParcelEPS1_j+70)
+06-15 19:57:33.677 26192 26192 F DEBUG   :     #08 pc 0003d1bb  /system/lib/libbinder.so (_ZN7android14IPCThreadState14executeCommandEi+702)
+06-15 19:57:33.677 26192 26192 F DEBUG   :     #09 pc 0003ce07  /system/lib/libbinder.so (_ZN7android14IPCThreadState20getAndExecuteCommandEv+114)
+06-15 19:57:33.677 26192 26192 F DEBUG   :     #10 pc 0003d31b  /system/lib/libbinder.so (_ZN7android14IPCThreadState14joinThreadPoolEb+46)
+06-15 19:57:33.678 26192 26192 F DEBUG   :     #11 pc 0004f8c5  /system/lib/libbinder.so
+06-15 19:57:33.678 26192 26192 F DEBUG   :     #12 pc 0000e345  /system/lib/libutils.so (_ZN7android6Thread11_threadLoopEPv+140)
+06-15 19:57:33.678 26192 26192 F DEBUG   :     #13 pc 000470b3  /system/lib/libc.so (_ZL15__pthread_startPv+22)
+06-15 19:57:33.678 26192 26192 F DEBUG   :     #14 pc 00019e3d  /system/lib/libc.so (__start_thread+6)
+06-15 19:57:33.839   934  2991 W NativeCrashListener: Couldn't find ProcessRecord for pid 12736
+06-15 19:57:33.846   934   952 I BootReceiver: Copying /data/tombstones/tombstone_01 to DropBox (SYSTEM_TOMBSTONE)
+06-15 19:57:35.130 26201 26227 F libc    : Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 26227 (Binder:26201_3)
+06-15 19:57:35.130 26201 26228 D PermissionCache: checking android.permission.MODIFY_AUDIO_SETTINGS for uid=1000 => granted (318 us)
+06-15 19:57:35.130   379   379 W         : debuggerd: handling request: pid=26201 uid=1041 gid=1005 tid=26227
+06-15 19:57:35.131 26201 26212 D audio_hw_primary: select_devices: changing use case low-latency-playback output device from(0: none, acdb -1) to (2: speaker, acdb 14)
+06-15 19:57:35.191 26230 26230 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
+06-15 19:57:35.191 26230 26230 F DEBUG   : Build fingerprint: 'google/bullhead/bullhead:7.1.2/N2G48C/4104010:userdebug/dev-keys'
+06-15 19:57:35.191 26230 26230 F DEBUG   : Revision: 'rev_1.0'
+06-15 19:57:35.191 26230 26230 F DEBUG   : ABI: 'arm'
+06-15 19:57:35.191 26230 26230 F DEBUG   : pid: 26201, tid: 26227, name: Binder:26201_3  >>> /system/bin/audioserver <<<
+06-15 19:57:35.191 26230 26230 F DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
+06-15 19:57:35.191 26230 26230 F DEBUG   :     r0 00000000  r1 00000000  r2 0000005f  r3 00000000
+06-15 19:57:35.191 26230 26230 F DEBUG   :     r4 ffffffff  r5 00000000  r6 e49bb000  r7 00000001
+06-15 19:57:35.191 26230 26230 F DEBUG   :     r8 00000004  r9 e6b53114  sl e6b13900  fp 00000000
+06-15 19:57:35.191 26230 26230 F DEBUG   :     ip e746bd88  sp e4c009c8  lr e7433bc5  pc e73fcaf4  cpsr 60000030
+06-15 19:57:35.195 26230 26230 F DEBUG   :
+06-15 19:57:35.195 26230 26230 F DEBUG   : backtrace:
+06-15 19:57:35.195 26230 26230 F DEBUG   :     #00 pc 00018af4  /system/lib/libc.so (strlen+71)
+06-15 19:57:35.195 26230 26230 F DEBUG   :     #01 pc 0004fbc1  /system/lib/libc.so (__strlen_chk+4)
+06-15 19:57:35.195 26230 26230 F DEBUG   :     #02 pc 0000c599  /system/lib/libutils.so (_ZN7android7String8C2EPKc+12)
+06-15 19:57:35.195 26230 26230 F DEBUG   :     #03 pc 0002fdbf  /system/lib/libaudiopolicymanagerdefault.so (_ZNK7android18HwModuleCollection19getDeviceDescriptorEjPKcS2_b+458)
+06-15 19:57:35.196 26230 26230 F DEBUG   :     #04 pc 0001de47  /system/lib/libaudiopolicymanagerdefault.so (_ZN7android18AudioPolicyManager27setDeviceConnectionStateIntEj24audio_policy_dev_state_tPKcS3_+178)
+06-15 19:57:35.196 26230 26230 F DEBUG   :     #05 pc 0000a009  /system/lib/libaudiopolicyservice.so
+06-15 19:57:35.196 26230 26230 F DEBUG   :     #06 pc 000a01a5  /system/lib/libmedia.so (_ZN7android20BnAudioPolicyService10onTransactEjRKNS_6ParcelEPS1_j+1256)
+06-15 19:57:35.196 26230 26230 F DEBUG   :     #07 pc 000359c3  /system/lib/libbinder.so (_ZN7android7BBinder8transactEjRKNS_6ParcelEPS1_j+70)
+06-15 19:57:35.196 26230 26230 F DEBUG   :     #08 pc 0003d1bb  /system/lib/libbinder.so (_ZN7android14IPCThreadState14executeCommandEi+702)
+06-15 19:57:35.196 26230 26230 F DEBUG   :     #09 pc 0003ce07  /system/lib/libbinder.so (_ZN7android14IPCThreadState20getAndExecuteCommandEv+114)
+06-15 19:57:35.196 26230 26230 F DEBUG   :     #10 pc 0003d31b  /system/lib/libbinder.so (_ZN7android14IPCThreadState14joinThreadPoolEb+46)
+06-15 19:57:35.196 26230 26230 F DEBUG   :     #11 pc 0004f8c5  /system/lib/libbinder.so
+06-15 19:57:35.196 26230 26230 F DEBUG   :     #12 pc 0000e345  /system/lib/libutils.so (_ZN7android6Thread11_threadLoopEPv+140)
+06-15 19:57:35.196 26230 26230 F DEBUG   :     #13 pc 000470b3  /system/lib/libc.so (_ZL15__pthread_startPv+22)
+06-15 19:57:35.196 26230 26230 F DEBUG   :     #14 pc 00019e3d  /system/lib/libc.so (__start_thread+6)
+06-15 19:57:35.346   934  2991 W NativeCrashListener: Couldn't find ProcessRecord for pid 26201
+06-15 19:57:35.346 26230 26230 E         : AM data write failed: Broken pipe
+06-15 19:57:40.605 26246 26261 D audio_hw_primary: enable_snd_device: snd_device(78: vi-feedback)
+06-15 19:57:40.606 26246 26261 D audio_hw_primary: enable_audio_route: usecase(21) apply and update mixer path: spkr-vi-record
+06-15 19:57:40.673 26283 26283 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
+06-15 19:57:40.674 26283 26283 F DEBUG   : Build fingerprint: 'google/bullhead/bullhead:7.1.2/N2G48C/4104010:userdebug/dev-keys'
+06-15 19:57:40.674 26283 26283 F DEBUG   : Revision: 'rev_1.0'
+06-15 19:57:40.674 26283 26283 F DEBUG   : ABI: 'arm'
+06-15 19:57:40.674 26283 26283 F DEBUG   : pid: 26246, tid: 26282, name: Binder:26246_5  >>> /system/bin/audioserver <<<
+06-15 19:57:40.674 26283 26283 F DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
+06-15 19:57:40.674 26283 26283 F DEBUG   :     r0 00000000  r1 00000000  r2 0000005f  r3 00000000
+06-15 19:57:40.674 26283 26283 F DEBUG   :     r4 ffffffff  r5 00000000  r6 eb750000  r7 00000001
+06-15 19:57:40.674 26283 26283 F DEBUG   :     r8 00000004  r9 ed953114  sl ed913900  fp 00000000
+06-15 19:57:40.674 26283 26283 F DEBUG   :     ip eda8bd88  sp eb4fd9c8  lr eda53bc5  pc eda1caf4  cpsr 60000030
+06-15 19:57:40.679 26283 26283 F DEBUG   :
+06-15 19:57:40.679 26283 26283 F DEBUG   : backtrace:
+06-15 19:57:40.679 26283 26283 F DEBUG   :     #00 pc 00018af4  /system/lib/libc.so (strlen+71)
+06-15 19:57:40.679 26283 26283 F DEBUG   :     #01 pc 0004fbc1  /system/lib/libc.so (__strlen_chk+4)
+06-15 19:57:40.679 26283 26283 F DEBUG   :     #02 pc 0000c599  /system/lib/libutils.so (_ZN7android7String8C2EPKc+12)
+06-15 19:57:40.679 26283 26283 F DEBUG   :     #03 pc 0002fdbf  /system/lib/libaudiopolicymanagerdefault.so (_ZNK7android18HwModuleCollection19getDeviceDescriptorEjPKcS2_b+458)
+06-15 19:57:40.679 26283 26283 F DEBUG   :     #04 pc 0001de47  /system/lib/libaudiopolicymanagerdefault.so (_ZN7android18AudioPolicyManager27setDeviceConnectionStateIntEj24audio_policy_dev_state_tPKcS3_+178)
+06-15 19:57:40.679 26283 26283 F DEBUG   :     #05 pc 0000a009  /system/lib/libaudiopolicyservice.so
+06-15 19:57:40.680 26283 26283 F DEBUG   :     #06 pc 000a01a5  /system/lib/libmedia.so (_ZN7android20BnAudioPolicyService10onTransactEjRKNS_6ParcelEPS1_j+1256)
+06-15 19:57:40.680 26283 26283 F DEBUG   :     #07 pc 000359c3  /system/lib/libbinder.so (_ZN7android7BBinder8transactEjRKNS_6ParcelEPS1_j+70)
+06-15 19:57:40.680 26283 26283 F DEBUG   :     #08 pc 0003d1bb  /system/lib/libbinder.so (_ZN7android14IPCThreadState14executeCommandEi+702)
+06-15 19:57:40.680 26283 26283 F DEBUG   :     #09 pc 0003ce07  /system/lib/libbinder.so (_ZN7android14IPCThreadState20getAndExecuteCommandEv+114)
+06-15 19:57:40.680 26283 26283 F DEBUG   :     #10 pc 0003d31b  /system/lib/libbinder.so (_ZN7android14IPCThreadState14joinThreadPoolEb+46)
+06-15 19:57:40.680 26283 26283 F DEBUG   :     #11 pc 0004f8c5  /system/lib/libbinder.so
+06-15 19:57:40.680 26283 26283 F DEBUG   :     #12 pc 0000e345  /system/lib/libutils.so (_ZN7android6Thread11_threadLoopEPv+140)
+06-15 19:57:40.680 26283 26283 F DEBUG   :     #13 pc 000470b3  /system/lib/libc.so (_ZL15__pthread_startPv+22)
+06-15 19:57:40.680 26283 26283 F DEBUG   :     #14 pc 00019e3d  /system/lib/libc.so (__start_thread+6)
+06-15 19:57:40.882   934  2991 W NativeCrashListener: Couldn't find ProcessRecord for pid 26246
+06-15 19:57:40.889   934   952 I BootReceiver: Copying /data/tombstones/tombstone_03 to DropBox (SYSTEM_TOMBSTONE)
+09-23 01:55:43.022   245   245 F installd: utils.cpp:67] Check failed: is_valid_package_name(package_name) == 0
+--------- beginning of crash
+09-23 01:55:43.022   245   245 F libc    : Fatal signal 6 (SIGABRT), code -6 in tid 245 (installd)
+09-23 01:55:43.022   166   166 W         : debuggerd: handling request: pid=245 uid=0 gid=0 tid=245
+09-23 01:55:43.026   546   546 E         : debuggerd: Unable to connect to activity manager (connect failed: Connection refused)
+09-23 01:55:43.076   546   546 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
+09-23 01:55:43.076   546   546 F DEBUG   : Build fingerprint: 'google/ryu/dragon:7.1.2/N2G48C/4104010:userdebug/dev-keys'
+09-23 01:55:43.076   546   546 F DEBUG   : Revision: '0'
+09-23 01:55:43.076   546   546 F DEBUG   : ABI: 'arm64'
+09-23 01:55:43.077   546   546 F DEBUG   : pid: 245, tid: 245, name: installd  >>> /system/bin/installd <<<
+09-23 01:55:43.077   546   546 F DEBUG   : signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
+09-23 01:55:43.077   546   546 F DEBUG   : Abort message: 'utils.cpp:67] Check failed: is_valid_package_name(package_name) == 0 '
+09-23 01:55:43.077   546   546 F DEBUG   :     x0   0000000000000000  x1   00000000000000f5  x2   0000000000000006  x3   0000000000000008
+09-23 01:55:43.078   546   546 F DEBUG   :     x4   0000000000000000  x5   0000000000000000  x6   00000076a4172000  x7   0000000000000000
+09-23 01:55:43.078   546   546 F DEBUG   :     x8   0000000000000083  x9   ffffffffffffffdf  x10  0000000000000000  x11  0000000000000001
+09-23 01:55:43.078   546   546 F DEBUG   :     x12  0000000000000018  x13  0000000000000000  x14  0000000000000000  x15  000157de95b365f0
+09-23 01:55:43.078   546   546 F DEBUG   :     x16  00000076a4099ee0  x17  00000076a4043b24  x18  0000000000000000  x19  00000076a4227b40
+09-23 01:55:43.078   546   546 F DEBUG   :     x20  0000000000000006  x21  00000076a4227a98  x22  0000000000000014  x23  0000000000000005
+09-23 01:55:43.078   546   546 F DEBUG   :     x24  00000076a3834040  x25  0000000000000000  x26  0000000000000005  x27  0000000000000006
+09-23 01:55:43.078   546   546 F DEBUG   :     x28  0000007ff8a77879  x29  0000007ff8a777f0  x30  00000076a4040f50
+09-23 01:55:43.078   546   546 F DEBUG   :     sp   0000007ff8a777d0  pc   00000076a4043b2c  pstate 0000000060000000
+09-23 01:55:43.081   546   546 F DEBUG   :
+09-23 01:55:43.081   546   546 F DEBUG   : backtrace:
+09-23 01:55:43.081   546   546 F DEBUG   :     #00 pc 000000000006bb2c  /system/lib64/libc.so (tgkill+8)
+09-23 01:55:43.081   546   546 F DEBUG   :     #01 pc 0000000000068f4c  /system/lib64/libc.so (pthread_kill+64)
+09-23 01:55:43.081   546   546 F DEBUG   :     #02 pc 0000000000023f58  /system/lib64/libc.so (raise+24)
+09-23 01:55:43.081   546   546 F DEBUG   :     #03 pc 000000000001c810  /system/lib64/libc.so (abort+52)
+09-23 01:55:43.081   546   546 F DEBUG   :     #04 pc 000000000000609c  /system/lib64/libbase.so (_ZN7android4base10LogMessageD1Ev+1084)
+09-23 01:55:43.082   546   546 F DEBUG   :     #05 pc 000000000001bbd4  /system/bin/installd
+09-23 01:55:43.082   546   546 F DEBUG   :     #06 pc 000000000001be38  /system/bin/installd
+09-23 01:55:43.082   546   546 F DEBUG   :     #07 pc 0000000000007f08  /system/bin/installd
+09-23 01:55:43.082   546   546 F DEBUG   :     #08 pc 0000000000005bd4  /system/bin/installd
+09-23 01:55:43.082   546   546 F DEBUG   :     #09 pc 000000000001a594  /system/lib64/libc.so (__libc_init+88)
+09-23 01:55:43.082   546   546 F DEBUG   :     #10 pc 0000000000004818  /system/bin/installd
+09-23 01:55:43.093   166   166 W         : debuggerd: resuming target 245
+09-23 01:55:43.132   516   516 E InstallerConnection: read exception
+09-23 01:55:43.132   516   516 I InstallerConnection: disconnecting...
+09-23 01:55:48.494   516   537 W WindowManager: App freeze timeout expired.
+09-23 01:55:52.058   163   163 W auditd  : type=1404 audit(0.0:4): enforcing=0 old_enforcing=1 auid=4294967295 ses=4294967295
+--------- beginning of main
+11-03 02:59:48.505  8049  8049 I stagefright: type=1400 audit(0.0:130): avc: denied { read } for path="/data/data/test1.mp4" dev="sda35" ino=868967 scontext=u:r:drmserver:s0 tcontext=u:object_r:system_data_file:s0 tclass=file permissive=1
+11-03 02:59:48.505  3939  3939 I chatty  : uid=10040(u0_a40) com.google.android.setupwizard expire 52528 lines
+11-03 02:59:48.559  8049  8054 I OMXClient: Treble IOmx obtained
+--------- beginning of crash
+11-03 02:59:48.892  6371  8072 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
+11-03 02:59:48.892  6371  8072 F DEBUG   : Build fingerprint: 'google/marlin/marlin:8.0.0/OC/mspect11021711:userdebug/dev-keys'
+11-03 02:59:48.892  6371  8072 F DEBUG   : Revision: '0'
+11-03 02:59:48.892  6371  8072 F DEBUG   : ABI: 'arm'
+11-03 02:59:48.892  6371  8072 F DEBUG   : pid: 6371, tid: 8072, name: media.codec  >>> omx@1.0-service <<<
+11-03 02:59:48.892  6371  8072 F DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xed000000
+11-03 02:59:48.900  6371  8072 F DEBUG   :     r0 ecffdba0  r1 ed000000  r2 d44854c0  r3 00000070
+11-03 02:59:48.900  6371  8072 F DEBUG   :     r4 00000070  r5 000021a0  r6 00000070  r7 00000070
+11-03 02:59:48.900  6371  8072 F DEBUG   :     r8 00000040  r9 ffc2b278  sl ffffde70  fp 00000060
+11-03 02:59:48.900  6371  8072 F DEBUG   :     ip ffffffa0  sp d2fff620  lr 00004308  pc ed1c0e7c  cpsr a00f0010
+11-03 02:59:48.901  6371  8072 F DEBUG   :
+11-03 02:59:48.901  6371  8072 F DEBUG   : backtrace:
+11-03 02:59:48.901  6371  8072 F DEBUG   :     #00 pc 00034e7c  /system/lib/libstagefright_soft_hevcdec.so
+--------- beginning of system
+11-03 02:59:48.905  1135  1155 I BootReceiver: Copying /data/tombstones/tombstone_03 to DropBox (SYSTEM_TOMBSTONE)
+05-04 21:59:23.695  9363  9363 I crash_dump64: performing dump of process 8373 (target tid = 8414)
+05-04 21:59:23.695  9363  9363 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
+05-04 21:59:23.696  9363  9363 F DEBUG   : Build fingerprint: 'google/taimen/taimen:8.1.0/OPM2.171026.006.A1/4756228:userdebug/dev-keys'
+05-04 21:59:23.696  9363  9363 F DEBUG   : Revision: 'rev_10'
+05-04 21:59:23.696  9363  9363 F DEBUG   : ABI: 'arm64'
+05-04 21:59:23.696  9363  9363 F DEBUG   : pid: 8373, tid: 8414, name: btu message loo  >>> com.android.bluetooth <<<
+05-04 21:59:23.696  9363  9363 F DEBUG   : signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
+05-04 21:59:23.700  9363  9363 F DEBUG   : Abort message: '[FATAL:allocation_tracker.cc(143)] Check failed: map_entry != allocations.end().
+05-04 21:59:23.700  9363  9363 F DEBUG   : '
+05-04 21:59:23.700  9363  9363 F DEBUG   :     x0   0000000000000000  x1   00000000000020de  x2   0000000000000006  x3   0000000000000008
+05-04 21:59:23.701  9363  9363 F DEBUG   :     x4   613a4c415441465b  x5   613a4c415441465b  x6   613a4c415441465b  x7   6f697461636f6c6c
+05-04 21:59:23.701  9363  9363 F DEBUG   :     x8   0000000000000083  x9   0000000010000000  x10  000000703a7a7c80  x11  0000000000000001
+05-04 21:59:23.701  9363  9363 F DEBUG   :     x12  746e655f70616d20  x13  6c61203d21207972  x14  ff00000000000000  x15  ffffffffffffffff
+05-04 21:59:23.701  9363  9363 F DEBUG   :     x16  0000006380f4afa8  x17  00000070d20af52c  x18  0000000000000000  x19  00000000000020b5
+05-04 21:59:23.701  9363  9363 F DEBUG   :     x20  00000000000020de  x21  0000000000000083  x22  000000703a7a9588  x23  000000703b8ee000
+05-04 21:59:23.701  9363  9363 F DEBUG   :     x24  000000703a7a7d01  x25  000000703a7a9588  x26  000000703bc7d948  x27  00000070484c92d8
+05-04 21:59:23.701  9363  9363 F DEBUG   :     x28  0000000000000006  x29  000000703a7a7cc0  x30  00000070d2064760
+05-04 21:59:23.701  9363  9363 F DEBUG   :     sp   000000703a7a7c80  pc   00000070d2064788  pstate 0000000060000000
+05-04 21:59:23.742  9363  9363 F DEBUG   :
+05-04 21:59:23.742  9363  9363 F DEBUG   : backtrace:
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #00 pc 000000000001d788  /system/lib64/libc.so (abort+120)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #01 pc 0000000000083470  /system/lib64/libchrome.so (base::debug::BreakDebugger()+20)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #02 pc 000000000009affc  /system/lib64/libchrome.so (logging::LogMessage::~LogMessage()+1068)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #03 pc 0000000000199130  /system/lib64/hw/bluetooth.default.so (allocation_tracker_notify_free(unsigned char, void*)+720)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #04 pc 000000000019984c  /system/lib64/hw/bluetooth.default.so (osi_free(void*)+20)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #05 pc 0000000000163f1c  /system/lib64/hw/bluetooth.default.so (l2c_fcr_cleanup(t_l2c_ccb*)+92)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #06 pc 000000000016adc8  /system/lib64/hw/bluetooth.default.so (l2cu_release_ccb(t_l2c_ccb*)+176)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #07 pc 0000000000162ea0  /system/lib64/hw/bluetooth.default.so (l2c_csm_execute(t_l2c_ccb*, unsigned short, void*)+1852)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #08 pc 000000000015e4f4  /system/lib64/hw/bluetooth.default.so (L2CA_DisconnectRsp(unsigned short)+92)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #09 pc 00000000001838b0  /system/lib64/hw/bluetooth.default.so (sdp_disconnect_ind(unsigned short, bool)+52)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #10 pc 0000000000163574  /system/lib64/hw/bluetooth.default.so (l2c_csm_execute(t_l2c_ccb*, unsigned short, void*)+3600)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #11 pc 0000000000169f94  /system/lib64/hw/bluetooth.default.so (l2c_rcv_acl_data(BT_HDR*)+3980)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #12 pc 00000000000849cc  /system/lib64/libchrome.so (base::debug::TaskAnnotator::RunTask(char const*, base::PendingTask const&)+188)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #13 pc 000000000009efa4  /system/lib64/libchrome.so (base::MessageLoop::RunTask(base::PendingTask const&)+444)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #14 pc 000000000009f26c  /system/lib64/libchrome.so (base::MessageLoop::DeferOrRunPendingTask(base::PendingTask)+52)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #15 pc 000000000009f698  /system/lib64/libchrome.so (base::MessageLoop::DoWork()+356)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #16 pc 00000000000a08a8  /system/lib64/libchrome.so (base::MessagePumpDefault::Run(base::MessagePump::Delegate*)+220)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #17 pc 00000000000ba124  /system/lib64/libchrome.so (base::RunLoop::Run()+136)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #18 pc 0000000000138660  /system/lib64/hw/bluetooth.default.so (btu_message_loop_run(void*)+248)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #19 pc 00000000001a24fc  /system/lib64/hw/bluetooth.default.so (work_queue_read_cb(void*)+92)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #20 pc 00000000001a0758  /system/lib64/hw/bluetooth.default.so (run_reactor(reactor_t*, int)+320)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #21 pc 00000000001a05ec  /system/lib64/hw/bluetooth.default.so (reactor_start(reactor_t*)+84)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #22 pc 00000000001a1f94  /system/lib64/hw/bluetooth.default.so (run_thread(void*)+184)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #23 pc 0000000000067d80  /system/lib64/libc.so (__pthread_start(void*)+36)
+05-04 21:59:23.743  9363  9363 F DEBUG   :     #24 pc 000000000001ec18  /system/lib64/libc.so (__start_thread+68)
+1-25 19:47:35.417  8080 11665 F MPEG4Extractor: frameworks/av/media/libstagefright/MPEG4Extractor.cpp:6853 CHECK_EQ( (unsigned)ptr[0],1u) failed: 129 vs. 1
+11-25 19:47:35.417  8080 11665 F libc    : Fatal signal 6 (SIGABRT), code -6 in tid 11665 (generic)
+11-25 19:47:35.487   940   940 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
+11-25 19:47:35.487   940   940 F DEBUG   : Build fingerprint: 'samsung/hero2qltezc/hero2qltechn:6.0.1/MMB29M/G9350ZCU2APJ6:user/release-keys'
+11-25 19:47:35.487   940   940 F DEBUG   : Revision: '15'
+11-25 19:47:35.487   940   940 F DEBUG   : ABI: 'arm'
+11-25 19:47:35.487   940   940 F DEBUG   : pid: 8080, tid: 11665, name: generic  >>> /system/bin/mediaserver <<<
+11-25 19:47:35.487   940   940 F DEBUG   : signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
+11-25 19:47:35.577   940   940 F DEBUG   : Abort message: 'frameworks/av/media/libstagefright/MPEG4Extractor.cpp:6853 CHECK_EQ( (unsigned)ptr[0],1u) failed: 129 vs. 1'
+11-25 19:47:35.577   940   940 F DEBUG   :     r0 00000000  r1 00002d91  r2 00000006  r3 eb23f978
+11-25 19:47:35.577   940   940 F DEBUG   :     r4 eb23f980  r5 eb23f930  r6 00000009  r7 0000010c
+11-25 19:47:35.577   940   940 F DEBUG   :     r8 e9e91140  r9 00000000  sl 00000000  fp 000003d3
+11-25 19:47:35.577   940   940 F DEBUG   :     ip 00000006  sp eb23db70  lr f701313d  pc f7015538  cpsr 40010010
+11-25 19:47:35.597   940   940 F DEBUG   :
+11-25 19:47:35.597   940   940 F DEBUG   : backtrace:
+11-25 19:47:35.597   940   940 F DEBUG   :     #00 pc 00042538  /system/lib/libc.so (tgkill+12)
+11-25 19:47:35.597   940   940 F DEBUG   :     #01 pc 00040139  /system/lib/libc.so (pthread_kill+32)
+11-25 19:47:35.597   940   940 F DEBUG   :     #02 pc 0001c783  /system/lib/libc.so (raise+10)
+11-25 19:47:35.597   940   940 F DEBUG   :     #03 pc 000199f1  /system/lib/libc.so (__libc_android_abort+34)
+11-25 19:47:35.597   940   940 F DEBUG   :     #04 pc 000175ac  /system/lib/libc.so (abort+4)
+11-25 19:47:35.597   940   940 F DEBUG   :     #05 pc 000085e7  /system/lib/libcutils.so (__android_log_assert+86)
+11-25 19:47:35.597   940   940 F DEBUG   :     #06 pc 000c1f49  /system/lib/libstagefright.so (_ZN7android14MPEG4Extractor25avcc_getCodecSpecificInfoERNS_2spINS_7ABufferEEEPKcj+392)
+11-25 19:47:35.597   940   940 F DEBUG   :     #07 pc 000c213f  /system/lib/libstagefright.so (_ZN7android14MPEG4Extractor15checkConfigDataEjRKNS_2spINS_8MetaDataEEE+218)
+11-25 19:47:35.597   940   940 F DEBUG   :     #08 pc 000bbd25  /system/lib/libstagefright.so (_ZN7android14MPEG4Extractor12checkSupportEjRKNS_2spINS_8MetaDataEEE+136)
+11-25 19:47:35.597   940   940 F DEBUG   :     #09 pc 000ba555  /system/lib/libstagefright.so (_ZN7android14MPEG4Extractor10parseChunkEPxi+13060)
+11-25 19:47:35.597   940   940 F DEBUG   :     #10 pc 000ba32d  /system/lib/libstagefright.so (_ZN7android14MPEG4Extractor10parseChunkEPxi+12508)
+11-25 19:47:35.597   940   940 F DEBUG   :     #11 pc 000b8a6f  /system/lib/libstagefright.so (_ZN7android14MPEG4Extractor10parseChunkEPxi+6174)
+11-25 19:47:35.597   940   940 F DEBUG   :     #12 pc 000b8a6f  /system/lib/libstagefright.so (_ZN7android14MPEG4Extractor10parseChunkEPxi+6174)
+11-25 19:47:35.597   940   940 F DEBUG   :     #13 pc 000b8a6f  /system/lib/libstagefright.so (_ZN7android14MPEG4Extractor10parseChunkEPxi+6174)
+11-25 19:47:35.597   940   940 F DEBUG   :     #14 pc 000b8a6f  /system/lib/libstagefright.so (_ZN7android14MPEG4Extractor10parseChunkEPxi+6174)
+11-25 19:47:35.597   940   940 F DEBUG   :     #15 pc 000b8a6f  /system/lib/libstagefright.so (_ZN7android14MPEG4Extractor10parseChunkEPxi+6174)
+11-25 19:47:35.597   940   940 F DEBUG   :     #16 pc 000b6e3b  /system/lib/libstagefright.so (_ZN7android14MPEG4Extractor12readMetaDataEv+94)
+11-25 19:47:35.597   940   940 F DEBUG   :     #17 pc 000b6daf  /system/lib/libstagefright.so (_ZN7android14MPEG4Extractor11getMetaDataEv+10)
+11-25 19:47:35.597   940   940 F DEBUG   :     #18 pc 00088c53  /system/lib/libmediaplayerservice.so (_ZN7android8NuPlayer13GenericSource18initFromDataSourceEv+386)
+11-25 19:47:35.597   940   940 F DEBUG   :     #19 pc 00089b43  /system/lib/libmediaplayerservice.so (_ZN7android8NuPlayer13GenericSource14onPrepareAsyncEv+238)
+11-25 19:47:35.597   940   940 F DEBUG   :     #20 pc 0000b405  /system/lib/libstagefright_foundation.so (_ZN7android8AHandler14deliverMessageERKNS_2spINS_8AMessageEEE+16)
+11-25 19:47:35.597   940   940 F DEBUG   :     #21 pc 0000d423  /system/lib/libstagefright_foundation.so (_ZN7android8AMessage7deliverEv+54)
+11-25 19:47:35.597   940   940 F DEBUG   :     #22 pc 0000be29  /system/lib/libstagefright_foundation.so (_ZN7android7ALooper4loopEv+224)
+11-25 19:47:35.597   940   940 F DEBUG   :     #23 pc 0001011d  /system/lib/libutils.so (_ZN7android6Thread11_threadLoopEPv+112)
+11-25 19:47:35.597   940   940 F DEBUG   :     #24 pc 0003fa3b  /system/lib/libc.so (_ZL15__pthread_startPv+30)
+11-25 19:47:35.597   940   940 F DEBUG   :     #25 pc 0001a085  /system/lib/libc.so (__start_thread+6)
+11-25 19:47:35.837   940   940 F DEBUG   :
+11-25 19:47:35.837   940   940 F DEBUG   : Tombstone written to: /data/tombstones/tombstone_01
\ No newline at end of file
diff --git a/common/util/tests/run_tests.sh b/common/util/tests/run_tests.sh
deleted file mode 100755
index bed2c19..0000000
--- a/common/util/tests/run_tests.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/bash
-
-# Copyright (C) 2015 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.
-
-HARNESS_DIR=$(dirname ${0})/../../..
-source ${HARNESS_DIR}/test_defs.sh
-
-JARS="
-    compatibility-common-util-hostsidelib\
-    compatibility-common-util-tests\
-    compatibility-host-util\
-    compatibility-host-util-tests\
-    compatibility-mock-tradefed\
-    compatibility-tradefed-tests"
-
-run_tests "com.android.compatibility.common.util.UnitTests" "${JARS}" "${@}"
-
diff --git a/common/util/tests/src/com/android/compatibility/common/util/BackupUtilsTest.java b/common/util/tests/src/com/android/compatibility/common/util/BackupUtilsTest.java
new file mode 100644
index 0000000..133a545
--- /dev/null
+++ b/common/util/tests/src/com/android/compatibility/common/util/BackupUtilsTest.java
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2018 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.compatibility.common.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.util.RunUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Unit tests for {@link BackupUtils}
+ */
+@RunWith(JUnit4.class)
+public class BackupUtilsTest {
+    private static final int BACKUP_SERVICE_INIT_TIMEOUT_SECS = 1;
+    private static final int TEST_USER_ID = 10;
+
+    private boolean mIsDumpsysCommandCalled;
+    private boolean mIsEnableCommandCalled;
+
+    @Before
+    public void setUp() {
+        mIsDumpsysCommandCalled = false;
+        mIsEnableCommandCalled = false;
+    }
+
+    @Test
+    public void testEnableBackup_whenEnableTrueAndEnabled_returnsTrue() throws Exception {
+        BackupUtils backupUtils = new BackupUtils() {
+            @Override
+            protected InputStream executeShellCommand(String command) throws IOException {
+                String output = "";
+                if (command.equals("bmgr enabled")) {
+                    output = "Backup Manager currently enabled";
+                } else if (command.equals("bmgr enable true")) {
+                    output = "Backup Manager now enabled";
+                    mIsEnableCommandCalled = true;
+                }
+                return new ByteArrayInputStream(output.getBytes("UTF-8"));
+            }
+        };
+        assertTrue(backupUtils.enableBackup(true));
+        assertTrue(mIsEnableCommandCalled);
+    }
+
+    @Test
+    public void testEnableBackup_whenEnableTrueAndDisabled_returnsFalse() throws Exception {
+        BackupUtils backupUtils = new BackupUtils() {
+            @Override
+            protected InputStream executeShellCommand(String command) throws IOException {
+                String output = "";
+                if (command.equals("bmgr enabled")) {
+                    output = "Backup Manager currently disabled";
+                } else if (command.equals("bmgr enable true")) {
+                    output = "Backup Manager now enabled";
+                    mIsEnableCommandCalled = true;
+                }
+                return new ByteArrayInputStream(output.getBytes("UTF-8"));
+            }
+        };
+        assertFalse(backupUtils.enableBackup(true));
+        assertTrue(mIsEnableCommandCalled);
+    }
+
+    @Test
+    public void testEnableBackup_whenEnableFalseAndEnabled_returnsTrue() throws Exception {
+        BackupUtils backupUtils = new BackupUtils() {
+            @Override
+            protected InputStream executeShellCommand(String command) throws IOException {
+                String output = "";
+                if (command.equals("bmgr enabled")) {
+                    output = "Backup Manager currently enabled";
+                } else if (command.equals("bmgr enable false")) {
+                    output = "Backup Manager now disabled";
+                    mIsEnableCommandCalled = true;
+                }
+                return new ByteArrayInputStream(output.getBytes("UTF-8"));
+            }
+        };
+        assertTrue(backupUtils.enableBackup(false));
+        assertTrue(mIsEnableCommandCalled);
+    }
+
+    @Test
+    public void testEnableBackup_whenEnableFalseAndDisabled_returnsFalse() throws Exception {
+        BackupUtils backupUtils = new BackupUtils() {
+            @Override
+            protected InputStream executeShellCommand(String command) throws IOException {
+                String output = "";
+                if (command.equals("bmgr enabled")) {
+                    output = "Backup Manager currently disabled";
+                } else if (command.equals("bmgr enable false")) {
+                    output = "Backup Manager now disabled";
+                    mIsEnableCommandCalled = true;
+                }
+                return new ByteArrayInputStream(output.getBytes("UTF-8"));
+            }
+        };
+        assertFalse(backupUtils.enableBackup(false));
+        assertTrue(mIsEnableCommandCalled);
+    }
+
+    @Test
+    public void testEnableBackup_whenEnableTrueAndEnabledAndCommandsReturnMultipleLines()
+            throws Exception {
+        BackupUtils backupUtils = new BackupUtils() {
+            @Override
+            protected InputStream executeShellCommand(String command) throws IOException {
+                String output = "";
+                if (command.equals("bmgr enabled")) {
+                    output = "Backup Manager currently enabled" + "\n...";
+                } else if (command.equals("bmgr enable true")) {
+                    output = "Backup Manager now enabled" + "\n...";
+                    mIsEnableCommandCalled = true;
+                }
+                return new ByteArrayInputStream(output.getBytes("UTF-8"));
+            }
+        };
+        assertTrue(backupUtils.enableBackup(true));
+        assertTrue(mIsEnableCommandCalled);
+    }
+
+    @Test
+    public void testEnableBackup_whenQueryCommandThrows_propagatesException() throws Exception {
+        BackupUtils backupUtils = new BackupUtils() {
+            @Override
+            protected InputStream executeShellCommand(String command) throws IOException {
+                String output = "";
+                if (command.equals("bmgr enabled")) {
+                    throw new IOException(String.format(
+                            "enableBackup: Failed to run command: %s", command));
+                } else if (command.equals("bmgr enable true")) {
+                    output = "Backup Manager now enabled";
+                    mIsEnableCommandCalled = true;
+                }
+                return new ByteArrayInputStream(output.getBytes("UTF-8"));
+            }
+        };
+
+        boolean isExceptionHappened = false;
+        try {
+            backupUtils.enableBackup(true);
+        } catch (IOException e) {
+            // enableBackup: Failed to run command: bmgr enabled
+            isExceptionHappened = true;
+        }
+        assertTrue(isExceptionHappened);
+        assertFalse(mIsEnableCommandCalled);
+    }
+
+    @Test
+    public void testEnableBackup_whenSetCommandThrows_propagatesException() throws Exception {
+        BackupUtils backupUtils = new BackupUtils() {
+            @Override
+            protected InputStream executeShellCommand(String command) throws IOException {
+                String output = "";
+                if (command.equals("bmgr enabled")) {
+                    output = "Backup Manager currently enabled";
+                } else if (command.equals("bmgr enable true")) {
+                    mIsEnableCommandCalled = true;
+                    throw new IOException(String.format(
+                            "enableBackup: Failed to run command: %s", command));
+                }
+                return new ByteArrayInputStream(output.getBytes("UTF-8"));
+            }
+        };
+
+        boolean isExceptionHappened = false;
+        try {
+            backupUtils.enableBackup(true);
+        } catch (IOException e) {
+            // enableBackup: Failed to run command: bmgr enable true
+            isExceptionHappened = true;
+        }
+        assertTrue(isExceptionHappened);
+        assertTrue(mIsEnableCommandCalled);
+    }
+
+    @Test
+    public void testEnableBackup_whenQueryCommandReturnsInvalidString_throwsException()
+            throws Exception {
+        BackupUtils backupUtils = new BackupUtils() {
+            @Override
+            protected InputStream executeShellCommand(String command) throws IOException {
+                String output = "";
+                if (command.equals("bmgr enabled")) {
+                    output = "Backup Manager ???";
+                } else if (command.equals("bmgr enable true")) {
+                    output = "Backup Manager now enabled";
+                    mIsEnableCommandCalled = true;
+                }
+                return new ByteArrayInputStream(output.getBytes("UTF-8"));
+            }
+        };
+
+        boolean isExceptionHappened = false;
+        try {
+            backupUtils.enableBackup(true);
+        } catch (RuntimeException e) {
+            // non-parsable output setting bmgr enabled: Backup Manager ???
+            isExceptionHappened = true;
+        }
+        assertTrue(isExceptionHappened);
+        assertFalse(mIsEnableCommandCalled);
+    }
+
+    @Test
+    public void testEnableBackup_whenQueryCommandReturnsEmptyString_throwsException()
+            throws Exception {
+        BackupUtils backupUtils = new BackupUtils() {
+            @Override
+            protected InputStream executeShellCommand(String command) throws IOException {
+                String output = "";
+                if (command.equals("bmgr enabled")) {
+                    // output is empty already
+                } else if (command.equals("bmgr enable true")) {
+                    output = "Backup Manager now enabled";
+                    mIsEnableCommandCalled = true;
+                }
+                return new ByteArrayInputStream(output.getBytes("UTF-8"));
+            }
+        };
+
+        boolean isExceptionHappened = false;
+        try {
+            backupUtils.enableBackup(true);
+        } catch (NullPointerException e) {
+            // null output by running command, bmgr enabled
+            isExceptionHappened = true;
+        }
+        assertTrue(isExceptionHappened);
+        assertFalse(mIsEnableCommandCalled);
+    }
+
+    @Test
+    public void testWaitForBackupInitialization_whenEnabled() throws Exception {
+        BackupUtils backupUtils = new BackupUtils() {
+            @Override
+            protected InputStream executeShellCommand(String command) throws IOException {
+                String output = "";
+                if (command.equals("dumpsys backup")) {
+                    output = "Backup Manager is enabled / provisioned / not pending init";
+                    mIsDumpsysCommandCalled = true;
+                }
+                return new ByteArrayInputStream(output.getBytes("UTF-8"));
+            }
+        };
+        backupUtils.waitForBackupInitialization();
+        assertTrue(mIsDumpsysCommandCalled);
+    }
+
+    @Test
+    public void testWaitForBackupInitialization_whenDisabled() throws Exception {
+        BackupUtils backupUtils = new BackupUtils() {
+            @Override
+            protected InputStream executeShellCommand(String command) throws IOException {
+                String output = "";
+                if (command.equals("dumpsys backup")) {
+                    output = "Backup Manager is disabled / provisioned / not pending init";
+                    mIsDumpsysCommandCalled = true;
+                }
+                return new ByteArrayInputStream(output.getBytes("UTF-8"));
+            }
+        };
+        backupUtils.waitForBackupInitialization();
+        assertTrue(mIsDumpsysCommandCalled);
+    }
+
+    @Test
+    public void testWaitUntilBackupServiceIsRunning_whenRunning_doesntThrow() throws Exception {
+        BackupUtils backupUtils = constructDumpsysForBackupUsers(TEST_USER_ID);
+
+        try {
+            backupUtils.waitUntilBackupServiceIsRunning(
+                    TEST_USER_ID, BACKUP_SERVICE_INIT_TIMEOUT_SECS);
+        } catch (AssertionError e) {
+            fail("BackupUtils#waitUntilBackupServiceIsRunning threw an exception");
+        }
+        assertTrue(mIsDumpsysCommandCalled);
+    }
+
+    @Test
+    public void testWaitUntilBackupServiceIsRunning_whenNotRunning_throws() throws Exception {
+        // Pass in a different userId to not have the current one among running ids.
+        BackupUtils backupUtils = constructDumpsysForBackupUsers(TEST_USER_ID + 1);
+
+        boolean wasExceptionThrown = false;
+        try {
+            backupUtils.waitUntilBackupServiceIsRunning(
+                    TEST_USER_ID, BACKUP_SERVICE_INIT_TIMEOUT_SECS);
+        } catch (AssertionError e) {
+            wasExceptionThrown = true;
+        }
+
+        assertTrue(mIsDumpsysCommandCalled);
+        assertTrue(wasExceptionThrown);
+    }
+
+    private BackupUtils constructDumpsysForBackupUsers(int runningUserId) {
+        return new BackupUtils() {
+            @Override
+            protected InputStream executeShellCommand(String command) throws IOException {
+                String output = "";
+                if (command.equals("dumpsys backup users")) {
+                    output = "Backup Manager is running for users: " + runningUserId;
+                    mIsDumpsysCommandCalled = true;
+                }
+                return new ByteArrayInputStream(output.getBytes("UTF-8"));
+            }
+        };
+    }
+
+    @Test
+    public void testWaitForBackupInitialization_whenEnabledAndCommandReturnsMultipleLines()
+            throws Exception {
+        BackupUtils backupUtils = new BackupUtils() {
+            @Override
+            protected InputStream executeShellCommand(String command) throws IOException {
+                String output = "";
+                if (command.equals("dumpsys backup")) {
+                    output = "Backup Manager is enabled / provisioned / not pending init" + "\n...";
+                    mIsDumpsysCommandCalled = true;
+                }
+                return new ByteArrayInputStream(output.getBytes("UTF-8"));
+            }
+        };
+        backupUtils.waitForBackupInitialization();
+        assertTrue(mIsDumpsysCommandCalled);
+    }
+
+    @Test
+    public void testWaitForBackupInitialization_whenCommandThrows_propagatesException()
+            throws Exception {
+        BackupUtils backupUtils = new BackupUtils() {
+            @Override
+            protected InputStream executeShellCommand(String command) throws IOException {
+                String output = "";
+                if (command.equals("dumpsys backup")) {
+                    mIsDumpsysCommandCalled = true;
+                    throw new IOException(String.format(
+                            "waitForBackupInitialization: Failed to run command: %s", command));
+                }
+                return new ByteArrayInputStream(output.getBytes("UTF-8"));
+            }
+        };
+
+        boolean isExceptionHappened = false;
+        try {
+            backupUtils.waitForBackupInitialization();
+        } catch (IOException e) {
+            // waitForBackupInitialization: Failed to run command: dumpsys backup
+            isExceptionHappened = true;
+        }
+        assertTrue(isExceptionHappened);
+        assertTrue(mIsDumpsysCommandCalled);
+    }
+
+    @Test
+    public void testWaitForBackupInitialization_whenCommandReturnsInvalidString()
+            throws Exception {
+        class TestRunnable implements Runnable {
+            @Override
+            public void run() {
+                try {
+                    BackupUtils backupUtils = new BackupUtils() {
+                        @Override
+                        protected InputStream executeShellCommand(String command)
+                                throws IOException {
+                            String output = "";
+                            if (command.equals("dumpsys backup")) {
+                                output = "Backup Manager ???";
+                                mIsDumpsysCommandCalled = true;
+                            }
+                            return new ByteArrayInputStream(output.getBytes("UTF-8"));
+                        }
+                    };
+                    backupUtils.waitForBackupInitialization();
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+
+        TestRunnable testRunnable = new TestRunnable();
+        Thread testThread = new Thread(testRunnable);
+
+        try {
+            testThread.start();
+            RunUtil.getDefault().sleep(100);
+            assertTrue(mIsDumpsysCommandCalled);
+            assertTrue(testThread.isAlive());
+        } catch (Exception e) {
+            // ignore
+        } finally {
+            testThread.interrupt();
+        }
+    }
+
+    @Test
+    public void testWaitForBackupInitialization_whenCommandReturnsEmptyString_throwsException()
+            throws Exception {
+        BackupUtils backupUtils = new BackupUtils() {
+            @Override
+            protected InputStream executeShellCommand(String command) throws IOException {
+                String output = "";
+                if (command.equals("dumpsys backup")) {
+                    // output is empty already
+                    mIsDumpsysCommandCalled = true;
+                }
+                return new ByteArrayInputStream(output.getBytes("UTF-8"));
+            }
+        };
+
+        boolean isExceptionHappened = false;
+        try {
+            backupUtils.waitForBackupInitialization();
+        } catch (NullPointerException e) {
+            // null output by running command, dumpsys backup
+            isExceptionHappened = true;
+        }
+        assertTrue(isExceptionHappened);
+        assertTrue(mIsDumpsysCommandCalled);
+    }
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/CrashUtilsTest.java b/common/util/tests/src/com/android/compatibility/common/util/CrashUtilsTest.java
new file mode 100644
index 0000000..94f472e
--- /dev/null
+++ b/common/util/tests/src/com/android/compatibility/common/util/CrashUtilsTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2019 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.compatibility.common.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link CrashUtils}. */
+@RunWith(JUnit4.class)
+public class CrashUtilsTest {
+
+    private JSONArray mCrashes;
+
+    @Before
+    public void setUp() throws IOException {
+        try (BufferedReader txtReader =
+                new BufferedReader(
+                        new InputStreamReader(
+                                getClass().getClassLoader().getResourceAsStream("logcat.txt")))) {
+            StringBuffer input = new StringBuffer();
+            String tmp;
+            while ((tmp = txtReader.readLine()) != null) {
+                input.append(tmp + "\n");
+            }
+            mCrashes = CrashUtils.addAllCrashes(input.toString(), new JSONArray());
+        }
+    }
+
+    @Test
+    public void testGetAllCrashes() throws Exception {
+        JSONArray expectedResults = new JSONArray();
+        expectedResults.put(createCrashJson(11071, 11189, "AudioOut_D", 3912761344L, "SIGSEGV"));
+        expectedResults.put(createCrashJson(12736, 12761, "Binder:12736_2", 0L, "SIGSEGV"));
+        expectedResults.put(createCrashJson(26201, 26227, "Binder:26201_3", 0L, "SIGSEGV"));
+        expectedResults.put(createCrashJson(26246, 26282, "Binder:26246_5", 0L, "SIGSEGV"));
+        expectedResults.put(createCrashJson(245, 245, "installd", null, "SIGABRT"));
+        expectedResults.put(createCrashJson(6371, 8072, "media.codec", 3976200192L, "SIGSEGV"));
+        expectedResults.put(createCrashJson(8373, 8414, "loo", null, "SIGABRT"));
+
+        Assert.assertEquals(mCrashes.toString(), expectedResults.toString());
+    }
+
+    public JSONObject createCrashJson(
+            int pid, int tid, String name, Long faultaddress, String signal) {
+        JSONObject json = new JSONObject();
+        try {
+            json.put(CrashUtils.PID, pid);
+            json.put(CrashUtils.TID, tid);
+            json.put(CrashUtils.NAME, name);
+            json.put(CrashUtils.FAULT_ADDRESS, faultaddress);
+            json.put(CrashUtils.SIGNAL, signal);
+        } catch (JSONException e) {
+
+        }
+        return json;
+    }
+
+    @Test
+    public void testValidCrash() throws Exception {
+        Assert.assertTrue(CrashUtils.detectCrash(new String[] {"AudioOut_D"}, true, mCrashes));
+    }
+
+    @Test
+    public void testMissingName() throws Exception {
+        Assert.assertFalse(CrashUtils.detectCrash(new String[] {""}, true, mCrashes));
+    }
+
+    @Test
+    public void testSIGABRT() throws Exception {
+        Assert.assertFalse(CrashUtils.detectCrash(new String[] {"installd"}, true, mCrashes));
+    }
+
+    @Test
+    public void testFaultAddressBelowMin() throws Exception {
+        Assert.assertFalse(CrashUtils.detectCrash(new String[] {"Binder:12736_2"}, true, mCrashes));
+    }
+
+    @Test
+    public void testIgnoreMinAddressCheck() throws Exception {
+        Assert.assertTrue(CrashUtils.detectCrash(new String[] {"Binder:12736_2"}, false, mCrashes));
+    }
+
+    @Test
+    public void testGoodAndBadCrashes() throws Exception {
+        Assert.assertTrue(
+                CrashUtils.detectCrash(new String[] {"AudioOut_D", "generic"}, true, mCrashes));
+    }
+
+    @Test
+    public void testNullFaultAddress() throws Exception {
+        JSONArray crashes = new JSONArray();
+        crashes.put(createCrashJson(8373, 8414, "loo", null, "SIGSEGV"));
+        Assert.assertTrue(CrashUtils.detectCrash(new String[] {"loo"}, true, crashes));
+    }
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/LightInvocationResultTest.java b/common/util/tests/src/com/android/compatibility/common/util/LightInvocationResultTest.java
index 540c87e..8a87277 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/LightInvocationResultTest.java
+++ b/common/util/tests/src/com/android/compatibility/common/util/LightInvocationResultTest.java
@@ -40,7 +40,7 @@
     }
 
     public void testLightInvocationResultInstatiate() throws Exception {
-        File resultDir = ResultHandlerTest.writeResultDir(resultsDir);
+        File resultDir = ResultHandlerTest.writeResultDir(resultsDir, false);
         IInvocationResult fullResult = ResultHandler.getResultFromDir(resultDir);
         LightInvocationResult lightResult = new LightInvocationResult(fullResult);
         // Ensure that light result implementation does not use a reference to the full result
diff --git a/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java b/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
index 1529499..50a00f0 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
+++ b/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
@@ -148,6 +148,11 @@
             "          </Metric>\n" +
             "        </Summary>\n" +
             "      </Test>\n";
+    private static final String NEW_XML_TEST_RESULT =
+            "      <Test result=\"pass\" name=\"%s\">\n"
+                    + "        <Metric key=\"%s\">%s</Metric>\n"
+                    + "      </Test>\n";
+
     private File resultsDir = null;
     private File resultDir = null;
 
@@ -208,49 +213,59 @@
                 COMMAND_LINE_ARGS);
 
         // Parse the results and assert correctness
-        checkResult(ResultHandler.getResultFromDir(resultDir));
+        checkResult(ResultHandler.getResultFromDir(resultDir), false);
     }
 
     public void testParsing() throws Exception {
-        File resultDir = writeResultDir(resultsDir);
+        File resultDir = writeResultDir(resultsDir, false);
         // Parse the results and assert correctness
-        checkResult(ResultHandler.getResultFromDir(resultDir));
+        checkResult(ResultHandler.getResultFromDir(resultDir), false);
+    }
+
+    public void testParsing_newTestFormat() throws Exception {
+        File resultDir = writeResultDir(resultsDir, true);
+        // Parse the results and assert correctness
+        checkResult(ResultHandler.getResultFromDir(resultDir), true);
     }
 
     public void testParsing_usesUnalteredBuildFingerprintWhenPresent() throws Exception {
         String buildInfo = String.format(XML_BUILD_INFO_WITH_UNALTERED_BUILD_FINGERPRINT,
                 EXAMPLE_BUILD_FINGERPRINT, EXAMPLE_BUILD_FINGERPRINT_UNALTERED,
                 EXAMPLE_BUILD_ID, EXAMPLE_BUILD_PRODUCT);
-        File resultDir = writeResultDir(resultsDir, buildInfo);
-        checkResult(ResultHandler.getResultFromDir(resultDir), EXAMPLE_BUILD_FINGERPRINT_UNALTERED);
+        File resultDir = writeResultDir(resultsDir, buildInfo, false);
+        checkResult(
+                ResultHandler.getResultFromDir(resultDir),
+                EXAMPLE_BUILD_FINGERPRINT_UNALTERED,
+                false);
     }
 
     public void testParsing_whenUnalteredBuildFingerprintIsEmpty_usesRegularBuildFingerprint() throws Exception {
         String buildInfo = String.format(XML_BUILD_INFO_WITH_UNALTERED_BUILD_FINGERPRINT,
                 EXAMPLE_BUILD_FINGERPRINT, "", EXAMPLE_BUILD_ID, EXAMPLE_BUILD_PRODUCT);
-        File resultDir = writeResultDir(resultsDir, buildInfo);
-        checkResult(ResultHandler.getResultFromDir(resultDir), EXAMPLE_BUILD_FINGERPRINT);
+        File resultDir = writeResultDir(resultsDir, buildInfo, false);
+        checkResult(ResultHandler.getResultFromDir(resultDir), EXAMPLE_BUILD_FINGERPRINT, false);
     }
 
     public void testGetLightResults() throws Exception {
-        File resultDir = writeResultDir(resultsDir);
+        File resultDir = writeResultDir(resultsDir, false);
         List<IInvocationResult> lightResults = ResultHandler.getLightResults(resultsDir);
         assertEquals("Expected one result", 1, lightResults.size());
         IInvocationResult lightResult = lightResults.get(0);
         checkLightResult(lightResult);
     }
 
-    static File writeResultDir(File resultsDir) throws IOException {
+    static File writeResultDir(File resultsDir, boolean newTestFormat) throws IOException {
         String buildInfo = String.format(XML_BUILD_INFO, EXAMPLE_BUILD_FINGERPRINT,
                 EXAMPLE_BUILD_ID, EXAMPLE_BUILD_PRODUCT);
-        return writeResultDir(resultsDir, buildInfo);
+        return writeResultDir(resultsDir, buildInfo, newTestFormat);
     }
 
     /*
      * Helper to write a result to the results dir, for testing.
      * @return the written resultDir
      */
-    static File writeResultDir(File resultsDir, String buildInfo) throws IOException {
+    static File writeResultDir(File resultsDir, String buildInfo, boolean newTestFormat)
+            throws IOException {
         File resultDir = null;
         FileWriter writer = null;
         try {
@@ -265,9 +280,26 @@
                     moduleACases);
             String moduleBTest3 = String.format(XML_TEST_FAIL, METHOD_3, MESSAGE, STACK_TRACE,
                     BUG_REPORT, LOGCAT, SCREENSHOT);
-            String moduleBTest4 = String.format(XML_TEST_RESULT, METHOD_4, SUMMARY_SOURCE,
-                    SUMMARY_MESSAGE, ResultType.HIGHER_BETTER.toReportString(),
-                    ResultUnit.SCORE.toReportString(), Double.toString(SUMMARY_VALUE));
+            String moduleBTest4 = "";
+            if (newTestFormat) {
+                moduleBTest4 =
+                        String.format(
+                                NEW_XML_TEST_RESULT,
+                                METHOD_4,
+                                SUMMARY_MESSAGE,
+                                Double.toString(SUMMARY_VALUE));
+            } else {
+                moduleBTest4 =
+                        String.format(
+                                XML_TEST_RESULT,
+                                METHOD_4,
+                                SUMMARY_SOURCE,
+                                SUMMARY_MESSAGE,
+                                ResultType.HIGHER_BETTER.toReportString(),
+                                ResultUnit.SCORE.toReportString(),
+                                Double.toString(SUMMARY_VALUE));
+            }
+
             String moduleBTest5 = String.format(XML_TEST_SKIP, METHOD_5);
             String moduleBTests = String.join("", moduleBTest3, moduleBTest4, moduleBTest5);
             String moduleBCases = String.format(XML_CASE, CLASS_B, moduleBTests);
@@ -314,11 +346,13 @@
         assertEquals("Expected 2 total modules", 2, modules.size());
     }
 
-    static void checkResult(IInvocationResult result) throws Exception {
-        checkResult(result, EXAMPLE_BUILD_FINGERPRINT);
+    static void checkResult(IInvocationResult result, boolean newTestFormat) throws Exception {
+        checkResult(result, EXAMPLE_BUILD_FINGERPRINT, newTestFormat);
     }
 
-    static void checkResult(IInvocationResult result, String expectedBuildFingerprint) throws Exception {
+    static void checkResult(
+            IInvocationResult result, String expectedBuildFingerprint, boolean newTestFormat)
+            throws Exception {
         assertEquals("Expected 3 passes", 3, result.countResults(TestStatus.PASS));
         assertEquals("Expected 1 failure", 1, result.countResults(TestStatus.FAIL));
 
@@ -392,16 +426,19 @@
         assertNull("Unexpected screenshot", moduleBTest4.getScreenshot());
         assertNull("Unexpected message", moduleBTest4.getMessage());
         assertNull("Unexpected stack trace", moduleBTest4.getStackTrace());
-        ReportLog report = moduleBTest4.getReportLog();
-        assertNotNull("Expected report", report);
-        ReportLog.Metric summary = report.getSummary();
-        assertNotNull("Expected report summary", summary);
-        assertEquals("Incorrect source", SUMMARY_SOURCE, summary.getSource());
-        assertEquals("Incorrect message", SUMMARY_MESSAGE, summary.getMessage());
-        assertEquals("Incorrect type", ResultType.HIGHER_BETTER, summary.getType());
-        assertEquals("Incorrect unit", ResultUnit.SCORE, summary.getUnit());
-        assertTrue("Incorrect values", Arrays.equals(new double[] { SUMMARY_VALUE },
-                summary.getValues()));
+        if (!newTestFormat) {
+            ReportLog report = moduleBTest4.getReportLog();
+            assertNotNull("Expected report", report);
+            ReportLog.Metric summary = report.getSummary();
+            assertNotNull("Expected report summary", summary);
+            assertEquals("Incorrect source", SUMMARY_SOURCE, summary.getSource());
+            assertEquals("Incorrect message", SUMMARY_MESSAGE, summary.getMessage());
+            assertEquals("Incorrect type", ResultType.HIGHER_BETTER, summary.getType());
+            assertEquals("Incorrect unit", ResultUnit.SCORE, summary.getUnit());
+            assertTrue(
+                    "Incorrect values",
+                    Arrays.equals(new double[] {SUMMARY_VALUE}, summary.getValues()));
+        }
         ITestResult moduleBTest5 = moduleBResults.get(2);
         assertEquals("Incorrect name", METHOD_5, moduleBTest5.getName());
         assertEquals("Incorrect result", TestStatus.PASS, moduleBTest5.getResultStatus());
diff --git a/common/util/tests/src/com/android/compatibility/common/util/UnitTests.java b/common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
index 14ca1cd..a7f8460 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
+++ b/common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
@@ -26,8 +26,10 @@
  */
 @RunWith(Suite.class)
 @SuiteClasses({
+    BackupUtilsTest.class,
     BusinessLogicTest.class,
     CaseResultTest.class,
+    CrashUtilsTest.class,
     DynamicConfigTest.class,
     LightInvocationResultTest.class,
     MetricsXmlSerializerTest.class,
diff --git a/tools/Android.mk b/tools/Android.mk
deleted file mode 100644
index 9af9f44..0000000
--- a/tools/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-include $(call all-subdir-makefiles)
diff --git a/tools/cts-instant-tradefed/Android.bp b/tools/cts-instant-tradefed/Android.bp
new file mode 100644
index 0000000..cbdb32f
--- /dev/null
+++ b/tools/cts-instant-tradefed/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2018 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.
+
+java_library_host {
+    name: "cts-instant-resources",
+
+    java_resource_dirs: ["res"],
+}
+
+tradefed_binary_host {
+    name: "cts-instant-tradefed",
+    defaults: ["cts_error_prone_rules"],
+    wrapper: "etc/cts-instant-tradefed",
+    short_name: "CTS_INSTANT",
+    full_name: "Compatibility Test Suite for Instant Apps",
+    version: "9.0_r2",
+
+    static_libs: [
+        "cts-instant-resources",
+        "cts-tradefed-harness",
+    ],
+}
diff --git a/tools/cts-instant-tradefed/Android.mk b/tools/cts-instant-tradefed/Android.mk
deleted file mode 100644
index c6c89a0..0000000
--- a/tools/cts-instant-tradefed/Android.mk
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright (C) 2018 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_JAVA_RESOURCE_DIRS := res
-
-LOCAL_SUITE_BUILD_NUMBER := $(BUILD_NUMBER_FROM_FILE)
-LOCAL_SUITE_TARGET_ARCH := $(TARGET_ARCH)
-LOCAL_SUITE_NAME := CTS_INSTANT
-LOCAL_SUITE_FULLNAME := "Compatibility Test Suite for Instant Apps"
-LOCAL_SUITE_VERSION := 9.0_r2
-LOCAL_STATIC_JAVA_LIBRARIES += cts-tradefed-harness
-
-LOCAL_MODULE := cts-instant-tradefed
-LOCAL_COMPATIBILITY_SUITE := cts_instant
-include cts/error_prone_rules.mk
-include $(BUILD_COMPATIBILITY_SUITE)
-
-# Build all sub-directories
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/cts-instant-tradefed/etc/Android.mk b/tools/cts-instant-tradefed/etc/Android.mk
deleted file mode 100644
index 2d3c098..0000000
--- a/tools/cts-instant-tradefed/etc/Android.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright (C) 2018 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 := cts-instant-tradefed
-include $(BUILD_HOST_PREBUILT)
-
diff --git a/tools/cts-instant-tradefed/etc/cts-instant-tradefed b/tools/cts-instant-tradefed/etc/cts-instant-tradefed
index 687eabb..e927b1c 100755
--- a/tools/cts-instant-tradefed/etc/cts-instant-tradefed
+++ b/tools/cts-instant-tradefed/etc/cts-instant-tradefed
@@ -93,8 +93,7 @@
   cts-instant-tradefed
   cts-instant-tradefed-tests
   compatibility-common-util-tests
-  compatibility-tradefed-tests
-  host-libprotobuf-java-full"
+  compatibility-tradefed-tests"
 
 for JAR in $JARS; do
     checkFile ${JAR_DIR}/${JAR}.jar
diff --git a/tools/cts-instant-tradefed/res/config/retry.xml b/tools/cts-instant-tradefed/res/config/retry.xml
index fe6c6ba..0a01dc3 100644
--- a/tools/cts-instant-tradefed/res/config/retry.xml
+++ b/tools/cts-instant-tradefed/res/config/retry.xml
@@ -13,55 +13,11 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<configuration description="Runs a retry of a previous CTS instant session.">
-    <option name="dynamic-sharding" value="true" />
-    <option name="disable-strict-sharding" value="true" />
-
-    <device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" />
-    <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
-    <test class="com.android.compatibility.common.tradefed.testtype.retry.RetryFactoryTest" />
-
-    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:rerun-from-file:true" />
-    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:fallback-to-serial-rerun:false" />
-    <option name="compatibility:test-arg" value="com.android.compatibility.testtype.LibcoreTest:rerun-from-file:true" />
-    <option name="compatibility:test-arg" value="com.android.compatibility.testtype.LibcoreTest:fallback-to-serial-rerun:false" />
+<configuration description="Runs a retry of a previous CTS session.">
+    <object type="previous_loader" class="com.android.compatibility.common.tradefed.result.suite.PreviousResultLoader" />
+    <test class="com.android.tradefed.testtype.suite.retry.RetryRescheduler" />
 
     <logger class="com.android.tradefed.log.FileLogger">
         <option name="log-level-display" value="WARN" />
     </logger>
-
-    <include name="cts-preconditions" />
-    <include name="cts-system-checkers" />
-    <include name="cts-known-failures" />
-    <include name="cts-exclude" />
-
-    <!-- Test instant apps only for the primary API -->
-    <option name="compatibility:primary-abi-only" value="true" />
-
-    <!-- Include cts-instant setup -->
-    <include name="cts-instant-option-setup" />
-    <!-- Do not include cts-instant-filter-setup. Otherwise retry includes all instant tests.  -->
-
-    <option name="plan" value="cts-instant-retry" />
-    <option name="test-tag" value="cts-instant" />
-    <option name="enable-root" value="false" />
-
-    <!-- retain 200MB of host log -->
-    <option name="max-log-size" value="200" />
-    <!--  retain 200MB of logcat -->
-    <option name="max-tmp-logcat-file" value="209715200" />
-
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command" value="settings put global package_verifier_enable 0" />
-        <option name="teardown-command" value="settings put global package_verifier_enable 1"/>
-    </target_preparer>
-
-    <result_reporter class="com.android.compatibility.common.tradefed.result.ConsoleReporter" />
-    <result_reporter class="com.android.compatibility.common.tradefed.result.ResultReporter" />
-
-    <template-include name="reporters" default="basic-reporters" />
-
-    <!-- Include additional test metadata output. -->
-    <template-include name="metadata-reporters" default="empty" />
-
 </configuration>
diff --git a/tools/cts-instant-tradefed/tests/Android.bp b/tools/cts-instant-tradefed/tests/Android.bp
new file mode 100644
index 0000000..e70c85a
--- /dev/null
+++ b/tools/cts-instant-tradefed/tests/Android.bp
@@ -0,0 +1,24 @@
+// Copyright (C) 2018 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.
+
+java_test_host {
+    name: "cts-instant-tradefed-tests",
+
+    srcs: ["src/**/*.java"],
+
+    libs: [
+        "tradefed",
+        "cts-instant-tradefed",
+    ],
+}
diff --git a/tools/cts-instant-tradefed/tests/Android.mk b/tools/cts-instant-tradefed/tests/Android.mk
deleted file mode 100644
index f3b76a6..0000000
--- a/tools/cts-instant-tradefed/tests/Android.mk
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE := cts-instant-tradefed-tests
-LOCAL_MODULE_TAGS := optional
-LOCAL_JAVA_LIBRARIES := tradefed cts-instant-tradefed
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/cts-instant-tradefed/tests/run_tests.sh b/tools/cts-instant-tradefed/tests/run_tests.sh
deleted file mode 100755
index 5ef31fc..0000000
--- a/tools/cts-instant-tradefed/tests/run_tests.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/bash
-
-# Copyright (C) 2018 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.
-
-# Helper script for running unit tests for compatibility libraries
-
-# TODO: Change it to the new harness repo once harness code is moved
-# to a new repo.
-CTS_DIR=$(dirname ${0})/../../..
-source ${CTS_DIR}/test_defs.sh
-
-JARS="
-    compatibility-common-util-hostsidelib\
-    compatibility-host-util\
-    cts-instant-tradefed-tests\
-    cts-instant-tradefed"
-
-run_tests "com.android.compatibility.tradefed.CtsInstantTradefedTest" "${JARS}" "${@}"
diff --git a/tools/cts-tradefed/Android.bp b/tools/cts-tradefed/Android.bp
new file mode 100644
index 0000000..728247b
--- /dev/null
+++ b/tools/cts-tradefed/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2015 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.
+
+java_library_host {
+    name: "cts-tradefed-harness",
+
+    java_resource_dirs: ["res"],
+    libs: [
+        "tradefed",
+        "compatibility-host-util",
+    ],
+    static_libs: [
+        "compatibility-tradefed",
+    ],
+}
+
+tradefed_binary_host {
+    name: "cts-tradefed",
+    wrapper: "etc/cts-tradefed",
+    short_name: "CTS",
+    full_name: "Compatibility Test Suite",
+    version: "9.0_r1",
+    static_libs: ["cts-tradefed-harness"],
+    required: ["compatibility-host-util"],
+}
diff --git a/tools/cts-tradefed/Android.mk b/tools/cts-tradefed/Android.mk
deleted file mode 100644
index 62fc414..0000000
--- a/tools/cts-tradefed/Android.mk
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright (C) 2015 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, ../../common/host-side/tradefed/src)
-
-LOCAL_JAVA_RESOURCE_DIRS := res
-LOCAL_JAVA_RESOURCE_DIRS += ../../common/host-side/tradefed/res
-LOCAL_MODULE := cts-tradefed-harness
-LOCAL_JAVA_LIBRARIES += tradefed compatibility-host-util
-LOCAL_STATIC_JAVA_LIBRARIES := google-api-java-client-min-repackaged
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-include $(CLEAR_VARS)
-LOCAL_SUITE_BUILD_NUMBER := $(BUILD_NUMBER_FROM_FILE)
-LOCAL_SUITE_TARGET_ARCH := $(TARGET_ARCH)
-LOCAL_SUITE_NAME := CTS
-LOCAL_SUITE_FULLNAME := "Compatibility Test Suite"
-LOCAL_SUITE_VERSION := 9.0_r1
-LOCAL_STATIC_JAVA_LIBRARIES += cts-tradefed-harness
-
-LOCAL_MODULE := cts-tradefed
-LOCAL_COMPATIBILITY_SUITE := general-tests
-include cts/error_prone_rules.mk
-include $(BUILD_COMPATIBILITY_SUITE)
-
-# Build all sub-directories
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/cts-tradefed/DynamicConfig.xml b/tools/cts-tradefed/DynamicConfig.xml
index 009e2aa..60b0e98 100644
--- a/tools/cts-tradefed/DynamicConfig.xml
+++ b/tools/cts-tradefed/DynamicConfig.xml
@@ -13,6 +13,7 @@
      limitations under the License.
 -->
 
+<!--TODO(b/117957288): Remove dynamic config from suite-level.-->
 <dynamicConfig>
     <entry key="media_files_url">
          <value>https://dl.google.com/dl/android/cts/android-cts-media-1.4.zip</value>
diff --git a/tools/cts-tradefed/etc/Android.mk b/tools/cts-tradefed/etc/Android.mk
deleted file mode 100644
index 1c376db..0000000
--- a/tools/cts-tradefed/etc/Android.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright (C) 2015 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 := cts-tradefed
-include $(BUILD_HOST_PREBUILT)
-
diff --git a/tools/cts-tradefed/etc/cts-tradefed b/tools/cts-tradefed/etc/cts-tradefed
index 5b70020..80f9c33 100755
--- a/tools/cts-tradefed/etc/cts-tradefed
+++ b/tools/cts-tradefed/etc/cts-tradefed
@@ -93,8 +93,7 @@
   cts-tradefed
   cts-tradefed-tests
   compatibility-common-util-tests
-  compatibility-tradefed-tests
-  host-libprotobuf-java-full"
+  compatibility-tradefed-tests"
 
 for JAR in $JARS; do
     checkFile ${JAR_DIR}/${JAR}.jar
@@ -107,9 +106,12 @@
   google-tradefed-tests
   google-tf-prod-tests"
 
+STANDALONE_JAR_DIR=${ANDROID_HOST_OUT}/framework
 for JAR in $OPTIONAL_JARS; do
     if [ -f "${JAR_DIR}/${JAR}.jar" ]; then
         JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}.jar
+    elif [ -f "${STANDALONE_JAR_DIR}/${JAR}.jar" ]; then
+        JAR_PATH=${JAR_PATH}:${STANDALONE_JAR_DIR}/${JAR}.jar
     fi;
 done
 
@@ -128,5 +130,5 @@
     JAR_PATH=${JAR_PATH}:$j
 done
 
-java $RDBG_FLAG -Xmx4g -XX:+HeapDumpOnOutOfMemoryError -cp ${JAR_PATH} -DCTS_ROOT=${CTS_ROOT} com.android.compatibility.common.tradefed.command.CompatibilityConsole "$@"
+java $RDBG_FLAG -Xmx6g -XX:+HeapDumpOnOutOfMemoryError -cp ${JAR_PATH} -DCTS_ROOT=${CTS_ROOT} com.android.compatibility.common.tradefed.command.CompatibilityConsole "$@"
 
diff --git a/tools/cts-tradefed/res/config/cts-exclude.xml b/tools/cts-tradefed/res/config/cts-exclude.xml
index 240ffa1..e47b303 100644
--- a/tools/cts-tradefed/res/config/cts-exclude.xml
+++ b/tools/cts-tradefed/res/config/cts-exclude.xml
@@ -18,4 +18,10 @@
     <option name="compatibility:exclude-filter" value="CtsSecurityHostTestCases android.security.cts.SELinuxHostTest#testNoExemptionsForBinderInVendorBan" />
     <option name="compatibility:exclude-filter" value="CtsSecurityHostTestCases android.security.cts.SELinuxHostTest#testNoExemptionsForSocketsBetweenCoreAndVendorBan" />
     <option name="compatibility:exclude-filter" value="CtsSecurityHostTestCases android.security.cts.SELinuxHostTest#testNoExemptionsForVendorExecutingCore" />
+
+    <!-- Test Harness Mode tests are not a part of CTS. They are a part
+         of their own testing plan, as they reset the device during the
+         test. It's possible and ideal in the future to incorporate the
+         tests into CTS, but until then, they should be excluded. -->
+    <option name="compatibility:exclude-filter" value="CtsTestHarnessModeTestCases" />
 </configuration>
diff --git a/tools/cts-tradefed/res/config/cts-instant-filter-setup.xml b/tools/cts-tradefed/res/config/cts-instant-filter-setup.xml
index abbe6f9..f706ed4 100644
--- a/tools/cts-tradefed/res/config/cts-instant-filter-setup.xml
+++ b/tools/cts-tradefed/res/config/cts-instant-filter-setup.xml
@@ -156,7 +156,7 @@
     <!-- CtsUiAutomationTestCases -->
     <option name="compatibility:include-filter" value="CtsUiAutomationTestCases" />
 
-    <!-- CtsActivityManagerDeviceSdk25TestCases -->
+    <!-- CtsWindowManagerSdk25TestCases -->
     <!-- These tests require targeting API 25 which does not support instant apps -->
 
     <!-- CtsHostsideWebViewTests -->
@@ -174,9 +174,6 @@
     <!-- CtsUsageStatsTestCases -->
     <option name="compatibility:include-filter" value="CtsUsageStatsTestCases" />
 
-    <!-- CtsActivityManagerDeviceTestCases -->
-    <!-- This module needs a permission not available to instant apps -->
-
     <!-- CtsLocationTestCases -->
     <option name="compatibility:include-filter" value="CtsLocationTestCases" />
 
diff --git a/tools/cts-tradefed/res/config/cts-instant-option-setup.xml b/tools/cts-tradefed/res/config/cts-instant-option-setup.xml
index 8db665a..2beaa51 100644
--- a/tools/cts-tradefed/res/config/cts-instant-option-setup.xml
+++ b/tools/cts-tradefed/res/config/cts-instant-option-setup.xml
@@ -157,7 +157,7 @@
     <!-- CtsUiAutomationTestCases -->
     <option name="compatibility:module-arg" value="CtsUiAutomationTestCases:instant-mode:true" />
 
-    <!-- CtsActivityManagerDeviceSdk25TestCases -->
+    <!-- CtsWindowManagerSdk25TestCases -->
     <!-- These tests require targeting API 25 which does not support instant apps -->
 
     <!-- CtsHostsideWebViewTests -->
@@ -175,9 +175,6 @@
     <!-- CtsUsageStatsTestCases -->
     <option name="compatibility:module-arg" value="CtsUsageStatsTestCases:instant-mode:true" />
 
-    <!-- CtsActivityManagerDeviceTestCases -->
-    <!-- This module needs a permission not available to instant apps -->
-
     <!-- CtsLocationTestCases -->
     <option name="compatibility:module-arg" value="CtsLocationTestCases:instant-mode:true" />
 
@@ -197,6 +194,18 @@
     <!-- CtsOsHostTestCases -->
     <option name="compatibility:module-arg" value="CtsOsHostTestCases:instant-mode:true" />
 
+    <!-- CtsOmapiTestCases -->
+    <option name="compatibility:module-arg" value="CtsOmapiTestCases:instant-mode:true" />
+
+    <!-- signed-CtsSecureElementAccessControlTestCases1 -->
+    <option name="compatibility:module-arg" value="signed-CtsSecureElementAccessControlTestCases1:instant-mode:true" />
+
+    <!-- signed-CtsSecureElementAccessControlTestCases2 -->
+    <option name="compatibility:module-arg" value="signed-CtsSecureElementAccessControlTestCases2:instant-mode:true" />
+
+    <!-- signed-CtsSecureElementAccessControlTestCases3 -->
+    <option name="compatibility:module-arg" value="signed-CtsSecureElementAccessControlTestCases3:instant-mode:true" />
+
     <!-- CtsEdiHostTestCases -->
     <!-- Included not for instant-app but to collect the required information for the run -->
 </configuration>
diff --git a/tools/cts-tradefed/res/config/cts-jvmti.xml b/tools/cts-tradefed/res/config/cts-jvmti.xml
index 6a48d81..ce60582 100644
--- a/tools/cts-tradefed/res/config/cts-jvmti.xml
+++ b/tools/cts-tradefed/res/config/cts-jvmti.xml
@@ -107,6 +107,7 @@
     <option name="compatibility:include-filter" value="CtsJvmtiRunTest1941HostTestCases" />
     <option name="compatibility:include-filter" value="CtsJvmtiRunTest1942HostTestCases" />
     <option name="compatibility:include-filter" value="CtsJvmtiRunTest1943HostTestCases" />
+    <option name="compatibility:include-filter" value="CtsJvmtiRunTest1953HostTestCases" />
     <option name="compatibility:include-filter" value="CtsJvmtiTaggingHostTestCases" />
     <option name="compatibility:include-filter" value="CtsJvmtiTrackingHostTestCases" />
 
diff --git a/tools/cts-tradefed/res/config/cts-known-failures.xml b/tools/cts-tradefed/res/config/cts-known-failures.xml
index 1502bd4..853315e 100755
--- a/tools/cts-tradefed/res/config/cts-known-failures.xml
+++ b/tools/cts-tradefed/res/config/cts-known-failures.xml
@@ -22,10 +22,6 @@
     <!-- b/38182235 -->
     <option name="compatibility:exclude-filter" value="CtsLocationTestCases android.location.cts.GnssTtffTests#testTtffWithNetwork" />
 
-    <!-- b/35314835 -->
-    <option name="compatibility:exclude-filter" value="CtsActivityManagerDeviceTestCases android.server.am.ActivityManagerPinnedStackTests#testAlwaysFocusablePipActivity" />
-    <option name="compatibility:exclude-filter" value="CtsActivityManagerDeviceTestCases android.server.am.ActivityManagerPinnedStackTests#testLaunchIntoPinnedStack" />
-
     <!-- b/17989532 -->
     <option name="compatibility:exclude-filter" value="CtsCameraTestCases android.hardware.camera2.cts.SurfaceViewPreviewTest#testPreparePerformance" />
 
@@ -125,29 +121,9 @@
     <option name="compatibility:exclude-filter" value="CtsVideoTestCases android.video.cts.VideoEncoderDecoderTest#testVp9Goog0Perf1280x0720" />
     <option name="compatibility:exclude-filter" value="CtsVideoTestCases android.video.cts.VideoEncoderDecoderTest#testVp9Goog0Perf1920x1080" />
 
-    <!-- b/37482372 -->
-    <option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityFlowPortraitTest#multiWindowHistoryPreservePortraitTest" />
-    <option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityFlowPortraitTest#multiWindowInOutPortraitTest" />
-    <option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityFlowPortraitTest#multiWindowInitialHeaderOnBackPortraitTest" />
-    <option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityFlowPortraitTest#multiWindowInnerFragmentInOutPortraitTest" />
-    <option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityFlowPortraitTest#startWithFragmentAndInitTitleMultiWindowPortraitTest" />
-    <option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityFlowPortraitTest#startWithFragmentNoHeadersMultiWindowPortraitTest" />
-
     <!-- b/63916274 -->
     <option name="compatibility:exclude-filter" value="CtsTelecomTestCases android.telecom.cts.WiredHeadsetTest" />
 
-    <!-- b/38177396 -->
-    <option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityFlowLandscapeTest#multiWindowHistoryPreserveLandscapeTest" />
-    <option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityFlowLandscapeTest#multiWindowInOutLandscapeTest" />
-    <option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityFlowLandscapeTest#multiWindowInitialHeaderOnBackLandscapeTest" />
-    <option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityFlowLandscapeTest#multiWindowInnerFragmentInOutLandscapeTest" />
-    <option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityFlowLandscapeTest#startWithFragmentAndInitTitleMultiWindowLandscapeTest" />
-    <option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityFlowLandscapeTest#startWithFragmentNoHeadersMultiWindowLandscapeTest" />
-
-    <!-- b/62924649 -->
-    <option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityLegacyFlowTest#legacyActivityMultiWindowTest" />
-    <option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityLegacyFlowTest#legacyActivityMultiWindowToggleTest" />
-
     <!-- b/38224690 -->
     <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.NativeEncoderTest" />
 
@@ -209,10 +185,16 @@
     <!-- b/110354076 -->
     <option name="compatibility:exclude-filter" value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.DeviceOwnerTest#testCreateAndManageUser_DontSkipSetupWizard" />
 
-    <!-- b/112125308 -->
-    <option name="compatibility:exclude-filter" value="CtsActivityManagerDeviceTestCases android.server.am.ActivityManagerActivityVisibilityTests#testTurnScreenOnAttrNoLockScreen" />
+    <!-- b/123280814 -->
+    <option name="compatibility:exclude-filter" value="CtsLocation2TestCases android.location2.cts.LocationManagerTest#testGetCoarseLocationUpdates_withListener" />
+    <option name="compatibility:exclude-filter" value="CtsLocation2TestCases android.location2.cts.LocationManagerTest#testGetNetworkProviderLocationUpdates_withListener" />
 
-    <!-- b/112688380 -->
-    <option name="compatibility:exclude-filter" value="CtsActivityManagerDeviceTestCases android.server.am.ActivityManagerAppConfigurationTests#testAppOrientationRequestConfigClears" />
-    <option name="compatibility:exclude-filter" value="CtsActivityManagerDeviceTestCases android.server.am.ActivityManagerAppConfigurationTests#testTaskCloseRestoreFreeOrientation" />
+    <!-- b/116002979 -->
+    <option name="compatibility:exclude-filter" value="CtsSecurityTestCases android.security.cts.ListeningPortsTest" />
+
+    <!-- b/129859594 -->
+    <option name="compatibility:exclude-filter" value="CtsAtomicInstallTestCases com.android.tests.atomicinstall.AtomicInstallTest#testFailInconsistentMultiPackageCommit" />
+
+    <!-- b/126548816 -->
+    <option name="compatibility:exclude-filter" value="CtsRcsTestCases" />
 </configuration>
diff --git a/tools/cts-tradefed/res/config/cts-suite.xml b/tools/cts-tradefed/res/config/cts-suite.xml
index f04b25b..080f8f1 100644
--- a/tools/cts-tradefed/res/config/cts-suite.xml
+++ b/tools/cts-tradefed/res/config/cts-suite.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<configuration description="Runs CTS as a suite">
+<configuration description="Runs CTS as a suite. Configuration for testing new changes.">
     <!-- running config -->
     <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
     <test class="com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite">
@@ -65,10 +65,6 @@
     <template-include name="metadata-reporters" default="empty" />
 
     <result_reporter class="com.android.compatibility.common.tradefed.result.ConsoleReporter" />
-    <result_reporter class="com.android.compatibility.common.tradefed.result.ResultReporter" />
-    <result_reporter class="com.android.compatibility.common.tradefed.result.suite.CertificationSuiteResultReporter">
-        <!-- Avoid double posting from this reporter until ResultReporter is removed -->
-        <option name="disable-result-posting" value="true" />
-    </result_reporter>
-
+    <result_reporter class="com.android.compatibility.common.tradefed.result.suite.CompatibilityProtoResultReporter" />
+    <result_reporter class="com.android.compatibility.common.tradefed.result.suite.CertificationSuiteResultReporter" />
 </configuration>
diff --git a/tools/cts-tradefed/res/config/cts-system-checkers.xml b/tools/cts-tradefed/res/config/cts-system-checkers.xml
index b3adb69..7639bf9 100644
--- a/tools/cts-tradefed/res/config/cts-system-checkers.xml
+++ b/tools/cts-tradefed/res/config/cts-system-checkers.xml
@@ -14,8 +14,17 @@
      limitations under the License.
 -->
 <configuration description="CTS system checker configs">
+    <system_checker class="com.android.tradefed.suite.checker.UserChecker" />
     <system_checker class="com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker" />
+    <system_checker class="com.android.tradefed.suite.checker.ShellStatusChecker" />
+    <system_checker class="com.android.tradefed.suite.checker.EnforcedSeLinuxChecker">
+        <!-- We expect selinux enforced for CTS -->
+        <option name="expect-enforced" value="true" />
+    </system_checker>
     <system_checker class="com.android.tradefed.suite.checker.KeyguardStatusChecker" />
+    <system_checker class="com.android.tradefed.suite.checker.LeakedThreadStatusChecker" />
+    <system_checker class="com.android.tradefed.suite.checker.TimeStatusChecker" />
+    <system_checker class="com.android.tradefed.suite.checker.DeviceSettingChecker" />
     <system_checker class="com.android.tradefed.suite.checker.SystemServerStatusChecker" />
     <system_checker class="com.android.tradefed.suite.checker.SystemServerFileDescriptorChecker" />
 </configuration>
diff --git a/tools/cts-tradefed/res/config/cts-virtual-device.xml b/tools/cts-tradefed/res/config/cts-virtual-device.xml
index 10945f0..697ee2f 100644
--- a/tools/cts-tradefed/res/config/cts-virtual-device.xml
+++ b/tools/cts-tradefed/res/config/cts-virtual-device.xml
@@ -17,9 +17,20 @@
 
     <include name="cts-automated" />
 
+    <!-- Tell all AndroidJUnitTests to exclude certain annotations -->
+    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:exclude-annotation:android.support.test.filters.RequiresDevice" />
+    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:exclude-annotation:androidx.test.filters.RequiresDevice" />
+    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:exclude-annotation:android.platform.test.annotations.RequiresDevice" />
+
+    <!-- Tell all HostTests to exclude certain annotations -->
+    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.HostTest:exclude-annotation:android.platform.test.annotations.RequiresDevice" />
+    <option name="compatibility:test-arg" value="com.android.compatibility.common.tradefed.testtype.JarHostTest:exclude-annotation:android.platform.test.annotations.RequiresDevice" />
+
     <!-- add per module rules for virtual devices below -->
-    <option name="compatibility:module-arg" value="CtsDevicePolicyManagerTestCases:exclude-annotation:android.platform.test.annotations.RequiresDevice" />
     <option name="compatibility:module-arg" value="CtsDeqpTestCases:include-filter:dEQP-GLES2.functional.prerequisite#*" />
     <option name="compatibility:module-arg" value="CtsDeqpTestCases:include-filter:dEQP-EGL.*" />
     <option name="compatibility:module-arg" value="CtsLibcoreTestCases:core-expectation:/virtualdeviceknownfailures.txt" />
+
+    <!-- Virtual devices usually run as root -->
+    <option name="compatibility:skip-system-status-check" value="com.android.tradefed.suite.checker.ShellStatusChecker" />
 </configuration>
diff --git a/tools/cts-tradefed/res/config/retry.xml b/tools/cts-tradefed/res/config/retry.xml
index 76d5ba9..0a01dc3 100644
--- a/tools/cts-tradefed/res/config/retry.xml
+++ b/tools/cts-tradefed/res/config/retry.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!-- Copyright (C) 2018 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.
@@ -14,48 +14,10 @@
      limitations under the License.
 -->
 <configuration description="Runs a retry of a previous CTS session.">
-    <option name="dynamic-sharding" value="true" />
-    <option name="disable-strict-sharding" value="true" />
-
-    <device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" />
-    <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
-    <test class="com.android.compatibility.common.tradefed.testtype.retry.RetryFactoryTest" />
-
-    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:rerun-from-file:true" />
-    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:fallback-to-serial-rerun:false" />
-    <option name="compatibility:test-arg" value="com.android.compatibility.testtype.LibcoreTest:rerun-from-file:true" />
-    <option name="compatibility:test-arg" value="com.android.compatibility.testtype.LibcoreTest:fallback-to-serial-rerun:false" />
+    <object type="previous_loader" class="com.android.compatibility.common.tradefed.result.suite.PreviousResultLoader" />
+    <test class="com.android.tradefed.testtype.suite.retry.RetryRescheduler" />
 
     <logger class="com.android.tradefed.log.FileLogger">
         <option name="log-level-display" value="WARN" />
     </logger>
-
-    <include name="cts-preconditions" />
-    <include name="cts-system-checkers" />
-    <include name="cts-known-failures" />
-    <include name="cts-exclude" />
-    <include name="cts-exclude-instant" />
-
-    <option name="plan" value="cts-retry" />
-    <option name="test-tag" value="cts" />
-    <option name="enable-root" value="false" />
-
-    <!-- retain 200MB of host log -->
-    <option name="max-log-size" value="200" />
-    <!--  retain 200MB of logcat -->
-    <option name="max-tmp-logcat-file" value="209715200" />
-
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command" value="settings put global package_verifier_enable 0" />
-        <option name="teardown-command" value="settings put global package_verifier_enable 1"/>
-    </target_preparer>
-
-    <result_reporter class="com.android.compatibility.common.tradefed.result.ConsoleReporter" />
-    <result_reporter class="com.android.compatibility.common.tradefed.result.ResultReporter" />
-
-    <template-include name="reporters" default="basic-reporters" />
-
-    <!-- Include additional test metadata output. -->
-    <template-include name="metadata-reporters" default="empty" />
-
 </configuration>
diff --git a/tools/cts-tradefed/tests/Android.bp b/tools/cts-tradefed/tests/Android.bp
new file mode 100644
index 0000000..0d0bcea
--- /dev/null
+++ b/tools/cts-tradefed/tests/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2015 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.
+
+java_library_host {
+    name: "cts-tradefed-tests",
+
+    srcs: ["src/**/*.java"],
+
+    libs: [
+        "tradefed",
+        "cts-tradefed",
+    ],
+    // We ship the Deqp Runner tests with the CTS one to validate them.
+    static_libs: ["CtsDeqpRunnerTests"],
+}
diff --git a/tools/cts-tradefed/tests/Android.mk b/tools/cts-tradefed/tests/Android.mk
deleted file mode 100644
index 2a391c0..0000000
--- a/tools/cts-tradefed/tests/Android.mk
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE := cts-tradefed-tests
-LOCAL_MODULE_TAGS := optional
-LOCAL_JAVA_LIBRARIES := tradefed cts-tradefed
-# We ship the Deqp Runner tests with the CTS one to validate them.
-LOCAL_STATIC_JAVA_LIBRARIES := CtsDeqpRunnerTests
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/cts-tradefed/tests/run_cts_tests.sh b/tools/cts-tradefed/tests/run_cts_tests.sh
new file mode 100755
index 0000000..428b9ec
--- /dev/null
+++ b/tools/cts-tradefed/tests/run_cts_tests.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+# Copyright (C) 2018 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.
+
+# A simple helper script that runs the CTS harness unit tests
+
+CTS_DIR=`dirname $0`/../etc
+
+${CTS_DIR}/cts-tradefed run singleCommand host -n \
+  --console-result-reporter:suppress-passed-tests \
+  --class com.android.compatibility.common.tradefed.UnitTests \
+  --class com.android.compatibility.common.util.HostUnitTests \
+  --class com.android.compatibility.common.util.UnitTests \
+  --class com.android.compatibility.tradefed.CtsTradefedTest \
+  --class com.drawelements.deqp.runner.DeqpTestRunnerTest \
+  "$@"
diff --git a/tools/cts-tradefed/tests/run_tests.sh b/tools/cts-tradefed/tests/run_tests.sh
deleted file mode 100755
index 610d157..0000000
--- a/tools/cts-tradefed/tests/run_tests.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-
-# Copyright (C) 2015 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.
-
-# Helper script for running unit tests for compatibility libraries
-
-HARNESS_DIR=$(dirname ${0})/../../..
-source ${HARNESS_DIR}/test_defs.sh
-
-JARS="
-    compatibility-common-util-hostsidelib\
-    compatibility-host-util\
-    cts-tradefed-tests\
-    cts-tradefed"
-
-run_tests "com.android.compatibility.tradefed.CtsTradefedTest" "${JARS}" "${@}"
diff --git a/tools/dex-tools/Android.bp b/tools/dex-tools/Android.bp
new file mode 100644
index 0000000..d8c1148
--- /dev/null
+++ b/tools/dex-tools/Android.bp
@@ -0,0 +1,23 @@
+// Copyright (C) 2008 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.
+
+// dex-tools java library
+// ============================================================
+java_library_host {
+    name: "dex-tools",
+
+    srcs: ["src/**/*.java"],
+
+    libs: ["dx"],
+}
diff --git a/tools/dex-tools/Android.mk b/tools/dex-tools/Android.mk
deleted file mode 100644
index fa5a566..0000000
--- a/tools/dex-tools/Android.mk
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright (C) 2008 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)
-
-# dex-tools java library
-# ============================================================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-
-LOCAL_MODULE := dex-tools
-LOCAL_JAVA_LIBRARIES := dx
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-